Made history grid filterable by tags (and values) and by status.

This commit is contained in:
Jeremy Goecks
2009-09-24 19:00:44 -04:00
parent 947491b270
commit 946617a106
9 changed files with 327 additions and 124 deletions
+40 -36
View File
@@ -21,8 +21,8 @@ class TagHandler( object ):
def get_tag_assoc_class(self, entity_class):
return self.tag_assoc_classes[entity_class]
# Remove a tag from an item.
def remove_item_tag(self, item, tag_name):
"""Remove a tag from an item."""
# Get item tag association.
item_tag_assoc = self._get_item_tag_assoc(item, tag_name)
@@ -35,8 +35,8 @@ class TagHandler( object ):
return False
# Delete tags from an item.
def delete_item_tags(self, item):
"""Delete tags from an item."""
# Delete item-tag associations.
for tag in item.tags:
tag.delete()
@@ -44,8 +44,8 @@ class TagHandler( object ):
# Delete tags from item.
del item.tags[:]
# Returns true if item is has a given tag.
def item_has_tag(self, item, tag):
"""Returns true if item is has a given tag."""
# Get tag name.
if isinstance(tag, basestring):
tag_name = tag
@@ -59,22 +59,25 @@ class TagHandler( object ):
return False
# Apply tags to an item.
def apply_item_tags(self, db_session, item, tags_str):
"""Apply tags to an item."""
# Parse tags.
parsed_tags = self._parse_tags(tags_str)
parsed_tags = self.parse_tags(tags_str)
# Apply each tag.
for name, value in parsed_tags.items():
# Use lowercase name for searching/creating tag.
lc_name = name.lower()
# Get or create item-tag association.
item_tag_assoc = self._get_item_tag_assoc(item, name)
item_tag_assoc = self._get_item_tag_assoc(item, lc_name)
if not item_tag_assoc:
#
# Create item-tag association.
#
# Create tag; if None, skip the tag (and log error).
tag = self._get_or_create_tag(db_session, name)
tag = self._get_or_create_tag(db_session, lc_name)
if not tag:
# Log error?
continue
@@ -88,16 +91,15 @@ class TagHandler( object ):
item_tag_assoc.tag = tag
# Apply attributes to item-tag association. Strip whitespace from user name and tag.
lc_value = None
if value:
trimmed_value = value.strip()
else:
trimmed_value = value
item_tag_assoc.user_tname = name.strip()
item_tag_assoc.user_value = trimmed_value
item_tag_assoc.value = self._scrub_tag_value(value)
lc_value = value.lower()
item_tag_assoc.user_tname = name
item_tag_assoc.user_value = value
item_tag_assoc.value = lc_value
# Build a string from an item's tags.
def get_tags_str(self, tags):
"""Build a string from an item's tags."""
# Return empty string if there are no tags.
if not tags:
return ""
@@ -111,16 +113,18 @@ class TagHandler( object ):
tags_str_list.append(tag_str)
return ", ".join(tags_str_list)
# Get a Tag object from a tag id.
def get_tag_by_id(self, db_session, tag_id):
"""Get a Tag object from a tag id."""
return db_session.query(Tag).filter(Tag.id==tag_id).first()
# Get a Tag object from a tag name (string).
def get_tag_by_name(self, db_session, tag_name):
return db_session.query(Tag).filter(Tag.name==tag_name).first()
"""Get a Tag object from a tag name (string)."""
if tag_name:
return db_session.query( Tag ).filter( Tag.name==tag_name.lower() ).first()
return None
# Create a Tag object from a tag string.
def _create_tag(self, db_session, tag_str):
"""Create a Tag object from a tag string."""
tag_hierarchy = tag_str.split(self.__class__.hierarchy_separator)
tag_prefix = ""
parent_tag = None
@@ -139,8 +143,8 @@ class TagHandler( object ):
tag_prefix = tag.name + self.__class__.hierarchy_separator
return tag
# Get or create a Tag object from a tag string.
def _get_or_create_tag(self, db_session, tag_str):
"""Get or create a Tag object from a tag string."""
# Scrub tag; if tag is None after being scrubbed, return None.
scrubbed_tag_str = self._scrub_tag_name(tag_str)
if not scrubbed_tag_str:
@@ -155,18 +159,18 @@ class TagHandler( object ):
return tag
# Return ItemTagAssociation object for an item and a tag string; returns None if there is
# no such tag.
def _get_item_tag_assoc(self, item, tag_name):
"""Return ItemTagAssociation object for an item and a tag string; returns None if there is
no such tag."""
scrubbed_tag_name = self._scrub_tag_name(tag_name)
for item_tag_assoc in item.tags:
if item_tag_assoc.tag.name == scrubbed_tag_name:
return item_tag_assoc
return None
# Returns a list of raw (tag-name, value) pairs derived from a string; method does not scrub tags.
# Return value is a dictionary where tag-names are keys.
def _parse_tags(self, tag_str):
def parse_tags(self, tag_str):
"""Returns a list of raw (tag-name, value) pairs derived from a string; method scrubs tag names and values as well.
Return value is a dictionary where tag-names are keys."""
# Gracefully handle None.
if not tag_str:
return dict()
@@ -179,11 +183,13 @@ class TagHandler( object ):
name_value_pairs = dict()
for raw_tag in raw_tags:
nv_pair = self._get_name_value_pair(raw_tag)
name_value_pairs[nv_pair[0]] = nv_pair[1]
scrubbed_name = self._scrub_tag_name( nv_pair[0] )
scrubbed_value = self._scrub_tag_value( nv_pair[1] )
name_value_pairs[scrubbed_name] = scrubbed_value
return name_value_pairs
# Scrub a tag value.
def _scrub_tag_value(self, value):
"""Scrub a tag value."""
# Gracefully handle None:
if not value:
return None
@@ -192,11 +198,10 @@ class TagHandler( object ):
reg_exp = re.compile('\s')
scrubbed_value = re.sub(reg_exp, "", value)
# Lowercase and return.
return scrubbed_value.lower()
return scrubbed_value
# Scrub a tag name.
def _scrub_tag_name(self, name):
"""Scrub a tag name."""
# Gracefully handle None:
if not name:
return None
@@ -213,21 +218,20 @@ class TagHandler( object ):
if len(scrubbed_name) < 3 or len(scrubbed_name) > 255:
return None
# Lowercase and return.
return scrubbed_name.lower()
return scrubbed_name
# Scrub a tag name list.
def _scrub_tag_name_list(self, tag_name_list):
"""Scrub a tag name list."""
scrubbed_tag_list = list()
for tag in tag_name_list:
scrubbed_tag_list.append(self._scrub_tag_name(tag))
scrubbed_tag_list.append( self._scrub_tag_name(tag) )
return scrubbed_tag_list
# Get name, value pair from a tag string.
def _get_name_value_pair(self, tag_str):
"""Get name, value pair from a tag string."""
# Use regular expression to parse name, value.
reg_exp = re.compile("[" + self.__class__.key_value_separators + "]")
name_value_pair = reg_exp.split(tag_str)
reg_exp = re.compile( "[" + self.__class__.key_value_separators + "]" )
name_value_pair = reg_exp.split( tag_str )
# Add empty slot if tag does not have value.
if len(name_value_pair) < 2:
+51 -9
View File
@@ -2,8 +2,11 @@ from galaxy.web.base.controller import *
from galaxy.web.framework.helpers import time_ago, iff, grids
from galaxy import util
from galaxy.model.mapping import desc
from galaxy.model import History
from galaxy.model.orm import *
from galaxy.util.json import *
from galaxy.tags.tag_handler import TagHandler
from sqlalchemy.sql.expression import ClauseElement
import webhelpers, logging, operator
from datetime import datetime
from cgi import escape
@@ -39,16 +42,55 @@ class HistoryListGrid( grids.Grid ):
return dict( operation="sharing" )
return None
class TagsColumn( grids.GridColumn ):
def __init__(self, col_name):
grids.GridColumn.__init__(self, col_name)
def __init__(self, col_name, key, filterable):
grids.GridColumn.__init__(self, col_name, key=key, filterable=filterable)
# Tags cannot be sorted.
self.sortable = False
self.tag_elt_id_gen = 0
def get_value( self, trans, grid, history ):
self.tag_elt_id_gen += 1
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=history, elt_id = elt_id, in_form="true", input_size="20" )
return div_elt + trans.fill_template( "/tagging_common.mako", trans=trans, tagged_item=history,
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 include only histories with tags in column_filter. """
if column_filter == "All":
pass
elif column_filter:
# Parse filter to extract multiple tags.
tag_handler = TagHandler()
raw_tags = tag_handler.parse_tags( column_filter.encode("utf-8") )
for name, value in raw_tags.items():
tag = tag_handler.get_tag_by_name( db_session, name )
if tag:
query = query.filter( History.tags.any( tag_id=tag.id ) )
if value:
query = query.filter( History.tags.any( value=value.lower() ) )
else:
# Tag doesn't exist; unclear what to do here, but the literal thing to do is add the criterion, which
# will then yield a query that returns no results.
query = query.filter( History.tags.any( user_tname=name ) )
return query
def get_accepted_filters( self ):
""" Returns a list of accepted filters for this column. """
accepted_filter_labels_and_vals = { "All": "All" }
accepted_filters = []
for label, val in accepted_filter_labels_and_vals.items():
args = { self.key: val }
accepted_filters.append( grids.GridColumnFilter( label, args) )
return accepted_filters
class DeletedColumn( grids.GridColumn ):
def get_accepted_filters( self ):
""" Returns a list of accepted filters for this column. """
accepted_filter_labels_and_vals = { "Active" : "False", "Deleted" : "True", "All": "All" }
accepted_filters = []
for label, val in accepted_filter_labels_and_vals.items():
args = { self.key: val }
accepted_filters.append( grids.GridColumnFilter( label, args) )
return accepted_filters
# Grid definition
title = "Stored histories"
@@ -60,12 +102,12 @@ class HistoryListGrid( grids.Grid ):
link=( lambda item: iff( item.deleted, None, dict( operation="switch", id=item.id ) ) ),
attach_popup=True ),
DatasetsByStateColumn( "Datasets (by state)", ncells=4 ),
TagsColumn( "Tags"),
TagsColumn( "Tags", key="tags", filterable=True),
StatusColumn( "Status", attach_popup=False ),
grids.GridColumn( "Created", key="create_time", format=time_ago ),
grids.GridColumn( "Last Updated", key="update_time", format=time_ago ),
# Valid for filtering but invisible
grids.GridColumn( "Deleted", key="deleted", visible=False )
DeletedColumn( "Status", key="deleted", visible=False, filterable=True )
]
operations = [
grids.GridOperation( "Switch", allow_multiple=False, condition=( lambda item: not item.deleted ) ),
@@ -80,9 +122,9 @@ class HistoryListGrid( grids.Grid ):
standard_filters = [
grids.GridColumnFilter( "Active", args=dict( deleted=False ) ),
grids.GridColumnFilter( "Deleted", args=dict( deleted=True ) ),
grids.GridColumnFilter( "All", args=dict( deleted='All' ) )
grids.GridColumnFilter( "All", args=dict( deleted='All' ) ),
]
default_filter = dict( deleted=False )
default_filter = dict( deleted="False", tags="All" )
def get_current_item( self, trans ):
return trans.get_history()
def apply_default_filter( self, trans, query, **kwargs ):
+41 -24
View File
@@ -34,7 +34,7 @@ class TagsController ( BaseController ):
self._do_security_check(trans, item)
self.tag_handler.apply_item_tags( trans.sa_session, item, unicode(new_tag).encode('utf-8') )
self.tag_handler.apply_item_tags( trans.sa_session, item, new_tag.encode('utf-8') )
trans.sa_session.flush()
@web.expose
@@ -45,7 +45,7 @@ class TagsController ( BaseController ):
self._do_security_check(trans, item)
self.tag_handler.remove_item_tag( item, unicode(tag_name).encode('utf-8') )
self.tag_handler.remove_item_tag( item, tag_name.encode('utf-8') )
#print tag_name
#print unicode(tag_name)
trans.sa_session.flush()
@@ -60,41 +60,53 @@ class TagsController ( BaseController ):
self._do_security_check(trans, item)
tag_handler.delete_item_tags(item)
self.tag_handler.apply_item_tags( trans.sa_session, item, unicode(new_tags).encode('utf-8') )
self.tag_handler.apply_item_tags( trans.sa_session, item, new_tags.encode('utf-8') )
trans.sa_session.flush()
@web.expose
@web.require_login( "get autocomplete data for an item's tags" )
def tag_autocomplete_data(self, trans, id=None, item_class=None, q=None, limit=None, timestamp=None):
def tag_autocomplete_data( self, trans, q=None, limit=None, timestamp=None, id=None, item_class=None ):
""" Get autocomplete data for an item's tags. """
#
# Get item, do security check, and get autocomplete data.
#
item = self._get_item(trans, item_class, trans.security.decode_id(id))
item = None
if id is not None:
item = self._get_item(trans, item_class, trans.security.decode_id(id))
self._do_security_check(trans, item)
# Get item class. TODO: we should have a mapper that goes from class_name to class object.
if item_class == 'History':
item_class = History
elif item_class == 'HistoryDatasetAssociation':
item_class = HistoryDatasetAssociation
self._do_security_check(trans, item)
q = unicode(q).encode('utf-8')
q = q.encode('utf-8')
if q.find(":") == -1:
return self._get_tag_autocomplete_names(trans, item, q, limit, timestamp)
return self._get_tag_autocomplete_names(trans, q, limit, timestamp, item, item_class)
else:
return self._get_tag_autocomplete_values(trans, item, q, limit, timestamp)
return self._get_tag_autocomplete_values(trans, q, limit, timestamp, item, item_class)
def _get_tag_autocomplete_names(self, trans, item, q, limit, timestamp):
def _get_tag_autocomplete_names( self, trans, q, limit, timestamp, item=None, item_class=None ):
"""Returns autocomplete data for tag names ordered from most frequently used to
least frequently used."""
#
# Get user's item tags and usage counts.
#
# Get item-tag association class.
item_tag_assoc_class = self.tag_handler.get_tag_assoc_class(item.__class__)
# Get item's class object and item-tag association class.
if item is None and item_class is None:
raise RuntimeError("Both item and item_class cannot be None")
elif item is not None:
item_class = item.__class__
item_tag_assoc_class = self.tag_handler.get_tag_assoc_class(item_class)
# Build select statement.
cols_to_select = [ item_tag_assoc_class.table.c.tag_id, func.count('*') ]
from_obj = item_tag_assoc_class.table.join(item.table).join(Tag)
where_clause = and_(self._get_column_for_filtering_item_by_user_id(item.__class__)==trans.get_user().id,
from_obj = item_tag_assoc_class.table.join(item_class.table).join(Tag)
where_clause = and_(self._get_column_for_filtering_item_by_user_id(item_class)==trans.get_user().id,
Tag.table.c.name.like(q + "%"))
order_by = [ func.count("*").desc() ]
group_by = item_tag_assoc_class.table.c.tag_id
@@ -109,18 +121,18 @@ class TagsController ( BaseController ):
for row in result_set:
tag = self.tag_handler.get_tag_by_id(trans.sa_session, row[0])
# Exclude tags that are already applied to the history.
if self.tag_handler.item_has_tag(item, tag):
# Exclude tags that are already applied to the item.
if ( item is not None ) and ( self.tag_handler.item_has_tag(item, tag) ):
continue
# Add tag to autocomplete data. Use the most frequent name that user
# has employed for the tag.
tag_names = self._get_usernames_for_tag(trans.sa_session, trans.get_user(),
tag, item.__class__, item_tag_assoc_class)
tag, item_class, item_tag_assoc_class)
ac_data += tag_names[0] + "|" + tag_names[0] + "\n"
return ac_data
def _get_tag_autocomplete_values(self, trans, item, q, limit, timestamp):
def _get_tag_autocomplete_values(self, trans, q, limit, timestamp, item=None, item_class=None):
"""Returns autocomplete data for tag values ordered from most frequently used to
least frequently used."""
@@ -132,13 +144,18 @@ class TagsController ( BaseController ):
if tag is None:
return ""
# Get item-tag association class.
item_tag_assoc_class = self.tag_handler.get_tag_assoc_class(item.__class__)
# Get item's class object and item-tag association class.
if item is None and item_class is None:
raise RuntimeError("Both item and item_class cannot be None")
elif item is not None:
item_class = item.__class__
item_tag_assoc_class = self.tag_handler.get_tag_assoc_class(item_class)
# Build select statement.
cols_to_select = [ item_tag_assoc_class.table.c.value, func.count('*') ]
from_obj = item_tag_assoc_class.table.join(item.table).join(Tag)
where_clause = and_(self._get_column_for_filtering_item_by_user_id(item.__class__)==trans.get_user().id,
from_obj = item_tag_assoc_class.table.join(item_class.table).join(Tag)
where_clause = and_(self._get_column_for_filtering_item_by_user_id(item_class)==trans.get_user().id,
Tag.table.c.id==tag.id,
item_tag_assoc_class.table.c.value.like(tag_value + "%"))
order_by = [ func.count("*").desc(), item_tag_assoc_class.table.c.value ]
+32 -19
View File
@@ -38,19 +38,27 @@ class Grid( object ):
query = self.apply_default_filter( trans, query, **kwargs )
# Maintain sort state in generated urls
extra_url_args = {}
# Process filtering arguments
filter_args = {}
if self.default_filter:
filter_args.update( self.default_filter )
# Process filtering arguments to (a) build a query that actuates the filter and (b) builds a
# dictionary that denotes the current filter.
cur_filter_dict = {}
for column in self.columns:
if column.key:
# Look for filter criterion in kwargs; if not found, look in default filter.
column_filter = None
if "f-" + column.key in kwargs:
column_filter = kwargs.get( "f-" + column.key )
query = column.filter( query, column_filter, filter_args )
# Carry filter along to newly generated urls
extra_url_args[ "f-" + column.key ] = column_filter
if filter_args:
query = query.filter_by( **filter_args )
elif ( self.default_filter ) and ( column.key in self.default_filter ):
column_filter = self.default_filter.get( column.key )
# If column filter found, apply it.
if column_filter is not None:
# Update query.
query = column.filter( trans.sa_session, query, column_filter )
# Upate current filter dict.
cur_filter_dict[ column.key ] = column_filter
# Carry filter along to newly generated urls.
extra_url_args[ "f-" + column.key ] = column_filter.encode("utf-8")
# Process sort arguments
sort_key = sort_order = None
if 'sort' in kwargs:
@@ -92,6 +100,7 @@ class Grid( object ):
return trans.fill_template( self.template,
grid=self,
query=query,
cur_filter_dict=cur_filter_dict,
sort_key=sort_key,
encoded_sort_key=encoded_sort_key,
sort_order=sort_order,
@@ -125,7 +134,7 @@ class Grid( object ):
return query
class GridColumn( object ):
def __init__( self, label, key=None, method=None, format=None, link=None, attach_popup=False, visible=True, ncells=1 ):
def __init__( self, label, key=None, method=None, format=None, link=None, attach_popup=False, visible=True, ncells=1, filterable=False ):
self.label = label
self.key = key
self.method = method
@@ -134,6 +143,7 @@ class GridColumn( object ):
self.attach_popup = attach_popup
self.visible = visible
self.ncells = ncells
self.filterable = filterable
# Currently can only sort of columns that have a database
# representation, not purely derived.
if self.key:
@@ -154,20 +164,23 @@ class GridColumn( object ):
if self.link and self.link( item ):
return self.link( item )
return None
def filter( self, query, column_filter, filter_args ):
"""
Must modify filter_args for carrying forward, and return query
(possibly filtered).
"""
def filter( self, db_session, query, column_filter ):
""" Modify query to reflect the column filter. """
if column_filter == "All":
pass
if column_filter == "True":
filter_args[self.key] = True
query = query.filter_by( **{ self.key: True } )
elif column_filter == "False":
filter_args[self.key] = False
query = query.filter_by( **{ self.key: False } )
elif column_filter == "All":
del filter_args[self.key]
return query
def get_accepted_filters( self ):
""" Returns a list of accepted filters for this column. """
accepted_filters_vals = [ "False", "True", "All" ]
accepted_filters = []
for val in accepted_filters_vals:
args = { self.key: val }
accepted_filters.append( GridColumnFilter( val, args) )
return accepted_filters
class GridOperation( object ):
def __init__( self, label, key=None, condition=None, allow_multiple=True, target=None, url_args=None ):
@@ -76,7 +76,7 @@
.toggle-link
{
font-weight: bold;
font-weight: normal;
padding: 0.3em;
margin-bottom: 1em;
width: 100%;
@@ -76,7 +76,7 @@
.toggle-link
{
font-weight: bold;
font-weight: normal;
padding: 0.3em;
margin-bottom: 1em;
width: 100%;
+132 -12
View File
@@ -1,3 +1,5 @@
<%! from galaxy.web.framework.helpers.grids import GridColumnFilter %>
<%inherit file="/base.mako"/>
<%def name="title()">${grid.title}</%def>
@@ -25,6 +27,74 @@
});
})
});
// Set up autocomplete for tag filter input.
var t = $("#input-tag-filter");
t.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 < 3)
return false;
//
// New tag OK.
//
}
});
// 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 autocomplete_options =
{ selectFirst: false, formatItem : format_item_func, autoFill: false, highlight: false, mustMatch: true };
t.autocomplete("${h.url_for( controller='tag', action='tag_autocomplete_data', item_class='History' )}", autocomplete_options);
//t.addClass("tag-input");
return t;
});
## Can this be moved into base.mako?
%if refresh_frames:
@@ -55,6 +125,25 @@
}
%endif
%endif
//
// Add a tag to the current grid filter; this adds the tag to the filter and then issues a request to refresh the grid.
//
function add_tag_to_grid_filter(tag_name, tag_value)
{
// Use tag as a filter: replace TAGNAME with tag_name and issue query.
<%
url_args = {}
if "tags" in cur_filter_dict and cur_filter_dict["tags"] != "All":
url_args["f-tags"] = cur_filter_dict["tags"].encode("utf-8") + ", TAGNAME"
else:
url_args["f-tags"] = "TAGNAME"
%>
var url_base = "${url( url_args )}";
var url = url_base.replace("TAGNAME", tag_name);
self.location = url;
}
</script>
</%def>
@@ -73,19 +162,50 @@
</style>
</%def>
%if grid.standard_filters:
<div class="grid-header">
<h2>${grid.title}</h2>
<span class="title">Filter:</span>
%for i, filter in enumerate( grid.standard_filters ):
%if i > 0:
<span>|</span>
%endif
<span class="filter"><a href="${url( filter.get_url_args() )}">${filter.label}</a></span>
%endfor
</div>
%endif
<div class="grid-header">
<h2>${grid.title}</h2>
## Print grid filter.
<form name="history_actions" action="javascript:add_tag_to_grid_filter($('#input-tag-filter').attr('value'))" method="get" >
<strong>Filter:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</strong>
%for column in grid.columns:
%if column.filterable:
<span> by ${column.label.lower()}:</span>
## For now, include special case to handle tags.
%if column.key == "tags":
%if cur_filter_dict[column.key] != "All":
<span class="filter" "style='font-style: italic'">
${cur_filter_dict[column.key]}
</span>
<span>|</span>
%endif
<input id="input-tag-filter" name="f-tags" type="text" value="" size="15"/>
<span>|</span>
%endif
## Handle other columns.
%for i, filter in enumerate( column.get_accepted_filters() ):
%if i > 0:
<span>|</span>
%endif
%if cur_filter_dict[column.key] == filter.args[column.key]:
<span class="filter" "style='font-style: italic'">${filter.label}</span>
%else:
<span class="filter"><a href="${url( filter.get_url_args() )}">${filter.label}</a></span>
%endif
%endfor
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>
%endif
%endfor
## Link to clear all filters.
<%
args = { "deleted" : "False", "tags" : "All" }
no_filter = GridColumnFilter("Clear", args)
%>
<span><a href="${url( no_filter.get_url_args() )}">${no_filter.label}</a></span>
</form>
</div>
<form name="history_actions" action="${url()}" method="post" >
<table class="grid">
<thead>
+20 -1
View File
@@ -242,6 +242,25 @@ var updater_callback = function ( tracked_datasets ) {
}
});
};
//
// 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>
<style>
@@ -289,7 +308,7 @@ var updater_callback = function ( tracked_datasets ) {
%if trans.get_user() is not None:
<div id='history-tag-area' class="tag-element"></div>
${render_tagging_element(history, "history-tag-area")}
${render_tagging_element(history, "history-tag-area", get_toggle_link_text_fn='get_toggle_link_text')}
%endif
%if not datasets:
+9 -21
View File
@@ -1,12 +1,11 @@
## 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, elt_id=elt_id, in_form=in_form, input_size=input_size)}
${render_tagging_element(tagged_item, elt_id=elt_id, in_form=in_form, input_size=input_size, tag_click_fn=tag_click_fn)}
%endif
## Render the tags 'tags' as an autocomplete element.
<%def name="render_tagging_element(tagged_item, elt_id, use_toggle_link='true', in_form='false', input_size='15')">
<%def name="render_tagging_element(tagged_item, elt_id, 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')">
<script type="text/javascript">
//
// Set up autocomplete tagger.
//
@@ -39,9 +38,9 @@
};
//
// Function get text to display on the toggle link.
// Default function get text to display on the toggle link.
//
var get_toggle_link_text = function(tags)
var default_get_toggle_link_text_fn = function(tags)
{
var text = "";
var num_tags = array_length(tags);
@@ -73,30 +72,19 @@
else
{
// No tags.
text = "Add tags to history";
text = "Add tags";
}
return text;
};
//
// Function to handle a tag click.
//
var tag_click_fn = function(tag_name, tag_value)
{
/*
alert(tag_name);
// Do URL request to get histories tag.
self.location = "http://www.yahoo.com";
*/
};
// Default function to handle a tag click.
var default_tag_click_fn = function(tag_name, tag_value) {};
var options =
{
tags : ${h.to_json_string(tag_names_and_values)},
get_toggle_link_text_fn: get_toggle_link_text,
tag_click_fn: tag_click_fn,
##tag_click_fn: function(name, value) { /* Do nothing. */ },
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__ )}",