Added 'annotate history' functionality to pages. To accomplish this, refactored tagging from pure javascript to progressive javascript. Also fixed unicode and grid filtering bugs.

This commit is contained in:
Jeremy Goecks
2009-12-08 18:49:06 -05:00
parent 0fd875b74f
commit 76914152e9
18 changed files with 949 additions and 549 deletions
+17
View File
@@ -7,6 +7,7 @@ import os, time, logging
# Pieces of Galaxy to make global in every controller
from galaxy import config, tools, web, model, util
from galaxy.web import error, form, url_for
from galaxy.model.orm import *
from Cheetah.Template import Template
@@ -25,6 +26,22 @@ class BaseController( object ):
"""Returns the application toolbox"""
return self.app.toolbox
def get_history( self, trans, id, check_ownership=True ):
"""Get a History from the database by id, verifying ownership."""
# Load history from database
id = trans.security.decode_id( id )
history = trans.sa_session.query( model.History ).get( id )
if not history:
err+msg( "History not found" )
if check_ownership:
# Verify ownership
user = trans.get_user()
if not user:
error( "Must be logged in to manage histories" )
if history.user != user:
error( "History is not owned by current user" )
return history
Root = BaseController
"""
Deprecated: `BaseController` used to be available under the name `Root`
+16 -34
View File
@@ -44,7 +44,7 @@ class HistoryListGrid( grids.Grid ):
return ""
def get_link( self, trans, grid, item ):
if item.users_shared_with or item.importable:
return dict( operation="sharing" )
return dict( operation="sharing", id=item.id )
return None
class DeletedColumn( grids.GridColumn ):
@@ -205,7 +205,7 @@ class HistoryController( BaseController ):
# Load the histories and ensure they all belong to the current user
histories = []
for history_id in history_ids:
history = get_history( trans, history_id )
history = self.get_history( trans, history_id )
if history:
# Ensure history is owned by current user
if history.user_id != None and trans.user:
@@ -237,7 +237,7 @@ class HistoryController( BaseController ):
history.importable = True
elif operation == "disable import via link":
if history_ids:
histories = [ get_history( trans, history_id ) for history_id in history_ids ]
histories = [ self.get_history( trans, history_id ) for history_id in history_ids ]
for history in histories:
if history.importable:
history.importable = False
@@ -332,7 +332,7 @@ class HistoryController( BaseController ):
if not ids:
message = "Select a history to unshare"
return self.shared_list_grid( trans, status='error', message=message, **kwargs )
histories = [ get_history( trans, history_id ) for history_id in ids ]
histories = [ self.get_history( trans, history_id ) for history_id in ids ]
for history in histories:
# Current user is the user with which the histories were shared
association = trans.sa_session.query( trans.app.model.HistoryUserShareAssociation ).filter_by( user=trans.user, history=history ).one()
@@ -375,7 +375,7 @@ class HistoryController( BaseController ):
@web.require_login( "get history name" )
def get_name_async( self, trans, id=None ):
""" Returns the name for a given history. """
history = get_history( trans, id, False )
history = self.get_history( trans, id, False )
# To get name: user must own history, history must be importable.
if history.user == trans.get_user() or history.importable or trans.get_user() in history.users_shared_with:
@@ -386,15 +386,15 @@ class HistoryController( BaseController ):
@web.require_login( "set history's importable flag" )
def set_importable_async( self, trans, id=None, importable=False ):
""" Set history's importable attribute. """
history = get_history( trans, id, True )
history = self.get_history( trans, id, True )
# Only set if importable value would change; this prevents a change in the update_time unless attribute really changed.
importable = importable in ['True', 'true', 't'];
importable = importable in ['True', 'true', 't', 'T'];
if history and history.importable != importable:
history.importable = importable
trans.sa_session.flush()
return result
return
@web.expose
def name_autocomplete_data( self, trans, q=None, limit=None, timestamp=None ):
@@ -416,7 +416,7 @@ class HistoryController( BaseController ):
user_history = trans.get_history()
if not id:
return trans.show_error_message( "You must specify a history you want to import." )
import_history = get_history( trans, id, check_ownership=False )
import_history = self.get_history( trans, id, check_ownership=False )
if not import_history:
return trans.show_error_message( "The specified history does not exist.")
if not import_history.importable:
@@ -470,7 +470,7 @@ class HistoryController( BaseController ):
# Get history to view.
if not id:
return trans.show_error_message( "You must specify a history you want to view." )
history_to_view = get_history( trans, id, False)
history_to_view = self.get_history( trans, id, False)
# Integrity checks.
if not history_to_view:
return trans.show_error_message( "The specified history does not exist." )
@@ -512,7 +512,7 @@ class HistoryController( BaseController ):
send_to_err = err_msg
histories = []
for history_id in id:
histories.append( get_history( trans, history_id ) )
histories.append( self.get_history( trans, history_id ) )
return trans.fill_template( "/history/share.mako",
histories=histories,
email=email,
@@ -618,7 +618,7 @@ class HistoryController( BaseController ):
send_to_err = ""
histories = []
for history_id in id:
histories.append( get_history( trans, history_id ) )
histories.append( self.get_history( trans, history_id ) )
send_to_users = []
for email_address in util.listify( email ):
email_address = email_address.strip()
@@ -776,7 +776,7 @@ class HistoryController( BaseController ):
if id:
ids = util.listify( id )
if ids:
histories = [ get_history( trans, history_id ) for history_id in ids ]
histories = [ self.get_history( trans, history_id ) for history_id in ids ]
for history in histories:
trans.sa_session.add( history )
if params.get( 'enable_import_via_link', False ):
@@ -831,7 +831,7 @@ class HistoryController( BaseController ):
histories = []
cur_names = []
for history_id in id:
history = get_history( trans, history_id )
history = self.get_history( trans, history_id )
if history and history.user_id == user.id:
histories.append( history )
cur_names.append( history.get_display_name() )
@@ -872,7 +872,7 @@ class HistoryController( BaseController ):
ids = util.listify( id )
histories = []
for history_id in ids:
history = get_history( trans, history_id, check_ownership=False )
history = self.get_history( trans, history_id, check_ownership=False )
histories.append( history )
user = trans.get_user()
for history in histories:
@@ -896,22 +896,4 @@ class HistoryController( BaseController ):
msg = 'Clone with name "%s" is now included in your previously stored histories.' % new_history.name
else:
msg = '%d cloned histories are now included in your previously stored histories.' % len( histories )
return trans.show_ok_message( msg )
## ---- Utility methods -------------------------------------------------------
def get_history( trans, id, check_ownership=True ):
"""Get a History from the database by id, verifying ownership."""
# Load history from database
id = trans.security.decode_id( id )
history = trans.sa_session.query( model.History ).get( id )
if not history:
err+msg( "History not found" )
if check_ownership:
# Verify ownership
user = trans.get_user()
if not user:
error( "Must be logged in to manage histories" )
if history.user != user:
error( "History is not owned by current user" )
return history
return trans.show_ok_message( msg )
+32 -1
View File
@@ -315,5 +315,36 @@ class PageController( BaseController ):
@web.expose
@web.require_login("select a history from saved histories")
def list_histories_for_selection( self, trans, **kwargs ):
""" Returns HTML that enables a user to select one or more histories. """
# Render the list view
return self._history_selection_grid( trans, **kwargs )
return self._history_selection_grid( trans, **kwargs )
@web.expose
@web.require_login("get annotation table for history")
def get_history_annotation_table( self, trans, id ):
""" Returns HTML for an annotation table for a history. """
# TODO: users should be able to annotate a history if they own it, it is importable, or it is shared with them. This only
# returns a history if a user owns it.
history = self.get_history( trans, id, True )
if history:
# TODO: Query taken from root/history; it should be moved either into history or trans object
# so that it can reused.
query = trans.sa_session.query( model.HistoryDatasetAssociation ) \
.filter( model.HistoryDatasetAssociation.history == history ) \
.options( eagerload( "children" ) ) \
.join( "dataset" ).filter( model.Dataset.purged == False ) \
.options( eagerload_all( "dataset.actions" ) ) \
.order_by( model.HistoryDatasetAssociation.hid )
# For now, do not show deleted datasets.
show_deleted = False
if not show_deleted:
query = query.filter( model.HistoryDatasetAssociation.deleted == False )
return trans.fill_template( "page/history_annotation_table.mako", history=history, datasets=query.all(), show_deleted=False )
@web.expose
def get_editor_iframe( self, trans ):
""" Returns the document for the page editor's iframe. """
return trans.fill_template( "page/wymiframe.mako" )
+2 -1
View File
@@ -178,8 +178,9 @@ class TagsController ( BaseController ):
# Create and return autocomplete data.
ac_data = "#Header|Your Values for '%s'\n" % (tag_name)
tag_uname = self._get_usernames_for_tag(trans.sa_session, trans.get_user(), tag, item_class, item_tag_assoc_class)[0]
for row in result_set:
ac_data += tag.name + ":" + row[0] + "|" + row[0] + "\n"
ac_data += tag_uname + ":" + row[0] + "|" + row[0] + "\n"
return ac_data
def _get_usernames_for_tag(self, db_session, user, tag, item_class, item_tag_assoc_class):
+1 -1
View File
@@ -360,7 +360,7 @@ class TagsColumn( TextColumn ):
elt_id="tagging-elt" + str( self.tag_elt_id_gen )
div_elt = "<div id=%s></div>" % elt_id
return div_elt + trans.fill_template( "/tagging_common.mako", trans=trans, tagged_item=item, elt_context=self.grid_name,
elt_id = elt_id, in_form="true", input_size="20", tag_click_fn="add_tag_to_grid_filter" )
elt_id = elt_id, in_form=True, input_size="20", tag_click_fn="add_tag_to_grid_filter" )
def filter( self, db_session, query, column_filter ):
""" Modify query to filter model_class by tag. Multiple filters are ANDed. """
if column_filter == "All":
+434 -462
View File
@@ -1,486 +1,458 @@
/**
* JQuery extension for tagging with autocomplete.
* @author: Jeremy Goecks
* @require: jquery.autocomplete plugin
*/
var ac_tag_area_id_gen = 1;
* JQuery extension for tagging with autocomplete.
* @author: Jeremy Goecks
* @require: jquery.autocomplete plugin
*/
jQuery.fn.autocomplete_tagging = function(elt_id, options)
{
jQuery.fn.autocomplete_tagging = function(options) {
//
// Set up function defaults.
//
var defaults =
{
get_toggle_link_text_fn: function(tags)
{
var text = "";
var num_tags = array_length(tags);
if (num_tags != 0)
text = num_tags + (num_tags != 0 ? " Tags" : " Tag");
else
// No tags.
text = "Add tags";
return text;
},
tag_click_fn : function (name, value) { },
editable: true,
input_size: 20,
in_form: false,
tags : {},
use_toggle_link: true,
item_id: "",
add_tag_img: "",
add_tag_img_rollover: "",
delete_tag_img: "",
ajax_autocomplete_tag_url: "",
ajax_retag_url: "",
ajax_delete_tag_url: "",
ajax_add_tag_url: ""
};
//
// Extend object.
//
var settings = jQuery.extend(defaults, options);
//
// Create core elements: tag area and TODO.
//
// Tag area.
var area_id = "tag-area-" + (ac_tag_area_id_gen)++;
var tag_area = $("<div>").attr("id", area_id).addClass("tag-area");
this.append(tag_area);
//
// Returns the number of keys (elements) in an array/dictionary.
//
var array_length = function(an_array)
{
if (an_array.length)
return an_array.length;
var count = 0;
for (element in an_array)
count++;
return count;
};
//
// Function to build toggle link.
//
var build_toggle_link = function()
{
var link_text = settings.get_toggle_link_text_fn(settings.tags);
var toggle_link = $("<a href='/history/tags'>").text(link_text).addClass("toggle-link");
// Link toggles the display state of the tag area.
toggle_link.click( function()
{
// Take special actions depending on whether toggle is showing or hiding link.
var showing_tag_area = (tag_area.css("display") == "none");
var after_toggle_fn;
if (showing_tag_area)
{
after_toggle_fn = function()
{
// If there are no tags, go right to editing mode by generating a
// click on the area.
var num_tags = array_length(settings.tags);
if (num_tags == 0)
tag_area.click();
};
}
else // Hiding area.
{
after_toggle_fn = function()
{
tag_area.blur();
};
}
tag_area.slideToggle("fast", after_toggle_fn);
return false;
});
return toggle_link;
};
// Add toggle link.
var toggle_link = build_toggle_link();
if (settings.use_toggle_link)
//
// Set up function defaults.
//
var defaults =
{
this.prepend(toggle_link);
}
//
// Function to build other elements.
//
get_toggle_link_text_fn: function(tags)
{
var text = "";
var num_tags = array_length(tags);
if (num_tags != 0)
text = num_tags + (num_tags != 0 ? " Tags" : " Tag");
else
// No tags.
text = "Add tags";
return text;
},
tag_click_fn : function (name, value) { },
editable: true,
input_size: 20,
in_form: false,
tags : {},
use_toggle_link: true,
item_id: "",
add_tag_img: "",
add_tag_img_rollover: "",
delete_tag_img: "",
ajax_autocomplete_tag_url: "",
ajax_retag_url: "",
ajax_delete_tag_url: "",
ajax_add_tag_url: ""
};
//
// Return a string that contains the contents of an associative array. This is
// a debugging method.
//
var assoc_array_to_str = function(an_array)
{
// Convert associative array to simple array and then join array elements.
var array_str_list = new Array();
for (key in an_array)
array_str_list[array_str_list.length] = key + "-->" + an_array[key];
return "{" + array_str_list.join(",") + "}"
};
//
// Extend object.
//
var settings = jQuery.extend(defaults, options);
//
// Collapse tag name + value into a single string.
//
var build_tag_str = function(tag_name, tag_value)
{
return tag_name + ( (tag_value != "" && tag_value) ? ":" + tag_value : "");
};
//
// Get tag name and value from a string.
//
var get_tag_name_and_value = function(tag_str)
{
return tag_str.split(":");
};
//
// Add "add tag" button.
//
var build_add_tag_button = function(tag_input_field)
{
var add_tag_button = $("<img src='" + settings.add_tag_img + "' rollover='" + settings.add_tag_img_rollover + "'/>").addClass("add-tag-button");
add_tag_button.click( function()
//
// Returns the number of keys (elements) in an array/dictionary.
//
var array_length = function(an_array)
{
// Hide button.
$(this).hide();
// Clicking on button is the same as clicking on the tag area.
tag_area.click();
return false;
if (an_array.length)
return an_array.length;
var count = 0;
for (element in an_array)
count++;
return count;
};
//
// Initalize object's elements.
//
// Get elements for this object.
var this_obj = $('#' + elt_id);
var id_parts = $(this).attr('id').split("-");
var obj_id = id_parts[ id_parts.length-1 ];
var tag_area = this_obj.find('#tag-area-' + obj_id);
var toggle_link = this_obj.find('#toggle-link-' + obj_id);
var tag_input_field = this_obj.find('#tag-input');
var add_tag_button = this_obj.find('.add-tag-button');
// Initialize toggle link.
toggle_link.click( function() {
var id = $(this).attr('id').split('-')[2];
// Take special actions depending on whether toggle is showing or hiding link.
var tag_area = $('#tag-area-' + id);
var showing_tag_area = (tag_area.css("display") == "none");
var after_toggle_fn;
if (showing_tag_area)
{
after_toggle_fn = function()
{
// If there are no tags, go right to editing mode by generating a
// click on the area.
var num_tags = $(this).find('.tag-button').length;
if (num_tags == 0)
tag_area.click();
};
}
else // Hiding area.
{
after_toggle_fn = function()
{
tag_area.blur();
};
}
tag_area.slideToggle("fast", after_toggle_fn);
return $(this);
});
return add_tag_button;
};
//
// Function that builds a tag button.
//
var build_tag_button = function(tag_str)
{
// Build "delete tag" image and handler.
var delete_img = $("<img src='" + settings.delete_tag_img + "'/>").addClass("delete-tag-img");
delete_img.mouseenter( function ()
{
$(this).attr("src", settings.delete_tag_img_rollover);
});
delete_img.mouseleave( function ()
{
$(this).attr("src", settings.delete_tag_img);
});
delete_img.click( function ()
{
// Tag button is image's parent.
var tag_button = $(this).parent();
// Get tag name, value.
var tag_name_elt = tag_button.find(".tag-name").eq(0);
var tag_str = tag_name_elt.text();
var tag_name_and_value = get_tag_name_and_value(tag_str);
var tag_name = tag_name_and_value[0];
var tag_value = tag_name_and_value[1];
var prev_button = tag_button.prev();
tag_button.remove();
// Remove tag from local list for consistency.
delete settings.tags[tag_name];
// Update toggle link text.
var new_text = settings.get_toggle_link_text_fn(settings.tags);
toggle_link.text(new_text);
// Delete tag.
$.ajax({
url: settings.ajax_delete_tag_url,
data: { tag_name: tag_name },
error: function()
{
// Failed. Roll back changes and show alert.
settings.tags[tag_name] = tag_value;
if (prev_button.hasClass("tag-button"))
prev_button.after(tag_button);
else
tag_area.prepend(tag_button);
var new_text = settings.get_toggle_link_text_fn(settings.tags);
alert( "Remove tag failed" );
toggle_link.text(new_text);
// TODO: no idea why it's necessary to set this up again.
delete_img.mouseenter( function ()
{
$(this).attr("src", settings.delete_tag_img_rollover);
});
delete_img.mouseleave( function ()
{
$(this).attr("src", settings.delete_tag_img);
});
},
success: function() {}
});
return true;
});
// Build tag button.
var tag_name_elt = $("<span>").text(tag_str).addClass("tag-name");
tag_name_elt.click( function()
{
tag_name_and_value = tag_str.split(":")
settings.tag_click_fn(tag_name_and_value[0], tag_name_and_value[1]);
return true;
});
var tag_button = $("<span></span>").addClass("tag-button");
tag_button.append(tag_name_elt);
// Allow delete only if element is editable.
// Initialize tag input field.
if (settings.editable)
tag_button.append(delete_img);
return tag_button;
};
//
// Build input + autocompete for tag.
//
var build_tag_input = function(tag_text)
{
// If element is in form, tag input is a textarea; otherwise element is a input type=text.
var t;
if (settings.in_form)
t = $( "<textarea id='history-tag-input' rows='1' cols='" +
settings.input_size + "' value='" + escape(tag_text) + "'></textarea>" );
else // element not in form.
t = $( "<input id='history-tag-input' type='text' size='" +
settings.input_size + "' value='" + escape(tag_text) + "'></input>" );
t.keyup( function( e )
tag_input_field.hide();
tag_input_field.keyup( function( e )
{
if ( e.keyCode == 27 )
{
// Escape key
$(this).trigger( "blur" );
} else if (
( e.keyCode == 13 ) || // Return Key
( e.keyCode == 188 ) || // Comma
( e.keyCode == 32 ) // Space
)
{
//
// Check input.
//
new_value = this.value;
// Do nothing if return key was used to autocomplete.
if (return_key_pressed_for_autocomplete == true)
{
return_key_pressed_for_autocomplete = false;
return false;
}
// Suppress space after a ":"
if ( new_value.indexOf(": ", new_value.length - 2) != -1)
{
this.value = new_value.substring(0, new_value.length-1);
return false;
}
// Remove trigger keys from input.
if ( (e.keyCode == 188) || (e.keyCode == 32) )
new_value = new_value.substring( 0 , new_value.length - 1 );
// Trim whitespace.
new_value = new_value.replace(/^\s+|\s+$/g,"");
// Too short?
if (new_value.length < 2)
return false;
//
// New tag OK - apply it.
//
this.value = "";
// Add button for tag after all other tag buttons.
var new_tag_button = build_tag_button(new_value);
var tag_buttons = tag_area.children(".tag-button");
if (tag_buttons.length != 0)
{
var last_tag_button = tag_buttons.slice(tag_buttons.length-1);
last_tag_button.after(new_tag_button);
}
else
tag_area.prepend(new_tag_button);
if ( e.keyCode == 27 )
{
// Escape key
$(this).trigger( "blur" );
}
else if (
( e.keyCode == 13 ) || // Return Key
( e.keyCode == 188 ) || // Comma
( e.keyCode == 32 ) // Space
)
{
//
// Check input.
//
// Add tag to internal list.
var tag_name_and_value = new_value.split(":");
settings.tags[tag_name_and_value[0]] = tag_name_and_value[1];
// Update toggle link text.
var new_text = settings.get_toggle_link_text_fn(settings.tags);
toggle_link.text(new_text);
new_value = this.value;
// Commit tag to server.
var $this = $(this);
$.ajax({
url: settings.ajax_add_tag_url,
data: { new_tag: new_value },
error: function()
{
// Failed. Roll back changes and show alert.
new_tag_button.remove();
delete settings.tags[tag_name_and_value[0]];
var new_text = settings.get_toggle_link_text_fn(settings.tags);
toggle_link.text(new_text);
alert( "Add tag failed" );
},
success: function()
{
// Flush autocomplete cache because it's not out of date.
// TODO: in the future, we could remove the particular item
// that was chosen from the cache rather than flush it.
$this.flushCache();
}
});
return false;
}
// Do nothing if return key was used to autocomplete.
if (return_key_pressed_for_autocomplete == true)
{
return_key_pressed_for_autocomplete = false;
return false;
}
// Suppress space after a ":"
if ( new_value.indexOf(": ", new_value.length - 2) != -1)
{
this.value = new_value.substring(0, new_value.length-1);
return false;
}
// Remove trigger keys from input.
if ( (e.keyCode == 188) || (e.keyCode == 32) )
new_value = new_value.substring( 0 , new_value.length - 1 );
// Trim whitespace.
new_value = new_value.replace(/^\s+|\s+$/g,"");
// Too short?
if (new_value.length < 2)
return false;
//
// New tag OK - apply it.
//
this.value = "";
// Add button for tag after all other tag buttons.
var new_tag_button = build_tag_button(new_value);
var tag_buttons = tag_area.children(".tag-button");
if (tag_buttons.length != 0)
{
var last_tag_button = tag_buttons.slice(tag_buttons.length-1);
last_tag_button.after(new_tag_button);
}
else
tag_area.prepend(new_tag_button);
// Add tag to internal list.
var tag_name_and_value = new_value.split(":");
settings.tags[tag_name_and_value[0]] = tag_name_and_value[1];
// Update toggle link text.
var new_text = settings.get_toggle_link_text_fn(settings.tags);
toggle_link.text(new_text);
// Commit tag to server.
var $this = $(this);
$.ajax({
url: settings.ajax_add_tag_url,
data: { new_tag: new_value },
error: function()
{
// Failed. Roll back changes and show alert.
new_tag_button.remove();
delete settings.tags[tag_name_and_value[0]];
var new_text = settings.get_toggle_link_text_fn(settings.tags);
toggle_link.text(new_text);
alert( "Add tag failed" );
},
success: function()
{
// Flush autocomplete cache because it's not out of date.
// TODO: in the future, we could remove the particular item
// that was chosen from the cache rather than flush it.
$this.flushCache();
}
});
return false;
}
});
// Add autocomplete to input.
var format_item_func = function(key, row_position, num_rows, value, search_term) {
tag_name_and_value = value.split(":");
return (tag_name_and_value.length == 1 ? tag_name_and_value[0] :tag_name_and_value[1]);
//var array = new Array(key, value, row_position, num_rows,
//search_term ); return "\"" + array.join("*") + "\"";
var format_item_func = function(key, row_position, num_rows, value, search_term)
{
tag_name_and_value = value.split(":");
return (tag_name_and_value.length == 1 ? tag_name_and_value[0] :tag_name_and_value[1]);
//var array = new Array(key, value, row_position, num_rows,
//search_term ); return "\"" + array.join("*") + "\"";
}
var autocomplete_options =
{ selectFirst: false, formatItem : format_item_func, autoFill: false, highlight: false };
t.autocomplete(settings.ajax_autocomplete_tag_url, autocomplete_options);
t.addClass("tag-input");
return t;
};
//
// Build tag area.
//
{ selectFirst: false, formatItem : format_item_func, autoFill: false, highlight: false };
tag_input_field.autocomplete(settings.ajax_autocomplete_tag_url, autocomplete_options);
// Add tag buttons for each current tag to the tag area.
for (tag_name in settings.tags)
// Initialize delete tag images for current tags.
this_obj.find('.delete-tag-img').each(function() {
init_delete_tag_image( $(this) );
});
this_obj.find('.tag-name').each( function() {
$(this).click( function() {
var tag_str = $(this).text();
var tag_name_and_value = tag_str.split(":")
settings.tag_click_fn(tag_name_and_value[0], tag_name_and_value[1]);
return true;
});
});
// Initialize "add tag" button.
add_tag_button.click( function()
{
var tag_value = settings.tags[tag_name];
var tag_str = build_tag_str(tag_name, tag_value);
var tag_button = build_tag_button(tag_str, toggle_link, settings.tags);
tag_area.append(tag_button);
}
// Add tag input field and "add tag" button.
var tag_input_field = build_tag_input("");
var add_tag_button = build_add_tag_button(tag_input_field);
// Hide button.
$(this).hide();
// When the tag area blurs, go to "view tag" mode.
tag_area.blur( function(e)
{
num_tags = array_length(settings.tags);
if (num_tags != 0)
{
add_tag_button.show();
tag_input_field.hide();
tag_area.removeClass("active-tag-area");
}
else
{
// No tags, so do nothing to ensure that input is still visible.
}
});
if (settings.editable)
{
tag_area.append(add_tag_button);
tag_area.append(tag_input_field);
tag_input_field.hide();
// On click, enable user to add tags.
tag_area.click( function(e)
{
var is_active = $(this).hasClass("active-tag-area");
// Clicking on button is the same as clicking on the tag area.
tag_area.click();
// If a "delete image" object was pressed and area is inactive, do nothing.
if ($(e.target).hasClass("delete-tag-img") && !is_active)
return false;
// If a "tag name" object was pressed and area is inactive, do nothing.
if ($(e.target).hasClass("tag-name") && !is_active)
return false;
// Hide add tag button, show tag_input field. Change background to show
// area is active.
$(this).addClass("active-tag-area");
add_tag_button.hide();
tag_input_field.show();
tag_input_field.focus();
// Add handler to document that will call blur when the tag area is blurred;
// a tag area is blurred when a user clicks on an element outside the area.
var handle_document_click = function(e)
{
var tag_area_id = tag_area.attr("id");
// Blur the tag area if the element clicked on is not in the tag area.
if (
($(e.target).attr("id") != tag_area_id) &&
($(e.target).parents().filter(tag_area_id).length == 0)
)
{
tag_area.blur();
$(document).unbind("click", handle_document_click);
}
};
// TODO: we should attach the click handler to all frames in order to capture
// clicks outside the frame that this element is in.
//window.parent.document.onclick = handle_document_click;
//var temp = $(window.parent.document.body).contents().find("iframe").html();
//alert(temp);
//$(document).parent().click(handle_document_click);
$(window).click(handle_document_click);
return false;
});
}
// If using toggle link, hide the tag area. Otherwise, if there are no tags,
// hide the "add tags" button and show the input field.
if (settings.use_toggle_link)
tag_area.hide();
else
});
//
// Set up tag area interactions; these are needed only if tags are editable.
//
if (settings.editable)
{
var num_tags = array_length(settings.tags);
if (num_tags == 0)
{
add_tag_button.hide();
tag_input_field.show();
}
// When the tag area blurs, go to "view tag" mode.
tag_area.blur( function(e)
{
num_tags = array_length(settings.tags);
if (num_tags != 0)
{
add_tag_button.show();
tag_input_field.hide();
tag_area.removeClass("active-tag-area");
}
else
{
// No tags, so do nothing to ensure that input is still visible.
}
});
// On click, enable user to add tags.
tag_area.click( function(e)
{
var is_active = $(this).hasClass("active-tag-area");
// If a "delete image" object was pressed and area is inactive, do nothing.
if ($(e.target).hasClass("delete-tag-img") && !is_active)
return false;
// If a "tag name" object was pressed and area is inactive, do nothing.
if ($(e.target).hasClass("tag-name") && !is_active)
return false;
// Hide add tag button, show tag_input field. Change background to show
// area is active.
$(this).addClass("active-tag-area");
add_tag_button.hide();
tag_input_field.show();
tag_input_field.focus();
// Add handler to document that will call blur when the tag area is blurred;
// a tag area is blurred when a user clicks on an element outside the area.
var handle_document_click = function(e)
{
var tag_area_id = tag_area.attr("id");
// Blur the tag area if the element clicked on is not in the tag area.
if (
($(e.target).attr("id") != tag_area_id) &&
($(e.target).parents().filter(tag_area_id).length == 0)
)
{
tag_area.blur();
$(document).unbind("click", handle_document_click);
}
};
// TODO: we should attach the click handler to all frames in order to capture
// clicks outside the frame that this element is in.
//window.parent.document.onclick = handle_document_click;
//var temp = $(window.parent.document.body).contents().find("iframe").html();
//alert(temp);
//$(document).parent().click(handle_document_click);
$(window).click(handle_document_click);
return false;
});
}
return this.addClass("tag-element");
}
// If using toggle link, hide the tag area. Otherwise, show the tag area.
if (settings.use_toggle_link)
tag_area.hide();
else
{
var num_tags = array_length(settings.tags);
if (num_tags == 0)
{
add_tag_button.hide();
tag_input_field.show();
}
}
// Initialize tag names.
//$('.tag-name').
//
// Helper functions.
//
//
// Collapse tag name + value into a single string.
//
function build_tag_str(tag_name, tag_value)
{
return tag_name + ( (tag_value != "" && tag_value) ? ":" + tag_value : "");
};
// Initialize a "delete tag image": when click, delete tag from UI and send delete request to server.
function init_delete_tag_image(delete_img)
{
$(delete_img).mouseenter( function ()
{
$(this).attr("src", settings.delete_tag_img_rollover);
});
$(delete_img).mouseleave( function ()
{
$(this).attr("src", settings.delete_tag_img);
});
$(delete_img).click( function ()
{
// Tag button is image's parent.
var tag_button = $(this).parent();
// Get tag name, value.
var tag_name_elt = tag_button.find(".tag-name").eq(0);
var tag_str = tag_name_elt.text();
var tag_name_and_value = get_tag_name_and_value(tag_str);
var tag_name = tag_name_and_value[0];
var tag_value = tag_name_and_value[1];
var prev_button = tag_button.prev();
tag_button.remove();
// Remove tag from local list for consistency.
delete settings.tags[tag_name];
// Update toggle link text.
var new_text = settings.get_toggle_link_text_fn(settings.tags);
toggle_link.text(new_text);
// Delete tag.
$.ajax({
url: settings.ajax_delete_tag_url,
data: { tag_name: tag_name },
error: function()
{
// Failed. Roll back changes and show alert.
settings.tags[tag_name] = tag_value;
if (prev_button.hasClass("tag-button"))
prev_button.after(tag_button);
else
tag_area.prepend(tag_button);
var new_text = settings.get_toggle_link_text_fn(settings.tags);
alert( "Remove tag failed" );
toggle_link.text(new_text);
// TODO: no idea why it's necessary to set this up again.
delete_img.mouseenter( function ()
{
$(this).attr("src", settings.delete_tag_img_rollover);
});
delete_img.mouseleave( function ()
{
$(this).attr("src", settings.delete_tag_img);
});
},
success: function() {}
});
return true;
});
};
//
// Return a string that contains the contents of an associative array. This is
// a debugging method.
//
function assoc_array_to_str(an_array)
{
// Convert associative array to simple array and then join array elements.
var array_str_list = new Array();
for (key in an_array)
array_str_list[array_str_list.length] = key + "-->" + an_array[key];
return "{" + array_str_list.join(",") + "}"
};
//
// Get tag name and value from a string.
//
function get_tag_name_and_value(tag_str)
{
return tag_str.split(":");
};
//
// Function that builds a tag button.
//
function build_tag_button(tag_str)
{
// Build "delete tag" image.
var delete_img = $("<img src='" + settings.delete_tag_img + "'/>").addClass("delete-tag-img");
init_delete_tag_image(delete_img);
// Build tag button.
var tag_name_elt = $("<span>").text(tag_str).addClass("tag-name");
tag_name_elt.click( function()
{
tag_name_and_value = tag_str.split(":")
settings.tag_click_fn(tag_name_and_value[0], tag_name_and_value[1]);
return true;
});
var tag_button = $("<span></span>").addClass("tag-button");
tag_button.append(tag_name_elt);
// Allow delete only if element is editable.
if (settings.editable)
tag_button.append(delete_img);
return tag_button;
};
};
+3 -2
View File
@@ -370,8 +370,9 @@ jQuery.fn.wymeditor = function(options) {
iframeHtml:"<div class='wym_iframe wym_section'>"
+ "<iframe "
+ "src='"
+ WYMeditor.IFRAME_BASE_PATH
+ "wymiframe.html' "
+ "/page/get_editor_iframe' "
//+ WYMeditor.IFRAME_BASE_PATH
//+ "wymiframe.html' "
+ "onload='this.contentWindow.parent.WYMeditor.INSTANCES["
+ WYMeditor.INDEX + "].initIframe(this)'"
+ "></iframe>"
@@ -1 +1 @@
var ac_tag_area_id_gen=1;jQuery.fn.autocomplete_tagging=function(c){var e={get_toggle_link_text_fn:function(u){var w="";var v=o(u);if(v!=0){w=v+(v!=0?" Tags":" Tag")}else{w="Add tags"}return w},tag_click_fn:function(u,v){},editable:true,input_size:20,in_form:false,tags:{},use_toggle_link:true,item_id:"",add_tag_img:"",add_tag_img_rollover:"",delete_tag_img:"",ajax_autocomplete_tag_url:"",ajax_retag_url:"",ajax_delete_tag_url:"",ajax_add_tag_url:""};var p=jQuery.extend(e,c);var k="tag-area-"+(ac_tag_area_id_gen)++;var m=$("<div>").attr("id",k).addClass("tag-area");this.append(m);var o=function(u){if(u.length){return u.length}var v=0;for(element in u){v++}return v};var b=function(){var u=p.get_toggle_link_text_fn(p.tags);var v=$("<a href='/history/tags'>").text(u).addClass("toggle-link");v.click(function(){var w=(m.css("display")=="none");var x;if(w){x=function(){var y=o(p.tags);if(y==0){m.click()}}}else{x=function(){m.blur()}}m.slideToggle("fast",x);return false});return v};var s=b();if(p.use_toggle_link){this.prepend(s)}var t=function(u){var v=new Array();for(key in u){v[v.length]=key+"-->"+u[key]}return"{"+v.join(",")+"}"};var a=function(v,u){return v+((u!=""&&u)?":"+u:"")};var h=function(u){return u.split(":")};var i=function(u){var v=$("<img src='"+p.add_tag_img+"' rollover='"+p.add_tag_img_rollover+"'/>").addClass("add-tag-button");v.click(function(){$(this).hide();m.click();return false});return v};var j=function(u){var v=$("<img src='"+p.delete_tag_img+"'/>").addClass("delete-tag-img");v.mouseenter(function(){$(this).attr("src",p.delete_tag_img_rollover)});v.mouseleave(function(){$(this).attr("src",p.delete_tag_img)});v.click(function(){var D=$(this).parent();var C=D.find(".tag-name").eq(0);var B=C.text();var z=h(B);var F=z[0];var y=z[1];var E=D.prev();D.remove();delete p.tags[F];var A=p.get_toggle_link_text_fn(p.tags);s.text(A);$.ajax({url:p.ajax_delete_tag_url,data:{tag_name:F},error:function(){p.tags[F]=y;if(E.hasClass("tag-button")){E.after(D)}else{m.prepend(D)}var G=p.get_toggle_link_text_fn(p.tags);alert("Remove tag failed");s.text(G);v.mouseenter(function(){$(this).attr("src",p.delete_tag_img_rollover)});v.mouseleave(function(){$(this).attr("src",p.delete_tag_img)})},success:function(){}});return true});var w=$("<span>").text(u).addClass("tag-name");w.click(function(){tag_name_and_value=u.split(":");p.tag_click_fn(tag_name_and_value[0],tag_name_and_value[1]);return true});var x=$("<span></span>").addClass("tag-button");x.append(w);if(p.editable){x.append(v)}return x};var d=function(v){var u;if(p.in_form){u=$("<textarea id='history-tag-input' rows='1' cols='"+p.input_size+"' value='"+escape(v)+"'></textarea>")}else{u=$("<input id='history-tag-input' type='text' size='"+p.input_size+"' value='"+escape(v)+"'></input>")}u.keyup(function(D){if(D.keyCode==27){$(this).trigger("blur")}else{if((D.keyCode==13)||(D.keyCode==188)||(D.keyCode==32)){new_value=this.value;if(return_key_pressed_for_autocomplete==true){return_key_pressed_for_autocomplete=false;return false}if(new_value.indexOf(": ",new_value.length-2)!=-1){this.value=new_value.substring(0,new_value.length-1);return false}if((D.keyCode==188)||(D.keyCode==32)){new_value=new_value.substring(0,new_value.length-1)}new_value=new_value.replace(/^\s+|\s+$/g,"");if(new_value.length<2){return false}this.value="";var A=j(new_value);var z=m.children(".tag-button");if(z.length!=0){var E=z.slice(z.length-1);E.after(A)}else{m.prepend(A)}var y=new_value.split(":");p.tags[y[0]]=y[1];var B=p.get_toggle_link_text_fn(p.tags);s.text(B);var C=$(this);$.ajax({url:p.ajax_add_tag_url,data:{new_tag:new_value},error:function(){A.remove();delete p.tags[y[0]];var F=p.get_toggle_link_text_fn(p.tags);s.text(F);alert("Add tag failed")},success:function(){C.flushCache()}});return false}}});var w=function(A,z,y,C,B){tag_name_and_value=C.split(":");return(tag_name_and_value.length==1?tag_name_and_value[0]:tag_name_and_value[1])};var x={selectFirst:false,formatItem:w,autoFill:false,highlight:false};u.autocomplete(p.ajax_autocomplete_tag_url,x);u.addClass("tag-input");return u};for(tag_name in p.tags){var q=p.tags[tag_name];var l=a(tag_name,q);var g=j(l,s,p.tags);m.append(g)}var n=d("");var f=i(n);m.blur(function(u){r=o(p.tags);if(r!=0){f.show();n.hide();m.removeClass("active-tag-area")}else{}});if(p.editable){m.append(f);m.append(n);n.hide();m.click(function(w){var v=$(this).hasClass("active-tag-area");if($(w.target).hasClass("delete-tag-img")&&!v){return false}if($(w.target).hasClass("tag-name")&&!v){return false}$(this).addClass("active-tag-area");f.hide();n.show();n.focus();var u=function(y){var x=m.attr("id");if(($(y.target).attr("id")!=x)&&($(y.target).parents().filter(x).length==0)){m.blur();$(document).unbind("click",u)}};$(window).click(u);return false})}if(p.use_toggle_link){m.hide()}else{var r=o(p.tags);if(r==0){f.hide();n.show()}}return this.addClass("tag-element")};
jQuery.fn.autocomplete_tagging=function(f,d){var g={get_toggle_link_text_fn:function(u){var w="";var v=o(u);if(v!=0){w=v+(v!=0?" Tags":" Tag")}else{w="Add tags"}return w},tag_click_fn:function(u,v){},editable:true,input_size:20,in_form:false,tags:{},use_toggle_link:true,item_id:"",add_tag_img:"",add_tag_img_rollover:"",delete_tag_img:"",ajax_autocomplete_tag_url:"",ajax_retag_url:"",ajax_delete_tag_url:"",ajax_add_tag_url:""};var q=jQuery.extend(g,d);var o=function(u){if(u.length){return u.length}var v=0;for(element in u){v++}return v};var e=$("#"+f);var m=$(this).attr("id").split("-");var a=m[m.length-1];var l=e.find("#tag-area-"+a);var s=e.find("#toggle-link-"+a);var n=e.find("#tag-input");var h=e.find(".add-tag-button");s.click(function(){var x=$(this).attr("id").split("-")[2];var v=$("#tag-area-"+x);var u=(v.css("display")=="none");var w;if(u){w=function(){var y=$(this).find(".tag-button").length;if(y==0){v.click()}}}else{w=function(){v.blur()}}v.slideToggle("fast",w);return $(this)});if(q.editable){n.hide()}n.keyup(function(z){if(z.keyCode==27){$(this).trigger("blur")}else{if((z.keyCode==13)||(z.keyCode==188)||(z.keyCode==32)){new_value=this.value;if(return_key_pressed_for_autocomplete==true){return_key_pressed_for_autocomplete=false;return false}if(new_value.indexOf(": ",new_value.length-2)!=-1){this.value=new_value.substring(0,new_value.length-1);return false}if((z.keyCode==188)||(z.keyCode==32)){new_value=new_value.substring(0,new_value.length-1)}new_value=new_value.replace(/^\s+|\s+$/g,"");if(new_value.length<2){return false}this.value="";var w=j(new_value);var v=l.children(".tag-button");if(v.length!=0){var A=v.slice(v.length-1);A.after(w)}else{l.prepend(w)}var u=new_value.split(":");q.tags[u[0]]=u[1];var x=q.get_toggle_link_text_fn(q.tags);s.text(x);var y=$(this);$.ajax({url:q.ajax_add_tag_url,data:{new_tag:new_value},error:function(){w.remove();delete q.tags[u[0]];var B=q.get_toggle_link_text_fn(q.tags);s.text(B);alert("Add tag failed")},success:function(){y.flushCache()}});return false}}});var c=function(w,v,u,y,x){tag_name_and_value=y.split(":");return(tag_name_and_value.length==1?tag_name_and_value[0]:tag_name_and_value[1])};var k={selectFirst:false,formatItem:c,autoFill:false,highlight:false};n.autocomplete(q.ajax_autocomplete_tag_url,k);e.find(".delete-tag-img").each(function(){p($(this))});e.find(".tag-name").each(function(){$(this).click(function(){var v=$(this).text();var u=v.split(":");q.tag_click_fn(u[0],u[1]);return true})});h.click(function(){$(this).hide();l.click();return false});if(q.editable){l.blur(function(u){r=o(q.tags);if(r!=0){h.show();n.hide();l.removeClass("active-tag-area")}else{}});l.click(function(w){var v=$(this).hasClass("active-tag-area");if($(w.target).hasClass("delete-tag-img")&&!v){return false}if($(w.target).hasClass("tag-name")&&!v){return false}$(this).addClass("active-tag-area");h.hide();n.show();n.focus();var u=function(y){var x=l.attr("id");if(($(y.target).attr("id")!=x)&&($(y.target).parents().filter(x).length==0)){l.blur();$(document).unbind("click",u)}};$(window).click(u);return false})}if(q.use_toggle_link){l.hide()}else{var r=o(q.tags);if(r==0){h.hide();n.show()}}function b(v,u){return v+((u!=""&&u)?":"+u:"")}function p(u){$(u).mouseenter(function(){$(this).attr("src",q.delete_tag_img_rollover)});$(u).mouseleave(function(){$(this).attr("src",q.delete_tag_img)});$(u).click(function(){var A=$(this).parent();var z=A.find(".tag-name").eq(0);var y=z.text();var w=i(y);var C=w[0];var v=w[1];var B=A.prev();A.remove();delete q.tags[C];var x=q.get_toggle_link_text_fn(q.tags);s.text(x);$.ajax({url:q.ajax_delete_tag_url,data:{tag_name:C},error:function(){q.tags[C]=v;if(B.hasClass("tag-button")){B.after(A)}else{l.prepend(A)}var D=q.get_toggle_link_text_fn(q.tags);alert("Remove tag failed");s.text(D);u.mouseenter(function(){$(this).attr("src",q.delete_tag_img_rollover)});u.mouseleave(function(){$(this).attr("src",q.delete_tag_img)})},success:function(){}});return true})}function t(u){var v=new Array();for(key in u){v[v.length]=key+"-->"+u[key]}return"{"+v.join(",")+"}"}function i(u){return u.split(":")}function j(u){var v=$("<img src='"+q.delete_tag_img+"'/>").addClass("delete-tag-img");p(v);var w=$("<span>").text(u).addClass("tag-name");w.click(function(){tag_name_and_value=u.split(":");q.tag_click_fn(tag_name_and_value[0],tag_name_and_value[1]);return true});var x=$("<span></span>").addClass("tag-button");x.append(w);if(q.editable){x.append(v)}return x}};
+1
View File
@@ -45,5 +45,6 @@ WYMeditor.STRINGS['en'] = {
// Galaxy replacements.
Galaxy_History_Link: 'Insert Link to History',
Galaxy_Dataset_Link: 'Insert Link to Dataset',
Annotate_Galaxy_History: 'Annotate History',
};
+7 -3
View File
@@ -54,12 +54,16 @@
<label>
Tags:
</label>
<div id="dataset-tag-area"
style="float: left; margin-left: 1px; width: 295px; margin-right: 10px; border-style: inset; border-color: #ddd; border-width: 1px">
<div style="float: left; width: 295px; margin-right: 10px; border-style: inset; border-width: 1px">
<style>
.tag-area {
border: none;
}
</style>
${render_tagging_element(data, "edit_attributes.mako", use_toggle_link=False, in_form=True, input_size="30")}
</div>
<div style="clear: both"></div>
</div>
${render_tagging_element(data, "dataset-tag-area", "edit_attributes.mako", use_toggle_link="false", in_form="true", input_size="30")}
%endif
%for name, spec in data.metadata.spec.items():
%if spec.visible:
+1 -1
View File
@@ -14,7 +14,7 @@
%else:
%for history in histories:
<div class="toolForm">
<div class="toolFormTitle">History '${history.name}' shared with</div>
<div class="toolFormTitle">History '${history.get_display_name()}' shared with</div>
<div class="toolFormBody">
<div class="form-row">
<div style="float: right;">
+2 -5
View File
@@ -2,7 +2,7 @@
<%def name="javascripts()">
${parent.javascripts()}
${h.js( "jquery", "json2", "jquery.jstore-all", "jquery.autocomplete", "autocomplete_tagging" )}
${h.js( "galaxy.base", "jquery", "json2", "jquery.jstore-all", "jquery.autocomplete", "autocomplete_tagging" )}
</%def>
<%def name="stylesheets()">
@@ -320,14 +320,11 @@
<p></p>
%endif
<div id="history-tag-area" style="margin-bottom: 1em">
</div>
<%namespace file="../tagging_common.mako" import="render_tagging_element" />
%if trans.get_user() is not None:
<div id='history-tag-area' class="tag-element"></div>
${render_tagging_element(history, "history-tag-area", "history/view.mako", use_toggle_link='false', get_toggle_link_text_fn='get_toggle_link_text', editable=user_owns_history)}
${render_tagging_element(tagged_item=history, elt_context="history/view.mako", use_toggle_link=False, get_toggle_link_text_fn='get_toggle_link_text', editable=user_owns_history)}
%endif
%if not datasets:
+169
View File
@@ -2,6 +2,175 @@
<%def name="title()">Galaxy :: ${page.user.username} :: ${page.title}</%def>
<%def name="javascripts()">
${parent.javascripts()}
${h.js( "jquery", "json2", "jquery.jstore-all", "jquery.autocomplete", "autocomplete_tagging" )}
<script type="text/javascript">
$(function() {
// Load jStore for local storage
$.extend(jQuery.jStore.defaults, { project: 'galaxy', flash: '/static/jStore.Flash.html' })
$.jStore.load(); // Auto-select best storage
$.jStore.ready(function(engine) {
engine.ready(function() {
// Init stuff that requires the local storage to be running
//initShowHide();
setupHistoryItem( $("div.historyItemWrapper") );
// Hide all for now.
$( "div.historyItemBody:visible" ).each( function() {
if ( $.browser.mozilla ) {
$(this).find( "pre.peek" ).css( "overflow", "hidden" );
}
$(this).slideUp( "fast" );
});
});
});
});
// Functionized so AJAX'd datasets can call them
function initShowHide() {
// Load saved state and show as necessary
try {
var stored = $.jStore.store("history_expand_state");
if (stored) {
var st = JSON.parse(stored);
for (var id in st) {
$("#" + id + " div.historyItemBody" ).show();
}
}
} catch(err) {
// Something was wrong with values in storage, so clear storage
$.jStore.remove("history_expand_state");
}
// If Mozilla, hide scrollbars in hidden items since they cause animation bugs
if ( $.browser.mozilla ) {
$( "div.historyItemBody" ).each( function() {
if ( ! $(this).is( ":visible" ) ) $(this).find( "pre.peek" ).css( "overflow", "hidden" );
})
}
}
// Add show/hide link and delete link to a history item
function setupHistoryItem( query ) {
query.each( function() {
var id = this.id;
var body = $(this).children( "div.historyItemBody" );
var peek = body.find( "pre.peek" )
$(this).children( ".historyItemTitleBar" ).find( ".historyItemTitle" ).wrap( "<a href='#'></a>" ).click( function() {
if ( body.is(":visible") ) {
// Hiding stuff here
if ( $.browser.mozilla ) { peek.css( "overflow", "hidden" ) }
body.slideUp( "fast" );
// Save setting
var stored = $.jStore.store("history_expand_state")
var prefs = stored ? JSON.parse(stored) : null
if (prefs) {
delete prefs[id];
$.jStore.store("history_expand_state", JSON.stringify(prefs));
}
} else {
// Showing stuff here
body.slideDown( "fast", function() {
if ( $.browser.mozilla ) { peek.css( "overflow", "auto" ); }
});
// Save setting
var stored = $.jStore.store("history_expand_state")
var prefs = stored ? JSON.parse(stored) : new Object;
prefs[id] = true;
$.jStore.store("history_expand_state", JSON.stringify(prefs));
}
return false;
});
// Delete link
$(this).find( "div.historyItemButtons > .delete" ).each( function() {
var data_id = this.id.split( "-" )[1];
$(this).click( function() {
$( '#historyItem-' + data_id + "> div.historyItemTitleBar" ).addClass( "spinner" );
$.ajax({
url: "${h.url_for( action='delete_async', id='XXX' )}".replace( 'XXX', data_id ),
error: function() { alert( "Delete failed" ) },
success: function() {
%if show_deleted:
var to_update = {};
to_update[data_id] = "none";
updater( to_update );
%else:
$( "#historyItem-" + data_id ).fadeOut( "fast", function() {
$( "#historyItemContainer-" + data_id ).remove();
if ( $( "div.historyItemContainer" ).length < 1 ) {
$( "#emptyHistoryMessage" ).show();
}
});
%endif
}
});
return false;
});
});
// Undelete link
$(this).find( "a.historyItemUndelete" ).each( function() {
var data_id = this.id.split( "-" )[1];
$(this).click( function() {
$( '#historyItem-' + data_id + " > div.historyItemTitleBar" ).addClass( "spinner" );
$.ajax({
url: "${h.url_for( controller='dataset', action='undelete_async', id='XXX' )}".replace( 'XXX', data_id ),
error: function() { alert( "Undelete failed" ) },
success: function() {
var to_update = {};
to_update[data_id] = "none";
updater( to_update );
}
});
return false;
});
});
});
};
//TODO: this function is a duplicate of array_length defined in galaxy.base.js ; not sure why it needs to be redefined here (due to streaming?).
// Returns the number of keys (elements) in an array/dictionary.
var array_length = function(an_array)
{
if (an_array.length)
return an_array.length;
var count = 0;
for (element in an_array)
count++;
return count;
};
//
// Function provides text for tagging toggle link.
//
var get_toggle_link_text = function(tags)
{
var text = "";
var num_tags = array_length(tags);
if (num_tags != 0)
{
text = num_tags + (num_tags != 1 ? " Tags" : " Tag");
}
else
{
// No tags.
text = "Add tags to history";
}
return text;
};
</script>
</%def>
<%def name="stylesheets()">
${parent.stylesheets()}
${h.css( "base", "history", "autocomplete_tagging" )}
</%def>
<%def name="init()">
<%
self.has_left_panel=False
+83 -22
View File
@@ -21,31 +21,37 @@
</%def>
<%def name="javascripts()">
${parent.javascripts()}
<script type='text/javascript' src="${h.url_for('/static/scripts/jquery.js')}"> </script>
<script type='text/javascript' src="${h.url_for('/static/scripts/jquery.event.drag.js')}"> </script>
<script type='text/javascript' src="${h.url_for('/static/scripts/jquery.event.drop.js')}"> </script>
<script type='text/javascript' src="${h.url_for('/static/scripts/jquery.event.hover.js')}"> </script>
<script type='text/javascript' src="${h.url_for('/static/scripts/jquery.form.js')}"> </script>
<script type='text/javascript' src="${h.url_for('/static/scripts/jquery.jstore-all.js')}"> </script>
<script type='text/javascript' src="${h.url_for('/static/scripts/json2.js')}"> </script>
<script type='text/javascript' src="${h.url_for('/static/scripts/galaxy.base.js')}"> </script>
<script type='text/javascript' src="${h.url_for('/static/scripts/jquery.wymeditor.js')}"> </script>
<script type='text/javascript' src="${h.url_for('/static/scripts/jquery.autocomplete.js')}"> </script>
${h.js( "jquery", "jquery.event.drag", "jquery.event.drop", "jquery.event.hover", "jquery.form", "jquery.jstore-all", "json2",
"galaxy.base", "jquery.wymeditor", "jquery.autocomplete", "autocomplete_tagging")}
<script type="text/javascript">
// Useful Galaxy stuff.
var Galaxy =
{
DIALOG_HISTORY_LINK : "history_link",
DIALOG_HISTORY_ANNOTATE : "history_annotate",
};
// Initialize Galaxy elements.
function init_galaxy_elts(wym)
{
// Set up events to make annotation easy.
$('.annotation', wym._doc.body).each( function()
{
$(this).click( function() {
// Works in Safari, not in Firefox.
var range = wym._doc.createRange();
range.selectNodeContents( this );
var selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
var t = "";
});
});
};
## Completely replace WYM's dialog handling
WYMeditor.editor.prototype.dialog = function( dialogType, dialogFeatures, bodyHtml ) {
@@ -193,7 +199,7 @@
);
}
// HISTORY DIALOG
// INSERT HISTORY LINK DIALOG
if ( dialogType == Galaxy.DIALOG_HISTORY_LINK ) {
$.ajax(
{
@@ -244,7 +250,7 @@
// Get history name.
$.get( '${h.url_for( controller='history', action='get_name_async' )}?id=' + item_id, function( history_name ) {
var href = '${h.url_for( controller='history', action='view' )}?id=' + item_id;
wym.insert("<a href='" + href + "'>History '" + history_name + "'</a>nbsp;");
wym.insert("<a href='" + href + "'>History '" + history_name + "'</a>");
});
}
else
@@ -268,6 +274,54 @@
}
});
}
// ANNOTATE HISTORY DIALOG
if ( dialogType == Galaxy.DIALOG_ANNOTATE_HISTORY ) {
$.ajax(
{
url: "${h.url_for( action='list_histories_for_selection' )}",
data: {},
error: function() { alert( "Grid refresh failed" ) },
success: function(table_html)
{
show_modal(
"Insert Link to History",
table_html,
{
"Annotate": function()
{
// Insert links to history for each checked item.
var item_ids = new Array();
$('input[name=id]:checked').each(function() {
var item_id = $(this).val();
// Get annotation table for history.
$.ajax(
{
url: "${h.url_for( action='get_history_annotation_table' )}",
data: { id : item_id },
error: function() { alert( "Grid refresh failed" ) },
success: function(result)
{
// Insert into document.
wym.insert(result);
init_galaxy_elts(wym);
}
});
});
hide_modal();
},
"Cancel": function()
{
hide_modal();
}
}
);
}
});
}
};
</script>
@@ -313,7 +367,8 @@
{'name': 'Unlink', 'title': 'Unlink', 'css': 'wym_tools_unlink'},
{'name': 'InsertImage', 'title': 'Image', 'css': 'wym_tools_image'},
{'name': 'InsertTable', 'title': 'Table', 'css': 'wym_tools_table'},
{'name': 'Insert Galaxy History Link', 'title' : 'Galaxy_History_Link', 'css' : 'galaxy_tools_insert_history_link'}
{'name': 'Insert Galaxy History Link', 'title' : 'Galaxy_History_Link', 'css' : 'galaxy_tools_insert_history_link'},
{'name': 'Annonate Galaxy History', 'title' : 'Annotate_Galaxy_History', 'css' : 'galaxy_tools_annotate_history'},
]
});
## Get the editor object
@@ -367,13 +422,19 @@
$('.galaxy_tools_insert_history_link').children().click( function() {
editor.dialog(Galaxy.DIALOG_HISTORY_LINK);
});
// Initialize 'Annotate history' button.
$('.galaxy_tools_annotate_history').children().click( function() {
editor.dialog(Galaxy.ANNOTATE_HISTORY);
});
// Initialize galaxy elements.
//init_galaxy_elts(editor);
});
</script>
</%def>
<%def name="stylesheets()">
${parent.stylesheets()}
${h.css( "autocomplete_tagging" )}
${h.css( "base", "history", "autocomplete_tagging" )}
</%def>
<%def name="center_panel()">
@@ -384,7 +445,7 @@
<a id="close-button" class="panel-header-button">Close</a>
</div>
<div class="unified-panel-header-inner">
Page editor
Page Editor <span style="font-weight: normal">| Title : ${page.title}</span>
</div>
</div>
@@ -0,0 +1,59 @@
<%namespace file="../tagging_common.mako" import="render_tagging_element_html" />
<%namespace file="../root/history_common.mako" import="render_dataset" />
<table>
## Table header.
<tr>
<th colspan='2'>History '${history.get_display_name()}'</th>
</tr>
<tr>
## Status messages and tags.
<th colspan='2'>
%if history.deleted:
<div class="warningmessagesmall">
${_('This is a deleted history.')}
</div>
%endif
%if trans.get_user() is not None:
Tags: ${render_tagging_element_html( tagged_item=history, editable=False, use_toggle_link=False )}
%endif
</th>
</tr>
<tr>
<th colspan="2">Description of History:
<ol>
<li>What was the motivation for this history?
<li>What is the outcome of this history?
<li>What are unresolved questions from this history?
<li>What new questions arise from this history?
</ol>
</th>
</tr>
## Table body. For each dataset, there is an area to annotate the dataset.
%if not datasets:
<tr>
<td>
<div class="infomessagesmall" id="emptyHistoryMessage">
${_("Your history is empty. Click 'Get Data' on the left pane to start")}
</div>
</td>
</tr>
%else:
## Render requested datasets.
%for data in datasets:
%if data.visible:
<tr>
<td valign="top"><span class="annotation">Describe this step: why was it done? what data does it produce?</span></td>
##<td valign="top" class="annotation">Describe this step: why was it done? what data does it produce?</td>
<td>
<div class="historyItemContainer" id="historyItemContainer-${data.id}">
${render_dataset( data, data.hid, show_deleted_on_refresh = show_deleted, user_owns_dataset = False )}
</div>
</td>
</tr>
%endif
%endfor
%endif
</table>
+27
View File
@@ -0,0 +1,27 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<!--
* WYMeditor : what you see is What You Mean web-based editor
* Copyright (c) 2005 - 2009 Jean-Francois Hovinne, http://www.wymeditor.org/
* Dual licensed under the MIT (MIT-license.txt)
* and GPL (GPL-license.txt) licenses.
*
* For further information visit:
* http://www.wymeditor.org/
*
* File Name:
* wymiframe.html
* Iframe used by designMode.
* See the documentation for more info.
*
* File Authors:
* Jean-Francois Hovinne (jf.hovinne a-t wymeditor dotorg)
-->
<html>
<head>
<title>WYMeditor iframe</title>
<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7" />
<link rel="stylesheet" type="text/css" media="screen" href="/static/wymeditor/iframe/galaxy/wymiframe.css" />
${h.css("base", "history", "autocomplete_tagging")}
</head>
<body class="wym_iframe text-content"></body>
</html>
+7 -5
View File
@@ -77,6 +77,7 @@ $(function() {
<% updateable = [data for data in reversed( datasets ) if data.visible and data.state not in [ "deleted", "empty", "error", "ok" ]] %>
${ ",".join( map(lambda data: "\"%s\" : \"%s\"" % (data.id, data.state), updateable) ) }
});
// Navigate to a dataset.
%if hda_id:
self.location = "#${hda_id}";
@@ -317,15 +318,16 @@ var updater_callback = function ( tracked_datasets ) {
<p></p>
%endif
<div id="history-tag-area" style="margin-bottom: 1em">
</div>
<%namespace file="../tagging_common.mako" import="render_tagging_element" />
<%namespace file="history_common.mako" import="render_dataset" />
%if trans.get_user() is not None:
<div id='history-tag-area' class="tag-element"></div>
${render_tagging_element( tagged_item=history, elt_id="history-tag-area", elt_context="history.mako", get_toggle_link_text_fn='get_toggle_link_text' )}
<style>
.tag-element {
margin-bottom: 0.5em;
}
</style>
${render_tagging_element( tagged_item=history, elt_context='history.mako', get_toggle_link_text_fn='get_toggle_link_text' )}
%endif
%if not datasets:
+87 -11
View File
@@ -1,10 +1,76 @@
<%!
from cgi import escape
from galaxy.web.framework.helpers import iff
%>
## Render a tagging element if there is a tagged_item.
%if tagged_item is not None and elt_id is not None:
${render_tagging_element(tagged_item=tagged_item, elt_id=elt_id, elt_context=elt_context, in_form=in_form, input_size=input_size, tag_click_fn=tag_click_fn)}
%if tagged_item is not None:
${render_tagging_element(tagged_item=tagged_item, elt_context=elt_context, in_form=in_form, input_size=input_size, tag_click_fn=tag_click_fn)}
%endif
## Render HTML for a tagging element.
<%def name="render_tagging_element_html(tagged_item=None, editable=True, use_toggle_link=True, input_size='15', in_form=False)">
## Useful attributes.
<%
tagged_item_id = str( trans.security.encode_id (tagged_item.id) )
elt_id = "tag-element-" + tagged_item_id
%>
<div id="${elt_id}" class="tag-element">
%if use_toggle_link:
<a id="toggle-link-${tagged_item_id}" class="toggle-link" href="#">${len(tagged_item.tags)} Tags</a>
%endif
<div id="tag-area-${tagged_item_id}" class="tag-area">
## Build buttons for current tags.
%for tag in tagged_item.tags:
<%
tag_name = tag.user_tname
tag_value = None
if tag.value is not None:
tag_value = tag.user_value
## Convert tag name, value to unicode.
if isinstance( tag_name, str ):
tag_name = unicode( escape( tag_name ), 'utf-8' )
if tag_value:
tag_value = unicode( escape( tag_value ), 'utf-8' )
tag_str = tag_name + ":" + tag_value
else:
tag_str = tag_name
%>
<span class="tag-button">
<span class="tag-name">${tag_str}</span>
%if editable:
<img class="delete-tag-img" src="${h.url_for('/static/images/delete_tag_icon_gray.png')}"/>
%endif
</span>
%endfor
## Add tag input field. If element is in form, tag input is a textarea; otherwise element is a input type=text.
%if editable:
%if in_form:
<textarea id='tag-input' class="tag-input" rows='1' cols='${input_size}'></textarea>
%else:
<input id='tag-input' class="tag-input" type='text' size='${input_size}'></input>
%endif
## Add "add tag" button.
<img src='${h.url_for('/static/images/add_icon.png')}' rollover='${h.url_for('/static/images/add_icon_dark.png')}' class="add-tag-button"/>
%endif
</div>
</div>
</%def>
## Render the tags 'tags' as an autocomplete element.
<%def name="render_tagging_element(tagged_item, elt_id, elt_context, use_toggle_link='true', in_form='false', input_size='15', tag_click_fn='default_tag_click_fn', get_toggle_link_text_fn='default_get_toggle_link_text_fn', editable='true')">
<%def name="render_tagging_element(tagged_item=None, elt_context=None, use_toggle_link=True, in_form=False, input_size='15', tag_click_fn='default_tag_click_fn', get_toggle_link_text_fn='default_get_toggle_link_text_fn', editable=True)">
## Useful attributes.
<%
tagged_item_id = str( trans.security.encode_id (tagged_item.id) )
elt_id = "tag-element-" + tagged_item_id
%>
## Build HTML.
${self.render_tagging_element_html(tagged_item, editable, use_toggle_link, input_size, in_form)}
## Build script that augments tags using progressive javascript.
<script type="text/javascript">
//
// Set up autocomplete tagger.
@@ -70,22 +136,32 @@
var options =
{
tags : ${h.to_json_string(tag_names_and_values)},
editable : ${str(editable).lower()},
editable : ${iff( editable, 'true', 'false' )},
get_toggle_link_text_fn: ${get_toggle_link_text_fn},
tag_click_fn: ${tag_click_fn},
<% tagged_item_id = trans.security.encode_id(tagged_item.id) %>
ajax_autocomplete_tag_url: "${h.url_for( controller='tag', action='tag_autocomplete_data', id=tagged_item_id, item_class=tagged_item.__class__.__name__ )}",
ajax_add_tag_url: "${h.url_for( controller='tag', action='add_tag_async', id=tagged_item_id, item_class=tagged_item.__class__.__name__, context=elt_context )}",
ajax_delete_tag_url: "${h.url_for( controller='tag', action='remove_tag_async', id=tagged_item_id, item_class=tagged_item.__class__.__name__, context=elt_context )}",
delete_tag_img: "${h.url_for('/static/images/delete_tag_icon_gray.png')}",
delete_tag_img_rollover: "${h.url_for('/static/images/delete_tag_icon_white.png')}",
add_tag_img: "${h.url_for('/static/images/add_icon.png')}",
add_tag_img_rollover: "${h.url_for('/static/images/add_icon_dark.png')}",
input_size: ${input_size},
in_form: ${in_form},
use_toggle_link: ${use_toggle_link}
use_toggle_link: ${iff( use_toggle_link, 'true', 'false' )},
};
$("#${elt_id}").autocomplete_tagging(options)
$("#${elt_id}").autocomplete_tagging('${elt_id}', options);
</script>
## Use style to hide/display the tag area.
<style>
.tag-area {
display: ${iff( use_toggle_link, "none", "block" )};
}
</style>
<noscript>
<style>
.tag-area {
display: block;
}
</style>
</noscript>
</%def>