details: http://www.bx.psu.edu/hg/galaxy/rev/5761948422a8 changeset: 3121:5761948422a8 user: jeremy goecks <jeremy.goecks@emory.edu> date: Tue Nov 24 14:53:19 2009 -0500 description: Added AJAX functionality to main grid framework. Use the use_async flag to turn on AJAXing in a grid; see the history grid for an example of a grid that uses AJAX. Small changes that are needed to fully employ AJAX for a grid are: (a) using the async_compatible flag to indicate which operations can be AJAXed and which cannot; and (b) ensuring that controller code for grid handles operation names in a case-insensitive manner. Also, there are bug fixes for inserting a history link into a page. diffstat: lib/galaxy/web/controllers/history.py | 7 +- lib/galaxy/web/controllers/page.py | 12 +- lib/galaxy/web/framework/helpers/grids.py | 16 +- templates/grid_base.mako | 1162 ++++++++++++++++++++++++--------- templates/grid_base_async.mako | 709 +-------------------- templates/grid_body_async.mako | 5 - templates/grid_common.mako | 63 +- templates/grid_common_async.mako | 155 ---- templates/history/grid.mako | 2 +- templates/page/select_histories_grid.mako | 11 +- 10 files changed, 930 insertions(+), 1212 deletions(-) diffs (2379 lines): diff -r 07c1b4bc14af -r 5761948422a8 lib/galaxy/web/controllers/history.py --- a/lib/galaxy/web/controllers/history.py Mon Nov 23 17:20:01 2009 -0500 +++ b/lib/galaxy/web/controllers/history.py Tue Nov 24 14:53:19 2009 -0500 @@ -87,11 +87,11 @@ # Grid definition title = "Saved Histories" model_class = model.History - template='/grid_base.mako' + template='/history/grid.mako' default_sort_key = "-create_time" columns = [ NameColumn( "Name", key="name", model_class=model.History, - link=( lambda history: iff( history.deleted, None, dict( operation="switch", id=history.id ) ) ), + link=( lambda history: iff( history.deleted, None, dict( operation="Switch", id=history.id ) ) ), attach_popup=True, filterable="advanced" ), DatasetsByStateColumn( "Datasets (by state)", ncells=4 ), grids.TagsColumn( "Tags", "tags", model.History, model.HistoryTagAssociation, filterable="advanced"), @@ -110,7 +110,7 @@ ) operations = [ - grids.GridOperation( "Switch", allow_multiple=False, condition=( lambda item: not item.deleted ), async_compatible=True ), + grids.GridOperation( "Switch", allow_multiple=False, condition=( lambda item: not item.deleted ), async_compatible=False ), grids.GridOperation( "Share", condition=( lambda item: not item.deleted ), async_compatible=False ), grids.GridOperation( "Unshare", condition=( lambda item: not item.deleted ), async_compatible=False ), grids.GridOperation( "Rename", condition=( lambda item: not item.deleted ), async_compatible=False ), @@ -127,6 +127,7 @@ default_filter = dict( name="All", deleted="False", tags="All", shared="All" ) num_rows_per_page = 50 preserve_state = False + use_async = True use_paging = True def get_current_item( self, trans ): return trans.get_history() diff -r 07c1b4bc14af -r 5761948422a8 lib/galaxy/web/controllers/page.py --- a/lib/galaxy/web/controllers/page.py Mon Nov 23 17:20:01 2009 -0500 +++ b/lib/galaxy/web/controllers/page.py Tue Nov 24 14:53:19 2009 -0500 @@ -116,11 +116,11 @@ # Grid definition. title = "Saved Histories" - template = "/page/select_histories_grid.mako" - async_template = "grid_body_async.mako" + template = "/page/select_histories_grid.mako" model_class = model.History default_filter = { "deleted" : "False" , "shared" : "All" } default_sort_key = "-update_time" + use_async = True use_paging = True num_rows_per_page = 10 columns = [ @@ -152,15 +152,15 @@ # Handle operation if 'operation' in kwargs and 'id' in kwargs: session = trans.sa_session - operation = kwargs['operation'] + operation = kwargs['operation'].lower() ids = util.listify( kwargs['id'] ) for id in ids: item = session.query( model.Page ).get( trans.security.decode_id( id ) ) - if operation == "Delete": + if operation == "delete": item.deleted = True - elif operation == "Publish": + elif operation == "publish": item.published = True - elif operation == "Unpublish": + elif operation == "unpublish": item.published = False session.flush() # Build grid diff -r 07c1b4bc14af -r 5761948422a8 lib/galaxy/web/framework/helpers/grids.py --- a/lib/galaxy/web/framework/helpers/grids.py Mon Nov 23 17:20:01 2009 -0500 +++ b/lib/galaxy/web/framework/helpers/grids.py Tue Nov 24 14:53:19 2009 -0500 @@ -17,9 +17,11 @@ 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" + async_template = "grid_base_async.mako" + + use_async = False + global_actions = [] columns = [] operations = [] @@ -174,6 +176,7 @@ if page_num == 0: # Show all rows in page. total_num_rows = query.count() + page_num = 1 num_pages = 1 else: # Show a limited number of rows. Before modifying query, get the total number of rows that query @@ -218,8 +221,8 @@ new_kwargs[ 'id' ] = trans.security.encode_id( id ) return url_for( **new_kwargs ) - - return trans.fill_template( iff( 'async' not in kwargs, self.template, self.async_template), + async_request = ( ( self.use_async ) and ( 'async' in kwargs ) and ( kwargs['async'] in [ 'True', 'true'] ) ) + return trans.fill_template( iff( async_request, self.async_template, self.template), grid=self, query=query, cur_page_num = page_num, @@ -233,7 +236,10 @@ ids = kwargs.get( 'id', [] ), url = url, message_type = status, - message = message ) + message = message, + # Pass back kwargs so that grid template can set and use args without grid explicitly having to pass them. + kwargs=kwargs + ) def get_ids( self, **kwargs ): id = [] if 'id' in kwargs: diff -r 07c1b4bc14af -r 5761948422a8 templates/grid_base.mako --- a/templates/grid_base.mako Mon Nov 23 17:20:01 2009 -0500 +++ b/templates/grid_base.mako Tue Nov 24 14:53:19 2009 -0500 @@ -1,4 +1,5 @@ <%! + from galaxy.web.framework.helpers.grids import TextColumn from galaxy.model import History, HistoryDatasetAssociation, User, Role, Group import galaxy.util def inherit(context): @@ -10,208 +11,727 @@ <%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 +<table> + <tr> + <td width="75%">${self.render_grid_header()}</td> + <td></td> + <td width="25%" id="grid-message" valign="top">${self.render_grid_message()}</td> + </tr> +</table> -${self.grid_header()} -${self.grid_table()} +${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. - $(".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 ); - }); - }) - }); - - // 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 }; + ${parent.javascripts()} + ${h.js("jquery.autocomplete", "autocomplete_tagging" )} + <script type="text/javascript"> + ## TODO: generalize and move into galaxy.base.js + $(document).ready(function() { + init_grid_elements(); + init_grid_controls(); + }); + ## 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. + // + + // Operations that are not async (AJAX) compatible. + var no_async_ops = new Object(); + %for operation in grid.operations: + %if not operation.async_compatible: + no_async_ops['${operation.label.lower()}'] = "True"; + %endif + %endfor + + // Initialize grid controls + function init_grid_controls() + { + + // Initialize operation buttons. + $('input[name=operation]:submit').each(function() { + $(this).click( function() { + // Get operation name. + var operation_name = $(this).attr("value"); - 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 }; + // For some reason, $('input[name=id]:checked').val() does not return all ids for checked boxes. + // The code below performs this function. + var item_ids = new Array() + $('input[name=id]:checked').each(function() { + item_ids[item_ids.length] = $(this).val(); + }); + do_operation(operation_name, item_ids); + }); + }); + + // Initialize submit image elements. + $('.submit-image').each( function() + { + // On mousedown, add class to simulate click. + $(this).mousedown( function() { + $(this).addClass('gray-background'); + }); + + // On mouseup, add class to simulate click. + $(this).mouseup( function() { + $(this).removeClass('gray-background'); + }); + + }); + + // Initialize sort links. + $('.sort-link').each( function() + { + var sort_key = $(this).attr('sort_key'); + $(this).click( function() { + set_sort_condition(sort_key); + return false; + }); + + }); + + // Initialize page links. + $('.page-link > a').each( function() + { + var page_num = $(this).attr('page_num'); + $(this).click( function() { + set_page(page_num); + return false; + }); + + }); + $('#show-all-link').click( function() { + set_page('all'); + return false; + }); + + // Initialize categorical filters. + $('.categorical-filter > a').each( function() + { + $(this).click( function() { + var filter_key = $(this).attr('filter_key'); + var filter_val = $(this).attr('filter_val'); + set_categorical_filter(filter_key, filter_val); + return false; + }); + }); + + // Initialize text filters. + $('.text-filter-form').each( function() + { + $(this).submit( function() { + var column_key = $(this).attr('column_key'); + var text_input_obj = $('#input-' + column_key + '-filter'); + var text_input = text_input_obj.val(); + text_input_obj.val(''); + add_filter_condition(column_key, text_input, true); + return false; + }); + }); + + // 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 }; - t2.autocomplete("${h.url_for( controller='history', action='name_autocomplete_data' )}", autocomplete_options); - } - - // Initialize submit image elements. - $('.submit-image').each( function() - { - // On mousedown, add class to simulate click. - $(this).mousedown( function() { - $(this).addClass('gray-background'); - }); - - // On mouseup, add class to simulate click. - $(this).mouseup( function() { - $(this).removeClass('gray-background'); - }); - - }); - }); - ## 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 - - // Filter and sort args for grid. - var filter_args = ${h.to_json_string(cur_filter_dict)}; - var sort_key = "${sort_key}"; - - // - // 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_condition_to_grid_filter("tags", tag, true); - } - - // - // Add a filter to the current grid filter; this adds the filter and then issues a request to refresh the grid. - // - function add_condition_to_grid_filter(name, value, append) - { - // Update filter arg with new condition. - if (append) - { - // Append value. - var cur_val = filter_args[name]; - if (cur_val != "All") - cur_val = cur_val + ", " + value; - else - cur_val = value; - filter_args[name] = cur_val; - } - else - { - // Replace value. - filter_args[name] = value; - } - - // Build URL with filter args, sort key. - var filter_arg_value_strs = new Array(); - var i = 0; - for (arg in filter_args) - { - filter_arg_value_strs[i++] = "f-" + arg + "=" + filter_args[arg]; - } - var filter_str = filter_arg_value_strs.join("&"); - var url_base = "${h.url_for( action='list')}"; - var url = url_base + "?" + filter_str + "&sort=" + sort_key; - self.location = url; - } - - // - // Initiate navigation when user selects a page to view. - // - function navigate_to_page(page_select) - { - page_num = $(page_select).val(); - <% url_args = {"page" : "PAGE"} %> - var url_base = "${url( url_args )}"; - var url = url_base.replace("PAGE", page_num); - self.location = url; - } + 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); + } + + // Initialize advanced search toggles. + $('.advanced-search-toggle').each( function() + { + $(this).click( function() { + $('#more-search-options').slideToggle('fast'); + return false; + }); + }); + } + + // Overrides function in galaxy.base.js so that click does operation. + function make_popup_menus() + { + jQuery( "div[popupmenu]" ).each( function() { + var options = {}; + $(this).find( "a" ).each( function() { + var confirmtext = $(this).attr( "confirm" ), + href = $(this).attr( "href" ), + target = $(this).attr( "target" ); + options[ $(this).text() ] = function() { + if ( !confirmtext || confirm( confirmtext ) ) { + do_operation_from_href(href); + } + }; + }); + var b = $( "#" + $(this).attr( 'popupmenu' ) ); + make_popupmenu( b, options ); + $(this).remove(); + b.show(); + }); + } + + // Initialize grid elements. + function init_grid_elements() + { + // Initialize grid selection checkboxes. + $(".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 ); + }); + }) + }); + + // Initialize item labels. + $(".label").each( function() { + // If href has an operation in it, do operation when clicked. Otherwise do nothing. + var href = $(this).attr('href'); + if ( href.indexOf('operation=') != -1 ) + { + $(this).click( function() { + do_operation_from_href( $(this).attr('href') ); + return false; + }); + } + }); + + // Initialize item menu operations. + make_popup_menus(); + } + + // 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 page to URL args. + url_args['page'] = ${cur_page_num}; + + var num_pages = ${num_pages}; + + // Go back to page one; this is useful when a filter is applied. + function go_page_one() + { + // 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; + } + + // 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 : ""); + $('#more-search-options').show('fast'); + 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) + { + // Do nothing is value is empty. + if (value == "") + return false; + + // 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. + + // Remove visible element. + $(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; + } + } + + go_page_one(); + update_grid(); + }); + + var container = $('#' + name + "-filtering-criteria"); + container.append(t); + + go_page_one(); + update_grid(); + } + + // 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); + + // 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['sort'] = new_sort; + go_page_one(); + update_grid(); + } + + // Set new value for categorical filter. + function set_categorical_filter(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 = $.trim( $(this).text() ); + 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).addClass("current-filter"); + $(this).append(text); + } + 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( name, filter_value ); + }); + $(this).removeClass("current-filter"); + $(this).append(t); + } + }); + + // Update grid. + url_args["f-" + name] = new_value; + go_page_one(); + update_grid(); + } + + // 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); + } + }); + + var maintain_page_links = true; + if (new_page == "all") + { + url_args['page'] = new_page; + maintain_page_links = false; + } + else + url_args['page'] = parseInt(new_page); + update_grid(maintain_page_links); + } + + // Perform a grid operation. + function do_operation(operation, item_ids) + { + operation = operation.toLowerCase(); + + // Update URL args. + url_args['operation'] = operation; + url_args['id'] = item_ids; + + // If operation cannot be performed asynchronously, redirect to location. Otherwise do operation. + var no_async = ( no_async_ops[operation] != undefined && no_async_ops[operation] != null); + if (no_async) + { + go_to_URL(); + } + else + { + update_grid(true); + delete url_args['operation']; + delete url_args['id']; + } + } + + // Perform a hyperlink click that initiates an operation. If there is no operation, ignore click. + function do_operation_from_href(href) + { + // Get operation, id in hyperlink's href. + var href_parts = href.split("?"); + if (href_parts.length > 1) + { + var href_parms_str = href_parts[1]; + var href_parms = href_parms_str.split("&"); + var operation = null; + var id = -1; + for (var index = 0; index < href_parms.length; index++) + { + if (href_parms[index].indexOf('operation') != -1) + { + // Found operation parm; get operation value. + operation = href_parms[index].split('=')[1]; + } + else if (href_parms[index].indexOf('id') != -1) + { + // Found operation parm; get operation value. + id = href_parms[index].split('=')[1]; + } + } + + // Do operation. + do_operation(operation, id); + return false; + } + + } + + // Navigate window to the URL defined by url_args. This method should be used to short-circuit grid AJAXing. + function go_to_URL() + { + // Not async request. + url_args['async'] = false; + + // Build argument string. + var arg_str = ""; + var arg; + for (arg in url_args) + arg_str = arg_str + arg + "=" + url_args[arg] + "&"; + + // Go. + window.location = encodeURI( "${h.url_for()}?" + arg_str ); + } + + // Update grid. + function update_grid(maintain_page_links) + { + ## If grid is not using async, then go to URL. + %if not grid.use_async: + go_to_URL(); + return; + %endif + + // If there's an operation in the args, do POST; otherwise, do GET. + var operation = url_args['operation']; + var method = (operation != null && operation != undefined ? "POST" : "GET" ); + $.ajax({ + type: method, + 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 elements in the + // response: (1) table body; (2) number of pages in table; and (3) message. + 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 number of pages. + var num_pages = parseInt( parsed_response_text[1] ); + + // Rebuild page links. + if (!maintain_page_links) + { + // Remove page links. + var page_link_container = $('#page-link-container'); + page_link_container.children().remove(); + + // 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); + + // Show all link is visible only if there are multiple pages. + var elt = $('#show-all-link-span'); + if (num_pages > 1) + elt.show(); + else + elt.hide(); + + // 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>"); + t.attr('page', i); + t.click(function() { + var page = $(this).attr('page'); + set_page(page); + }); + span.append(t) + page_link_container.append(span); + } + } + + // Show message if there is one. + var message = $.trim( parsed_response_text[2] ); + if (message != "") + { + $('#grid-message').html( message ); + setTimeout("$('#grid-message').hide()", 5000); + } + } + }); + } </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; - } - .submit-image { - vertical-align: text-bottom; - margin: 0; - padding: 0; - } - .no-padding-or-margin { - margin: 0; - padding: 0; - } - .gray-background { - background-color: #DDDDDD; - } - .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; - } + ## 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, .current-filter { + font-style: italic; + } + .submit-image { + vertical-align: text-bottom; + margin: 0; + padding: 0; + } + .no-padding-or-margin { + margin: 0; + padding: 0; + } + .gray-background { + background-color: #DDDDDD; + } </style> </%def> <%namespace file="./grid_common.mako" import="*" /> -## Print grid header. -<%def name="grid_header()"> +## Render grid message. +<%def name="render_grid_message()"> + %if message: + <p> + <div class="${message_type}message transient-message">${util.restore_text( message )}</div> + <div style="clear: both"></div> + </p> + %endif +</%def> + +## Render grid header. +<%def name="render_grid_header(render_title=True)"> <div class="grid-header"> - <h2>${grid.title}</h2> + %if render_title: + <h2>${grid.title}</h2> + %endif %if grid.global_actions: <ul class="manage-table-actions"> @@ -227,11 +747,11 @@ </div> </%def> -## Print grid. -<%def name="grid_table()"> - <form action="${url()}" method="post" > +## Render grid. +<%def name="render_grid_table()"> + <form action="${url()}" method="post" onsubmit="return false;"> <table class="grid"> - <thead> + <thead id="grid-table-header"> <tr> <th></th> %for column in grid.columns: @@ -251,152 +771,164 @@ href = url( sort=column.key ) %> <th\ + id="${column.key}-header" %if column.ncells > 1: colspan="${column.ncells}" %endif > %if href: - <a href="${href}">${column.label}</a> + <a href="${href}" class="sort-link" sort_key='${column.key}'>${column.label}</a> %else: ${column.label} %endif - <span>${extra}</span> + <span class="sort-arrow">${extra}</span> </th> %endif %endfor <th></th> </tr> </thead> - <tbody> - %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 + <tbody id="grid-table-body"> + ${render_grid_table_body_contents()} </tbody> <tfoot> - ## 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> - <td></td> - <td colspan="100"> - Page ${cur_page_num} of ${num_pages} - Go to: - ## Next page link. - %if cur_page_num != num_pages: - <% args = { "page" : cur_page_num+1 } %> - <span><a href="${url( args )}">Next</a></span> - %endif - ## Previous page link. - %if cur_page_num != 1: - <span>|</span> - <% args = { "page" : cur_page_num-1 } %> - <span><a href="${url( args )}">Previous</a></span> - %endif - ## Go to page select box. - <span>| Select:</span> - <select id="page-select" onchange="navigate_to_page(this)"> - <option value=""></option> - %for page_index in range(1, num_pages + 1): - %if page_index == cur_page_num: - continue - %else: - <% args = { "page" : page_index } %> - <option value='${page_index}'>Page ${page_index}</option> - %endif - %endfor - </select> - ## Show all link. - <% args = { "page" : "all" } %> - <span>| <a href="${url( args )}">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 + ${render_grid_table_footer_contents()} </tfoot> </table> </form> -</%def> \ No newline at end of file +</%def> + +## Render grid table body contents. +<%def name="render_grid_table_body_contents()"> + <% num_rows_rendered = 0 %> + %if query.count() == 0: + ## No results. + <tr><td></td><td><em>No Items</em></td></tr> + <% num_rows_rendered = 1 %> + %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> + <% num_rows_rendered += 1 %> + %endfor + ## Dummy rows to prevent table for moving too much. + ##%if grid.use_paging: + ## %for i in range( num_rows_rendered , grid.num_rows_per_page ): + ## <tr><td colspan="1000"> </td></tr> + ## %endfor + ##%endif +</%def> + +## Render grid table footer contents. +<%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 grid.use_paging and 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 )}" page_num='${page_index}'>${page_index}</a></span> + %endif + %endfor + </span> + + ## Show all link. + <% args = { "page" : "all" } %> + <span id='show-all-link-span'>| <a href="${url( args )}" id="show-all-link">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 07c1b4bc14af -r 5761948422a8 templates/grid_base_async.mako --- a/templates/grid_base_async.mako Mon Nov 23 17:20:01 2009 -0500 +++ b/templates/grid_base_async.mako Tue Nov 24 14:53:19 2009 -0500 @@ -1,704 +1,7 @@ -<%! - 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)}"/> +<%namespace file="./grid_base.mako" import="*" /> -## 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(); - - // Operations that are not async (AJAX) compatible. - var no_async_ops = new Object(); - %for operation in grid.operations: - %if not operation.async_compatible: - no_async_ops['${operation.label}'] = "True"; - %endif - %endfor - - // Initialize each operation button to do operation when clicked. - $('input[name=operation]:submit').each(function() { - $(this).click( function() { - var this_value = $(this).attr("value"); - var no_async = ( no_async_ops[this_value] != undefined && no_async_ops[this_value] != null); - do_operation(this_value, no_async); - }); - }); - - // 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(); - }); - - var container = $('#' + name + "-filtering-criteria"); - container.append(t); - - update_grid(); - } - - // 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(); - } - - 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(true); - } - - // Perform a grid operation. TODO: this is not complete. - function do_operation(operation, no_async) - { - // For some reason, $('input[name=id]:checked').val() does not return all ids for checked boxes. - // The code below performs this function. - var item_ids = new Array() - $('input[name=id]:checked').each(function() { - item_ids[item_ids.length] = $(this).val(); - }); - - // Update URL args. - url_args['operation'] = operation; - url_args['id'] = item_ids; - - // If operation cannot be performed asynchronously, redirect to location. Otherwise do operation. - if (no_async) - { - var arg_str = ""; - var arg; - - for (arg in url_args) - arg_str = arg_str + arg + "=" + url_args[arg] + "&"; - - self.location = encodeURI( "${h.url_for()}?" + arg_str ); - } - else - update_grid(); - - } - - // Update grid. - function update_grid(maintain_page_links) - { - // If there's an operation in the args, do POST; otherwise, do GET. - var operation = url_args['operation']; - var method = (operation != null && operation != undefined ? "POST" : "GET" ); - $.ajax({ - type: method, - 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. - if (!maintain_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'); - } - } - } - }); - } - - </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(include_title)"> - <div class="grid-header"> - %if include_title: - <h2>${grid.title}</h2> - %endif - - %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="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> - +${render_grid_table_body_contents()} +***** +${num_pages} +***** +${render_grid_message()} \ No newline at end of file diff -r 07c1b4bc14af -r 5761948422a8 templates/grid_body_async.mako --- a/templates/grid_body_async.mako Mon Nov 23 17:20:01 2009 -0500 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,5 +0,0 @@ -<%namespace file="./grid_base_async.mako" import="*" /> - -${render_grid_table_body_contents()} -***** -${num_pages} \ No newline at end of file diff -r 07c1b4bc14af -r 5761948422a8 templates/grid_common.mako --- a/templates/grid_common.mako Mon Nov 23 17:20:01 2009 -0500 +++ b/templates/grid_common.mako Tue Nov 24 14:53:19 2009 -0500 @@ -11,8 +11,7 @@ <td align="left" style="padding-left: 10px">${column_label}:</td> <td> %if isinstance(column, TextColumn): - <form name="history_actions" action="${url( dict() )}" - method="get" > + <form class="text-filter-form" column_key="${column.key}" action="${url( dict() )}" method="get" > ## Carry forward filtering criteria with hidden inputs. %for temp_column in grid.columns: %if temp_column.key in cur_filter_dict: @@ -28,6 +27,7 @@ %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 ): @@ -54,21 +54,34 @@ %endif %endif + </span> + ## Print input field for column. <span> - <input class="no-padding-or-margin" id="input-${column.key}-filter" name="f-${column.key}" type="text" value="" size="15"/> - <input class='submit-image' type='image' src='${h.url_for('/static/images/mag_glass.png')}' alt='Filter'/></span> + <input class="no-padding-or-margin" id="input-${column.key}-filter" name="f-${column.key}" type="text" value="" size="15"/> + <input class='submit-image' type='image' src='${h.url_for('/static/images/mag_glass.png')}' alt='Filter'/> + </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: - <span>|</span> + | %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="filter" style="font-style: italic">${filter.label}</span> + <span class="categorical-filter ${column.key}-filter current-filter">${filter.label}</span> %else: - <span class="filter"><a href="${url( filter.get_url_args() )}">${filter.label}</a></span> + <span class="categorical-filter ${column.key}-filter"> + <a href="${url( filter.get_url_args() )}" filter_key="${filter_key}" filter_val="${filter_arg}">${filter.label}</a> + </span> %endif %endfor + </span> %endif </td> </tr> @@ -96,31 +109,47 @@ ## Only show advanced search if there are filterable columns. <% - show_advanced_search = False + show_advanced_search_link = False for column in grid.columns: if column.filterable == "advanced": - show_advanced_search = True + show_advanced_search_link = True break endif %> - %if show_advanced_search: - | <a href="" onclick="javascript:$('#more-search-options').slideToggle('fast');return false;">Advanced Search</a> + %if show_advanced_search_link: + <% args = { "advanced-search" : True } %> + | <a href="${url( args )}" class="advanced-search-toggle">Advanced Search</a> %endif </td> </tr></table> </div> ## Advanced search. - <div id="more-search-options" style="display: none; padding-top: 5px"> + <% + # Show advanced search if flag set or if there are filters for advanced search fields. + advanced_search_display = "none" + if 'advanced-search' in kwargs and kwargs['advanced-search'] in ['True', 'true']: + advanced_search_display = "block" + + 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]: + advanced_search_display = "block" + %> + <div id="more-search-options" style="display: ${advanced_search_display}; 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> | + <% args = { "advanced-search" : False } %> + <a href="${url( args )}" class="advanced-search-toggle">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> + ##| + ##<% + ## 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": diff -r 07c1b4bc14af -r 5761948422a8 templates/grid_common_async.mako --- a/templates/grid_common_async.mako Mon Nov 23 17:20:01 2009 -0500 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,155 +0,0 @@ -<%! 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 07c1b4bc14af -r 5761948422a8 templates/history/grid.mako --- a/templates/history/grid.mako Mon Nov 23 17:20:01 2009 -0500 +++ b/templates/history/grid.mako Tue Nov 24 14:53:19 2009 -0500 @@ -1,1 +1,1 @@ -<%inherit file="/grid_base.mako"/> +<%inherit file="../grid_base.mako"/> diff -r 07c1b4bc14af -r 5761948422a8 templates/page/select_histories_grid.mako --- a/templates/page/select_histories_grid.mako Mon Nov 23 17:20:01 2009 -0500 +++ b/templates/page/select_histories_grid.mako Tue Nov 24 14:53:19 2009 -0500 @@ -1,6 +1,13 @@ ## Template generates a grid that enables user to select histories. -<%namespace file="../grid_base_async.mako" import="*" /> +<%namespace file="../grid_base.mako" import="*" /> ${javascripts()} +${stylesheets()} ${render_grid_header(False)} -${render_grid_table()} \ No newline at end of file +${render_grid_table()} + +## Initialize the grid. +<script type="text/javascript"> + init_grid_elements(); + init_grid_controls(); +</script>