details:
http://www.bx.psu.edu/hg/galaxy/rev/40f8f713cbd8
changeset: 2769:40f8f713cbd8
user: jeremy goecks <jeremy.goecks(a)emory.edu>
date: Thu Sep 24 19:00:44 2009 -0400
description:
Made history grid filterable by tags (and values) and by status.
9 file(s) affected in this change:
lib/galaxy/tags/tag_handler.py
lib/galaxy/web/controllers/history.py
lib/galaxy/web/controllers/tag.py
lib/galaxy/web/framework/helpers/grids.py
static/june_2007_style/autocomplete_tagging.css.tmpl
static/june_2007_style/blue/autocomplete_tagging.css
templates/history/grid.mako
templates/root/history.mako
templates/tagging_common.mako
diffs (837 lines):
diff -r 35dd55a7898e -r 40f8f713cbd8 lib/galaxy/tags/tag_handler.py
--- a/lib/galaxy/tags/tag_handler.py Thu Sep 24 16:52:15 2009 -0400
+++ b/lib/galaxy/tags/tag_handler.py Thu Sep 24 19:00:44 2009 -0400
@@ -21,8 +21,8 @@
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 @@
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 @@
# 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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:
diff -r 35dd55a7898e -r 40f8f713cbd8 lib/galaxy/web/controllers/history.py
--- a/lib/galaxy/web/controllers/history.py Thu Sep 24 16:52:15 2009 -0400
+++ b/lib/galaxy/web/controllers/history.py Thu Sep 24 19:00:44 2009 -0400
@@ -2,8 +2,11 @@
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 @@
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 @@
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 @@
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 ):
diff -r 35dd55a7898e -r 40f8f713cbd8 lib/galaxy/web/controllers/tag.py
--- a/lib/galaxy/web/controllers/tag.py Thu Sep 24 16:52:15 2009 -0400
+++ b/lib/galaxy/web/controllers/tag.py Thu Sep 24 19:00:44 2009 -0400
@@ -34,7 +34,7 @@
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 @@
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 @@
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 @@
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 @@
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 ]
diff -r 35dd55a7898e -r 40f8f713cbd8 lib/galaxy/web/framework/helpers/grids.py
--- a/lib/galaxy/web/framework/helpers/grids.py Thu Sep 24 16:52:15 2009 -0400
+++ b/lib/galaxy/web/framework/helpers/grids.py Thu Sep 24 19:00:44 2009 -0400
@@ -38,19 +38,27 @@
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 @@
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 @@
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 @@
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 @@
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 ):
diff -r 35dd55a7898e -r 40f8f713cbd8 static/june_2007_style/autocomplete_tagging.css.tmpl
--- a/static/june_2007_style/autocomplete_tagging.css.tmpl Thu Sep 24 16:52:15 2009 -0400
+++ b/static/june_2007_style/autocomplete_tagging.css.tmpl Thu Sep 24 19:00:44 2009 -0400
@@ -76,7 +76,7 @@
.toggle-link
{
- font-weight: bold;
+ font-weight: normal;
padding: 0.3em;
margin-bottom: 1em;
width: 100%;
diff -r 35dd55a7898e -r 40f8f713cbd8 static/june_2007_style/blue/autocomplete_tagging.css
--- a/static/june_2007_style/blue/autocomplete_tagging.css Thu Sep 24 16:52:15 2009 -0400
+++ b/static/june_2007_style/blue/autocomplete_tagging.css Thu Sep 24 19:00:44 2009 -0400
@@ -76,7 +76,7 @@
.toggle-link
{
- font-weight: bold;
+ font-weight: normal;
padding: 0.3em;
margin-bottom: 1em;
width: 100%;
diff -r 35dd55a7898e -r 40f8f713cbd8 templates/history/grid.mako
--- a/templates/history/grid.mako Thu Sep 24 16:52:15 2009 -0400
+++ b/templates/history/grid.mako Thu Sep 24 19:00:44 2009 -0400
@@ -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>
+<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: </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> </span>
%endif
- <span class="filter"><a href="${url(
filter.get_url_args() )}">${filter.label}</a></span>
%endfor
- </div>
-%endif
-
+
+ ## 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>
diff -r 35dd55a7898e -r 40f8f713cbd8 templates/root/history.mako
--- a/templates/root/history.mako Thu Sep 24 16:52:15 2009 -0400
+++ b/templates/root/history.mako Thu Sep 24 19:00:44 2009 -0400
@@ -242,6 +242,25 @@
}
});
};
+
+ //
+ // 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 @@
%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:
diff -r 35dd55a7898e -r 40f8f713cbd8 templates/tagging_common.mako
--- a/templates/tagging_common.mako Thu Sep 24 16:52:15 2009 -0400
+++ b/templates/tagging_common.mako Thu Sep 24 19:00:44 2009 -0400
@@ -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__ )}",