details: http://www.bx.psu.edu/hg/galaxy/rev/a5bdbae15600 changeset: 3030:a5bdbae15600 user: jeremy goecks <jeremy.goecks@emory.edu> date: Fri Nov 13 16:34:32 2009 -0500 description: Initial AJAX support for grids. AJAX support is not integrated into grids yet, but there are mako templates for AJAXed grids. AJAXed grids can be used for grids that do not use operations. diffstat: lib/galaxy/web/controllers/page.py | 60 ++- lib/galaxy/web/framework/helpers/grids.py | 5 +- static/scripts/galaxy.base.js | 20 +- templates/grid_base.mako | 4 +- templates/grid_base_async.mako | 665 +++++++++++++++++++++++++++++++++++ templates/grid_body_async.mako | 5 + templates/grid_common_async.mako | 155 ++++++++ templates/tagging_common.mako | 13 - 8 files changed, 905 insertions(+), 22 deletions(-) diffs (1055 lines): diff -r d98c52439f53 -r a5bdbae15600 lib/galaxy/web/controllers/page.py --- a/lib/galaxy/web/controllers/page.py Fri Nov 13 16:32:36 2009 -0500 +++ b/lib/galaxy/web/controllers/page.py Fri Nov 13 16:34:32 2009 -0500 @@ -1,6 +1,7 @@ from galaxy.web.base.controller import * from galaxy.web.framework.helpers import time_ago, grids from galaxy.util.sanitize_html import sanitize_html +from galaxy.util.odict import odict import re @@ -69,21 +70,66 @@ ] def apply_default_filter( self, trans, query, **kwargs ): return query.filter_by( deleted=False, published=True ) - - -class NameColumn( grids.TextColumn ): - def get_value(self, trans, grid, history): - return history.get_display_name() class HistorySelectionGrid( grids.Grid ): + # Custom columns. + class NameColumn( grids.TextColumn ): + def get_value(self, trans, grid, history): + return history.get_display_name() + + 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 + + class SharingColumn( grids.GridColumn ): + def filter( self, db_session, query, column_filter ): + """ Modify query to filter histories by sharing status. """ + if column_filter == "All": + pass + elif column_filter: + if column_filter == "private": + query = query.filter( model.History.users_shared_with == None ) + query = query.filter( model.History.importable == False ) + elif column_filter == "shared": + query = query.filter( model.History.users_shared_with != None ) + elif column_filter == "importable": + query = query.filter( model.History.importable == True ) + return query + def get_accepted_filters( self ): + """ Returns a list of accepted filters for this column. """ + accepted_filter_labels_and_vals = odict() + accepted_filter_labels_and_vals["private"] = "private" + accepted_filter_labels_and_vals["shared"] = "shared" + accepted_filter_labels_and_vals["importable"] = "importable" + 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 + # Grid definition. title = "Saved Histories" + template = "grid_base_async.mako" + async_template = "grid_body_async.mako" model_class = model.History + default_filter = { "deleted" : "False" , "shared" : "All" } default_sort_key = "-update_time" + use_paging = True + num_rows_per_page = 5 columns = [ NameColumn( "Name", key="name", model_class=model.History, filterable="advanced" ), grids.TagsColumn( "Tags", "tags", model.History, model.HistoryTagAssociation, filterable="advanced"), grids.GridColumn( "Last Updated", key="update_time", format=time_ago ), + # Columns that are valid for filtering but are not visible. + DeletedColumn( "Deleted", key="deleted", visible=False, filterable="advanced" ), + SharingColumn( "Shared", key="shared", visible=False, filterable="advanced" ), ] columns.append( grids.MulticolFilterColumn( @@ -91,6 +137,8 @@ cols_to_filter=[ columns[0], columns[1] ], key="free-text-search", visible=False, filterable="standard" ) ) + def apply_default_filter( self, trans, query, **kwargs ): + return query.filter_by( user=trans.user, purged=False ) class PageController( BaseController ): @@ -268,4 +316,4 @@ @web.require_login("select a history from saved histories") def list_histories_for_selection( self, trans, **kwargs ): # Render the list view - return self._history_selection_grid( trans, status=status, message=message, **kwargs ) \ No newline at end of file + return self._history_selection_grid( trans, **kwargs ) \ No newline at end of file diff -r d98c52439f53 -r a5bdbae15600 lib/galaxy/web/framework/helpers/grids.py --- a/lib/galaxy/web/framework/helpers/grids.py Fri Nov 13 16:32:36 2009 -0500 +++ b/lib/galaxy/web/framework/helpers/grids.py Fri Nov 13 16:34:32 2009 -0500 @@ -1,6 +1,7 @@ from galaxy.model import * from galaxy.model.orm import * +from galaxy.web.framework.helpers import iff from galaxy.tags.tag_handler import TagHandler from galaxy.web import url_for from galaxy.util.json import from_json_string, to_json_string @@ -16,7 +17,9 @@ title = "" exposed = True model_class = None + # To use grid's async features, set template="grid_base_async.mako" template = "grid_base.mako" + async_template = "grid_body_async.mako" global_actions = [] columns = [] operations = [] @@ -213,7 +216,7 @@ return url_for( **new_kwargs ) - return trans.fill_template( self.template, + return trans.fill_template( iff( 'async' not in kwargs, self.template, self.async_template), grid=self, query=query, cur_page_num = page_num, diff -r d98c52439f53 -r a5bdbae15600 static/scripts/galaxy.base.js --- a/static/scripts/galaxy.base.js Fri Nov 13 16:32:36 2009 -0500 +++ b/static/scripts/galaxy.base.js Fri Nov 13 16:34:32 2009 -0500 @@ -19,6 +19,12 @@ jQuery( "a[confirm]" ).click( function() { return confirm( jQuery(this).attr( "confirm" ) ) }); + // Make popup menus. + make_popup_menus(); +}); + +function make_popup_menus() +{ jQuery( "div[popupmenu]" ).each( function() { var options = {}; $(this).find( "a" ).each( function() { @@ -40,7 +46,7 @@ $(this).remove(); b.show(); }); -}); +} function ensure_popup_helper() { // And the helper below the popup menus @@ -103,3 +109,15 @@ }; $( button_element ).click( click ); }; + +// 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; +}; diff -r d98c52439f53 -r a5bdbae15600 templates/grid_base.mako --- a/templates/grid_base.mako Fri Nov 13 16:32:36 2009 -0500 +++ b/templates/grid_base.mako Fri Nov 13 16:34:32 2009 -0500 @@ -347,8 +347,9 @@ </td> </tr> %endif + ## Grid operations. + %if grid.operations: <tr> - ## Grid operations. <td></td> <td colspan="100"> For <span class="grid-selected-count"></span> selected ${items_plural}: @@ -359,6 +360,7 @@ %endfor </td> </tr> + %endif </tfoot> </table> </form> diff -r d98c52439f53 -r a5bdbae15600 templates/grid_base_async.mako --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/grid_base_async.mako Fri Nov 13 16:34:32 2009 -0500 @@ -0,0 +1,665 @@ +<%! + from galaxy.web.framework.helpers.grids import TextColumn + from galaxy.model import History, HistoryDatasetAssociation, User, Role, Group + import galaxy.util + def inherit(context): + if context.get('use_panels'): + return '/base_panels.mako' + else: + return '/base.mako' +%> +<%inherit file="${inherit(context)}"/> + +## Render the grid's basic elements. Each of these elements can be subclassed. +%if message: + <p> + <div class="${message_type}message transient-message">${util.restore_text( message )}</div> + <div style="clear: both"></div> + </p> +%endif + +${self.render_grid_header()} +${self.render_grid_table()} + +## Function definitions. + +<%def name="title()">${grid.title}</%def> + +<%def name="javascripts()"> + ${parent.javascripts()} + ${h.js("jquery.autocomplete", "autocomplete_tagging" )} + <script type="text/javascript"> + ## TODO: generalize and move into galaxy.base.js + $(document).ready(function() { + // Initialize grid elements. + init_grid_elements(); + + // Initialize autocomplete for text inputs in search UI. + var t = $("#input-tags-filter"); + if (t.length) + { + + var autocomplete_options = + { selectFirst: false, autoFill: false, highlight: false, mustMatch: false }; + + t.autocomplete("${h.url_for( controller='tag', action='tag_autocomplete_data', item_class='History' )}", autocomplete_options); + } + + var t2 = $("#input-name-filter"); + if (t2.length) + { + var autocomplete_options = + { selectFirst: false, autoFill: false, highlight: false, mustMatch: false }; + + t2.autocomplete("${h.url_for( controller='history', action='name_autocomplete_data' )}", autocomplete_options); + } + }); + ## Can this be moved into base.mako? + %if refresh_frames: + %if 'masthead' in refresh_frames: + ## Refresh masthead == user changes (backward compatibility) + if ( parent.user_changed ) { + %if trans.user: + parent.user_changed( "${trans.user.email}", ${int( app.config.is_admin_user( trans.user ) )} ); + %else: + parent.user_changed( null, false ); + %endif + } + %endif + %if 'history' in refresh_frames: + if ( parent.frames && parent.frames.galaxy_history ) { + parent.frames.galaxy_history.location.href="${h.url_for( controller='root', action='history')}"; + if ( parent.force_right_panel ) { + parent.force_right_panel( 'show' ); + } + } + %endif + %if 'tools' in refresh_frames: + if ( parent.frames && parent.frames.galaxy_tools ) { + parent.frames.galaxy_tools.location.href="${h.url_for( controller='root', action='tool_menu')}"; + if ( parent.force_left_panel ) { + parent.force_left_panel( 'show' ); + } + } + %endif + %endif + + // + // Code to handle grid operations: filtering, sorting, paging, and operations. + // + + // Initialize grid elements. + function init_grid_elements() + { + $(".grid").each( function() { + var grid = this; + var checkboxes = $(this).find("input.grid-row-select-checkbox"); + var update = $(this).find( "span.grid-selected-count" ); + $(checkboxes).each( function() { + $(this).change( function() { + var n = $(checkboxes).filter("[checked]").size(); + update.text( n ); + }); + }) + }); + } + + // Filter values for categorical filters. + var categorical_filters = new Object(); + %for column in grid.columns: + %if column.filterable is not None and not isinstance( column, TextColumn ): + var ${column.key}_filters = + { + %for i, filter in enumerate( column.get_accepted_filters() ): + %if i > 0: + , + %endif + ${filter.label} : ${h.to_json_string( filter.args )} + %endfor + }; + categorical_filters['${column.key}'] = ${column.key}_filters; + %endif + %endfor + + // Initialize URL args with filter arguments. + var url_args = ${h.to_json_string( cur_filter_dict )}; + + // Place "f-" in front of all filter arguments. + var arg; + for (arg in url_args) + { + value = url_args[arg]; + delete url_args[arg]; + url_args["f-" + arg] = value; + } + + // Add sort argument to URL args. + url_args['sort'] = "${encoded_sort_key}"; + + // Add async keyword to URL args. + url_args['async'] = true; + + // Add tag to grid filter. + function add_tag_to_grid_filter(tag_name, tag_value) + { + // Put tag name and value together. + var tag = tag_name + (tag_value != null && tag_value != "" ? ":" + tag_value : ""); + add_filter_condition("tags", tag, true); + } + + // Add a condition to the grid filter; this adds the condition and refreshes the grid. + function add_filter_condition(name, value, append) + { + // Update URL arg with new condition. + if (append) + { + // Update or append value. + var cur_val = url_args["f-" + name]; + var new_val; + if (cur_val == null || cur_val == undefined) + { + new_val = value; + } + else if (typeof(cur_val) == "string") + { + if (cur_val == "All") + new_val = value; + else + { + // Replace string with array. + var values = new Array(); + values[0] = cur_val; + values[1] = value; + new_val = values; + } + } + else { + // Current value is an array. + new_val = cur_val; + new_val[new_val.length] = value; + } + url_args["f-" + name] = new_val; + } + else + { + // Replace value. + url_args["f-" + name] = value; + } + + // Add button that displays filter and provides a button to delete it. + var t = $("<span>" + value + + " <a href='#'><img src='${h.url_for('/static/images/delete_tag_icon_gray.png')}'/></a></span>"); + t.addClass('text-filter-val'); + t.click(function() { + // + // Remove filter condition. + // + + // TODO: remove element. + //var tag_button = $(this).parent(); + $(this).remove(); + + // Remove condition from URL args. + var cur_val = url_args["f-" + name]; + if (cur_val == null || cur_val == undefined) + { + // Unexpected. Throw error? + } + else if (typeof(cur_val) == "string") + { + if (cur_val == "All") + { + // Unexpected. Throw error? + } + else + // Remove condition. + delete url_args["f-" + name]; + } + else { + // Current value is an array. + var conditions = cur_val; + var index; + for (index = 0; index < conditions.length; index++) + if (conditions[index] == value) + { + conditions.splice(index, 1); + break; + } + } + + update_grid(true); + }); + + var container = $('#' + name + "-filtering-criteria"); + container.append(t); + + update_grid(true); + } + + // Set sort condition for grid. + function set_sort_condition(col_key) + { + // Set new sort condition. New sort is col_key if sorting new column; if reversing sort on + // currently sorted column, sort is reversed. + var cur_sort = url_args['sort']; + var new_sort = col_key; + if ( cur_sort.indexOf( col_key ) != -1) + { + // Reverse sort. + if ( cur_sort.substring(0,1) != '-' ) + new_sort = '-' + col_key; + else + { + // Sort reversed by using just col_key. + } + } + + // Remove sort arrows elements. + $('.sort-arrow').remove() + + // Add sort arrow element to new sort column. + var sort_arrow = "↑"; + if (new_sort.substring(0,1) != '-') + sort_arrow = "↓"; + var t = $("<span>" + sort_arrow + "</span>").addClass('sort-arrow'); + var th = $("#" + col_key + '-header'); + th.append(t); + + // Update grid. + url_args['sort'] = new_sort; + update_grid(); + } + + // Set new value for categorical filter. + function set_categorical_filter(this_obj, name, new_value) + { + // Update filter hyperlinks to reflect new filter value. + var category_filter = categorical_filters[name]; + var cur_value = url_args["f-" + name]; + $("." + name + "-filter").each( function() { + var text = $(this).text().trim(); + var filter = category_filter[text]; + var filter_value = filter[name]; + if (filter_value == new_value) + { + // Remove filter link since grid will be using this filter. It is assumed that + // this element has a single child, a hyperlink/anchor with text. + $(this).empty(); + $(this).append("<span style='font-style: italic'>" + text + "</span>"); + } + else if (filter_value == cur_value) + { + // Add hyperlink for this filter since grid will no longer be using this filter. It is assumed that + // this element has a single child, a hyperlink/anchor. + $(this).empty(); + var t = $("<a href='#'>" + text + "</a>"); + t.click(function() { + set_categorical_filter( $(this), name, filter_value ); + }); + $(this).append(t); + } + }); + + // Need to go back to page 1 if not showing all. + var cur_page = url_args['page']; + if (cur_page != null && cur_page != undefined && cur_page != 'all') + url_args['page'] = 1; + + // Update grid. + url_args["f-" + name] = new_value; + update_grid(true); + } + + var num_pages = ${num_pages}; + url_args['page'] = 1; + // Set page to view. + function set_page(new_page) + { + // Update page hyperlink to reflect new page. + $(".page-link").each( function() { + var id = $(this).attr('id'); + var page_num = parseInt( id.split("-")[2] ); // Id has form 'page-link-<page_num> + var cur_page = url_args['page']; + if (page_num == new_page) + { + // Remove link to page since grid will be on this page. It is assumed that + // this element has a single child, a hyperlink/anchor with text. + var text = $(this).children().text(); + $(this).empty(); + $(this).addClass("inactive-link"); + $(this).text(text); + } + else if (page_num == cur_page) + { + // Add hyperlink to this page since grid will no longer be on this page. It is assumed that + // this element has a single child, a hyperlink/anchor. + var text = $(this).text(); + $(this).empty(); + $(this).removeClass("inactive-link"); + var t = $("<a href='#'>" + text + "</a>"); + t.click(function() { + set_page(page_num); + }); + $(this).append(t); + } + }); + + + if (new_page == "all") + { + url_args['page'] = new_page; + $('#page-links-row').hide('slow'); + } + else + { + url_args['page'] = parseInt(new_page); + } + update_grid(); + } + + // Update grid. + function update_grid() + { + $.ajax({ + url: "${h.url_for()}", + data: url_args, + error: function() { alert( "Grid refresh failed" ) }, + success: function(response_text) { + // HACK: use a simple string to separate the two elements in the + // response: (1) table body and (2) number of pages in table. + var parsed_response_text = response_text.split("*****"); + + // Update grid body. + var table_body = parsed_response_text[0]; + $('#grid-table-body').html(table_body); + + // Process grid body. + init_grid_elements(); + make_popup_menus(); + + // Update pages. + var num_pages = parseInt( parsed_response_text[1] ); + + // Rebuild page links. + var page_link_container = $('#page-link-container'); + page_link_container.children().remove(); + if (num_pages > 1) + { + // Show page link row. + $('#page-links-row').show(); + + // First page is the current page. + var t = $("<span>1</span>"); + t.addClass('page-link'); + t.addClass('inactive-link'); + t.attr('id', 'page-link-1'); + page_link_container.append(t); + + // Subsequent pages are navigable. + for (var i = 2; i <= num_pages; i++) + { + var span = $("<span></span>"); + span.addClass('page-link'); + span.attr('id', 'page-link-' + i); + var t = $("<a href='#'>" + i + "</a>"); + var page_num = i + t.click(function() { + set_page(page_num); + }); + span.append(t) + page_link_container.append(span); + } + } + else + { + // Hide page link row. + $('#page-links-row').hide('slow'); + } + } + }); + } + + // Perform a grid operation. TODO: this is not complete. + function do_operation() + { + // Get grid form. + var form = $('#grid-form'); + var operation = $('input[name=operation]:submit').val(); + var item_ids = $('input[name=id]:checked').val(); + + // Update URL args. + url_args['operation'] = operation; + url_args['id'] = item_ids; + + //update_grid(); + + //document.location = ${h.url_for()} + "?" + } + + </script> +</%def> + +<%def name="stylesheets()"> + ${h.css( "base", "autocomplete_tagging" )} + <style> + ## Not generic to all grids -- move to base? + .count-box { + min-width: 1.1em; + padding: 5px; + border-width: 1px; + border-style: solid; + text-align: center; + display: inline-block; + } + .text-filter-val { + border: solid 1px #AAAAAA; + padding: 1px 3px 1px 3px; + margin-right: 5px; + -moz-border-radius: .5em; + -webkit-border-radius: .5em; + font-style: italic; + } + .page-link a, .inactive-link { + padding: 0px 7px 0px 7px; + } + .inactive-link { + font-style: italic; + } + </style> +</%def> + +<%namespace file="./grid_common_async.mako" import="*" /> + +## Print grid header. +<%def name="render_grid_header()"> + <div class="grid-header"> + <h2>${grid.title}</h2> + + %if grid.global_actions: + <ul class="manage-table-actions"> + %for action in grid.global_actions: + <li> + <a class="action-button" href="${h.url_for( **action.url_args )}">${action.label}</a> + </li> + %endfor + </ul> + %endif + + ${render_grid_filters()} + </div> +</%def> + +## Print grid. +<%def name="render_grid_table()"> + <form action="${url()}" method="post" onsubmit="do_operation();return false;"> + <table class="grid"> + <thead id="grid-table-header"> + <tr> + <th></th> + %for column in grid.columns: + %if column.visible: + <% + href = "" + extra = "" + if column.sortable: + if sort_key == column.key: + if sort_order == "asc": + href = url( sort=( "-" + column.key ) ) + extra = "↓" + else: + href = url( sort=( column.key ) ) + extra = "↑" + else: + href = url( sort=column.key ) + %> + <th\ + id="${column.key}-header" + %if column.ncells > 1: + colspan="${column.ncells}" + %endif + > + %if href: + <a href="${href}" onclick="set_sort_condition('${column.key}');return false;">${column.label}</a> + %else: + ${column.label} + %endif + <span class="sort-arrow">${extra}</span> + </th> + %endif + %endfor + <th></th> + </tr> + </thead> + <tbody id="grid-table-body"> + ${render_grid_table_body_contents()} + </tbody> + <tfoot id="grid-table-footer"> + ${render_grid_table_footer_contents()} + </tfoot> + </table> + </form> +</%def> + +<%def name="render_grid_table_body_contents()"> + %if query.count() == 0: + ## No results. + <tr><td></td><td><em>No Items</em></td></tr> + %endif + %for i, item in enumerate( query ): + <tr \ + %if current_item == item: + class="current" \ + %endif + > + ## Item selection column + <td style="width: 1.5em;"> + <input type="checkbox" name="id" value=${trans.security.encode_id( item.id )} class="grid-row-select-checkbox" /> + </td> + ## Data columns + %for column in grid.columns: + %if column.visible: + <% + # Link + link = column.get_link( trans, grid, item ) + if link: + href = url( **link ) + else: + href = None + # Value (coerced to list so we can loop) + value = column.get_value( trans, grid, item ) + if column.ncells == 1: + value = [ value ] + %> + %for cellnum, v in enumerate( value ): + <% + # Handle non-ascii chars. + if isinstance(v, str): + v = unicode(v, 'utf-8') + # Attach popup menu? + if column.attach_popup and cellnum == 0: + extra = '<a id="grid-%d-popup" class="arrow" style="display: none;"><span>▼</span></a>' % i + else: + extra = "" + %> + %if href: + <td><div class="menubutton split" style="float: left;"><a class="label" href="${href}">${v}</a>${extra}</td> + %else: + <td >${v}${extra}</td> + %endif + %endfor + %endif + %endfor + ## Actions column + <td> + <div popupmenu="grid-${i}-popup"> + %for operation in grid.operations: + %if operation.allowed( item ): + <% + target = "" + if operation.target: + target = "target='" + operation.target + "'" + %> + <a class="action-button" ${target} href="${ url( **operation.get_url_args( item ) ) }">${operation.label}</a> + %endif + %endfor + </div> + </td> + </tr> + %endfor +</%def> + +<%def name="render_grid_table_footer_contents()"> + ## Row for navigating among pages. + <% + # Mapping between item class and plural term for item. + items_plural = "items" + if grid.model_class == History: + items_plural = "histories" + elif grid.model_class == HistoryDatasetAssociation: + items_plural = "datasets" + elif grid.model_class == User: + items_plural = "users" + elif grid.model_class == Role: + items_plural = "roles" + elif grid.model_class == Group: + items_plural = "groups" + %> + %if num_pages > 1: + <tr id="page-links-row"> + <td></td> + <td colspan="100"> + <span id='page-link-container'> + ## Page links. + Page: + %for page_index in range(1, num_pages + 1): + %if page_index == cur_page_num: + <span class='page-link inactive-link' id="page-link-${page_index}">${page_index}</span> + %else: + <% args = { 'page' : page_index } %> + <span class='page-link' id="page-link-${page_index}"><a href="${url( args )}" onclick="set_page('${page_index}'); return false;">${page_index}</a></span> + %endif + %endfor + </span> + + ## Show all link. + <% args = { "page" : "all" } %> + <span id='show-all-link'>| <a href="${url( args )}" onclick="set_page('all');return false;">Show all ${items_plural} on one page</a></span> + </td> + </tr> + %endif + ## Grid operations. + %if grid.operations: + <tr> + <td></td> + <td colspan="100"> + For <span class="grid-selected-count"></span> selected ${items_plural}: + %for operation in grid.operations: + %if operation.allow_multiple: + <input type="submit" name="operation" value="${operation.label}" class="action-button"> + %endif + %endfor + </td> + </tr> + %endif +</%def> + diff -r d98c52439f53 -r a5bdbae15600 templates/grid_body_async.mako --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/grid_body_async.mako Fri Nov 13 16:34:32 2009 -0500 @@ -0,0 +1,5 @@ +<%namespace file="./grid_base_async.mako" import="*" /> + +${render_grid_table_body_contents()} +***** +${num_pages} \ No newline at end of file diff -r d98c52439f53 -r a5bdbae15600 templates/grid_common_async.mako --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/grid_common_async.mako Fri Nov 13 16:34:32 2009 -0500 @@ -0,0 +1,155 @@ +<%! from galaxy.web.framework.helpers.grids import TextColumn, GridColumnFilter %> + +## Render an AJAX filter UI for a grid column. Filter is rendered as a table row. +<%def name="render_ajax_grid_column_filter(column)"> + <tr> + <% + column_label = column.label + if column.filterable == "advanced": + column_label = column_label.lower() + %> + <td align="left" style="padding-left: 10px">${column_label}:</td> + <td> + %if isinstance(column, TextColumn): + <form action="${url( dict() )}" id="form-filter-${column.key}" + ## Move this to doc.ready() + ##onsubmit="var text_input=$('#input-${column.key}-filter').val();$('#input-${column.key}-filter').val('');add_filter_condition('${column.key}',text_input,true);return false;" + onsubmit="var text_input=$('#input-${column.key}-filter').val();$('#input-${column.key}-filter').val('');add_filter_condition('${column.key}',text_input,true);return false;" + method="get" > + ## Carry forward filtering criteria with hidden inputs. + %for temp_column in grid.columns: + %if temp_column.key in cur_filter_dict: + <% value = cur_filter_dict[ temp_column.key ] %> + %if value != "All": + <% + if isinstance( temp_column, TextColumn ): + value = h.to_json_string( value ) + %> + <input type="hidden" id="${temp_column.key}" name="f-${temp_column.key}" value='${value}'/> + %endif + %endif + %endfor + + ## Print current filtering criteria and links to delete. + <span id="${column.key}-filtering-criteria"> + %if column.key in cur_filter_dict: + <% column_filter = cur_filter_dict[column.key] %> + %if isinstance( column_filter, basestring ): + %if column_filter != "All": + <span style="font-style: italic">${cur_filter_dict[column.key]}</span> + <% filter_all = GridColumnFilter( "", { column.key : "All" } ) %> + <a href="${url( filter_all.get_url_args() )}"><img src="${h.url_for('/static/images/delete_tag_icon_gray.png')}"/></a> + | + %endif + %elif isinstance( column_filter, list ): + %for i, filter in enumerate( column_filter ): + %if i > 0: + , + %endif + <span style="font-style: italic">${filter}</span> + <% + new_filter = list( column_filter ) + del new_filter[ i ] + new_column_filter = GridColumnFilter( "", { column.key : h.to_json_string( new_filter ) } ) + %> + <a href="${url( new_column_filter.get_url_args() )}"><img src="${h.url_for('/static/images/delete_tag_icon_gray.png')}"/></a> + %endfor + + %endif + %endif + </span> + + ## Print input field for column. + <span><input id="input-${column.key}-filter" name="f-${column.key}" type="text" value="" size="15"/></span> + </form> + %else: + <span id="${column.key}-filtering-criteria"> + %for i, filter in enumerate( column.get_accepted_filters() ): + <% + # HACK: we know that each filter will have only a single argument, so get that single argument. + for key, arg in filter.args.items(): + filter_key = key + filter_arg = arg + %> + %if i > 0: + | + %endif + %if column.key in cur_filter_dict and column.key in filter.args and cur_filter_dict[column.key] == filter.args[column.key]: + <span class="${column.key}-filter">${filter.label}</span> + %else: + <span class="${column.key}-filter"> + <a href="${url( filter.get_url_args() )}" + onclick="set_categorical_filter($(this), '${column.key}','${filter_arg}'); return false;">${filter.label}</a> + </span> + %endif + %endfor + </span> + %endif + </td> + </tr> +</%def> + +## Print grid search/filtering UI. +<%def name="render_grid_filters()"> + ## Standard search. + <div> + <table><tr> + <td> + <table> + %for column in grid.columns: + %if column.filterable == "standard": + ${render_ajax_grid_column_filter(column)} + %endif + %endfor + </table> + </td> + <td> + ## Clear the standard search. + ##| + ##<% filter_all = GridColumnFilter( "", { column.key : "All" } ) %> + ##<a href="${url( filter_all.get_url_args() )}">Clear All</a> + + ## Only show advanced search if there are filterable columns. + <% + show_advanced_search = False + for column in grid.columns: + if column.filterable == "advanced": + show_advanced_search = True + break + endif + %> + %if show_advanced_search: + | <a href="" onclick="javascript:$('#more-search-options').slideToggle('fast');return false;">Advanced Search</a> + %endif + </td> + </tr></table> + </div> + + ## Advanced search. + <div id="more-search-options" style="display: none; padding-top: 5px"> + <table style="border: 1px solid gray;"> + <tr><td style="text-align: left" colspan="100"> + Advanced Search | + <a href=""# onclick="javascript:$('#more-search-options').slideToggle('fast');return false;">Close</a> | + ## Link to clear all filters. + <% + no_filter = GridColumnFilter("Clear All", default_filter_dict) + %> + <a href="${url( no_filter.get_url_args() )}">${no_filter.label}</a> + </td></tr> + %for column in grid.columns: + %if column.filterable == "advanced": + ## Show div if current filter has value that is different from the default filter. + %if column.key in cur_filter_dict and column.key in default_filter_dict and \ + cur_filter_dict[column.key] != default_filter_dict[column.key]: + <script type="text/javascript"> + $('#more-search-options').css("display", "block"); + </script> + %endif + + ${render_ajax_grid_column_filter(column)} + %endif + %endfor + </table> + </div> +</%def> \ No newline at end of file diff -r d98c52439f53 -r a5bdbae15600 templates/tagging_common.mako --- a/templates/tagging_common.mako Fri Nov 13 16:32:36 2009 -0500 +++ b/templates/tagging_common.mako Fri Nov 13 16:34:32 2009 -0500 @@ -23,19 +23,6 @@ else: ## isInstance( tag_name, unicode ): tag_names_and_values[tag_name] = tag_value %> - // - // 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; - }; // // Default function get text to display on the toggle link.