Grid framework refactoring to increase speed and ease of debugging: (1) create and use Backbone object for grid; (2) move grid javascript to its own file; (3) remove webapp parameter; (4) augment search box style so that search button does not wrap. Pack scripts.

This commit is contained in:
Jeremy Goecks
2013-01-28 14:02:25 -05:00
parent 0ca2a0112f
commit 409ba63a4f
9 changed files with 587 additions and 526 deletions
@@ -16,7 +16,6 @@ class Grid( object ):
"""
Specifies the content and format of a grid (data table).
"""
webapp = None
title = ""
exposed = True
model_class = None
@@ -56,7 +55,6 @@ class Grid( object ):
def __call__( self, trans, **kwargs ):
# Get basics.
# FIXME: pretty sure this is only here to pass along, can likely be eliminated
webapp = trans.webapp.name
status = kwargs.get( 'status', None )
message = kwargs.get( 'message', None )
# Build a base filter and sort key that is the combination of the saved state and defaults.
@@ -229,7 +227,6 @@ class Grid( object ):
params = cur_filter_dict.copy()
params['sort'] = sort_key
params['async'] = ( 'async' in kwargs )
params['webapp'] = webapp
trans.log_action( trans.get_user(), unicode( "grid.view" ), context, params )
# Render grid.
def url( *args, **kwargs ):
@@ -273,7 +270,6 @@ class Grid( object ):
status = status,
message = message,
use_panels=self.use_panels,
webapp=webapp,
show_item_checkboxes = ( self.show_item_checkboxes or
kwargs.get( 'show_item_checkboxes', '' ) in [ 'True', 'true' ] ),
# Pass back kwargs so that grid template can set and use args without
+1
View File
@@ -1085,6 +1085,7 @@ div.debug {
vertical-align: bottom;
display: inline-block;
padding: 0;
white-space: nowrap;
// border: 1px solid #aaa;
}
.gray-background {
+1 -1
View File
@@ -976,7 +976,7 @@ div.debug{margin:10px;padding:5px;background:#FFFF99;border:solid #FFFF33 1px;co
#advanced-search table{border-collapse:separate;}
.delete-search-icon{background:url(../images/delete_tag_icon_gray.png) center no-repeat;display:inline-block;width:10px;cursor:pointer;height:18px;vertical-align:middle;margin-left:2px;}
.search-box-input{border:0;float:left;outline:medium none;font-style:italic;font-size:inherit;}
.search-box{vertical-align:bottom;display:inline-block;padding:0;}
.search-box{vertical-align:bottom;display:inline-block;padding:0;white-space:nowrap;}
.gray-background{background-color:#DDDDDD;}
.loading-elt-overlay{background-color:white;opacity:0.5;width:100%;height:100%;z-index:14000;position:fixed;display:none;}
div.odd_row{background:#dadfef;}
+549
View File
@@ -0,0 +1,549 @@
// External dependencies (for module management eventually): jQuery, Backbone, underscore
// This is necessary so that, when nested arrays are used in ajax/post/get methods, square brackets ('[]') are
// not appended to the identifier of a nested array.
jQuery.ajaxSettings.traditional = true;
// Initialize grid objects on load.
$(document).ready(function() {
init_grid_elements();
init_grid_controls();
// Initialize text filters to select text on click and use normal font when user is typing.
$('input[type=text]').each(function() {
$(this).click(function() { $(this).select(); } )
.keyup(function () { $(this).css("font-style", "normal"); });
});
});
/**
* A Galaxy grid.
*/
var Grid = Backbone.Model.extend({
defaults: {
url_base: '',
async: false,
async_ops: [],
categorical_filters: [],
filters: {},
sort_key: null,
show_item_checkboxes: false,
cur_page: 1,
num_pages: 1,
operation: undefined,
item_ids: undefined
},
/**
* Return true if operation can be done asynchronously.
*/
can_async_op: function(op) {
return _.indexOf(this.attributes.async_ops, op) !== -1;
},
/**
* Add filtering criterion.
*/
add_filter: function(key, value, append) {
// Update URL arg with new condition.
if (append) {
// Update or append value.
var cur_val = this.attributes.key,
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 = [];
values[0] = cur_val;
values[1] = value;
new_val = values;
}
}
else {
// Current value is an array.
new_val = cur_val;
new_val.push(value);
}
this.attributes.filters[key] = new_val;
}
else {
// Replace value.
this.attributes.filters[key] = value;
}
},
/**
* Remove filtering criterion.
*/
remove_filter: function(key, condition) {
var cur_val = this.attributes.filters[key];
if (cur_val === null || cur_val === undefined) {
return false;
}
var removed = true;
if (typeof(cur_val) === "string") {
if (cur_val == "All") {
// Unexpected. Throw error?
removed = false;
}
else {
// Remove condition.
delete this.attributes.filters[key];
}
}
else {
// Filter contains an array of conditions.
var condition_index = _.indexOf(cur_val, condition);
if (condition_index !== -1) {
cur_val.splice(condition_index, 1);
}
else {
removed = false;
}
}
return removed;
},
/**
* Returns URL data for obtaining a new grid.
*/
get_url_data: function() {
var url_data = {
async: this.attributes.async,
sort: this.attributes.sort_key,
page: this.attributes.cur_page,
show_item_checkboxes: this.attributes.show_item_checkboxes,
operation: this.attributes.operation,
id: this.attributes.item_ids
};
// Add filter arguments to data, placing "f-" in front of all arguments.
// FIXME: when underscore updated, use pairs function().
var self = this;
_.each(_.keys(self.attributes.filters), function(k) {
url_data['f-' + k] = self.attributes.filters[k];
});
return url_data;
}
});
//
// Code to handle grid operations: filtering, sorting, paging, and operations.
//
// Init operation buttons.
function init_operation_buttons() {
// Initialize operation buttons.
$('input[name=operation]:submit').each(function() {
$(this).click( function() {
var operation_name = $(this).val();
// 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 = [];
$('input[name=id]:checked').each(function() {
item_ids.push( $(this).val() );
});
do_operation(operation_name, item_ids);
});
});
}
// Initialize grid controls
function init_grid_controls() {
init_operation_buttons();
// 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() {
$(this).click( function() {
set_sort_condition( $(this).attr('sort_key') );
return false;
});
});
// Initialize page links.
$('.page-link > a').each( function() {
$(this).click( function() {
set_page( $(this).attr('page_num') );
return false;
});
});
// Initialize categorical filters.
$('.categorical-filter > a').each( function() {
$(this).click( function() {
set_categorical_filter( $(this).attr('filter_key'), $(this).attr('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) {
t.autocomplete(history_tag_autocomplete_url,
{ selectFirst: false, autoFill: false, highlight: false, mustMatch: false });
}
var t2 = $("#input-name-filter");
if (t2.length) {
t2.autocomplete(history_name_autocomplete_url,
{ selectFirst: false, autoFill: false, highlight: false, mustMatch: false });
}
// Initialize standard, advanced search toggles.
$('.advanced-search-toggle').each( function() {
$(this).click( function() {
$("#standard-search").slideToggle('fast');
$('#advanced-search').slideToggle('fast');
return false;
});
});
}
// Initialize grid elements.
function init_grid_elements() {
// Initialize grid selection checkboxes.
$(".grid").each( function() {
var checkboxes = $(this).find("input.grid-row-select-checkbox");
var check_count = $(this).find("span.grid-selected-count");
var update_checked = function() {
check_count.text( $(checkboxes).filter(":checked").length );
};
$(checkboxes).each( function() {
$(this).change(update_checked);
});
update_checked();
});
// 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 !== undefined && href.indexOf('operation=') != -1 ) {
$(this).click( function() {
do_operation_from_href( $(this).attr('href') );
return false;
});
}
});
// Initialize ratings.
$('.community_rating_star').rating({});
// Initialize item menu operations.
make_popup_menus();
}
// 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 = grid.get('cur_page');
if (cur_page !== null && cur_page !== undefined && cur_page !== 'all') {
grid.set('cur_page', 1);
}
}
// 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;
}
// Add condition to grid.
grid.add_filter(name, value, append);
// Add button that displays filter and provides a button to delete it.
var t = $("<span>" + value + "<a href='javascript:void(0);'><span class='delete-search-icon' /></a></span>");
t.addClass('text-filter-val');
t.click(function() {
// Remove filter condition.
grid.remove_filter(name, value);
// Remove visible element.
$(this).remove();
go_page_one();
update_grid();
});
var container = $('#' + name + "-filtering-criteria");
container.append(t);
go_page_one();
update_grid();
}
// 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 !== undefined && tag_value !== "" ? ":" + tag_value : "");
$('#advanced-search').show('fast');
add_filter_condition("tags", tag, 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 = grid.get('sort_key');
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 = (new_sort.substring(0,1) == '-') ? "&uarr;" : "&darr;";
var t = $("<span>" + sort_arrow + "</span>").addClass('sort-arrow');
var th = $("#" + col_key + '-header');
th.append(t);
// Update grid.
grid.set('sort_key', 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 = grid.get('categorical_filters')[name],
cur_value = grid.get('filters')[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.
grid.add_filter(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'),
page_num = parseInt( id.split("-")[2], 10 ), // Id has form 'page-link-<page_num>
cur_page = grid.get('cur_page'),
text;
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.
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.
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") {
grid.set('cur_page', new_page);
maintain_page_links = false;
} else {
grid.set('cur_page', parseInt(new_page, 10));
}
update_grid(maintain_page_links);
}
// Perform a grid operation.
function do_operation(operation, item_ids) {
operation = operation.toLowerCase();
// Update grid.
grid.set({
operation: operation,
item_ids: item_ids
});
// Do operation. If operation cannot be performed asynchronously, redirect to location.
if (grid.can_async_op(operation)) {
update_grid(true);
}
else {
go_to_URL();
}
}
// 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 id parm; get id 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 can be used to short-circuit grid AJAXing.
function go_to_URL() {
// Not async request.
grid.set('async', false);
// Go.
window.location = grid.get('url_base') + "?" + $.param(grid.get_url_data());
}
// Update grid.
function update_grid(maintain_page_links) {
// If grid is not using async, then go to URL.
if (!grid.get('async')) {
go_to_URL();
}
// If there's an operation, do POST; otherwise, do GET.
var method = (grid.get('operation') ? "POST" : "GET" );
$('.loading-elt-overlay').show(); // Show overlay to indicate loading and prevent user actions.
$.ajax({
type: method,
url: grid.get('url_base'),
data: grid.get_url_data(),
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 and footer.
$('#grid-table-body').html(parsed_response_text[0]);
// FIXME: this does not work at all; what's needed is a function
// that updates page links when number of pages changes.
$('#grid-table-footer').html(parsed_response_text[1]);
// Trigger custom event to indicate grid body has changed.
$('#grid-table-body').trigger('update');
// Init grid.
init_grid_elements();
init_operation_buttons();
make_popup_menus();
// Hide loading overlay.
$('.loading-elt-overlay').hide();
// Show message if there is one.
var message = $.trim( parsed_response_text[2] );
if (message !== "") {
$('#grid-message').html( message ).show();
setTimeout( function() { $('#grid-message').hide(); }, 5000);
}
},
complete: function() {
// Clear grid of transient request attributes.
grid.set({
operation: undefined,
item_ids: undefined
});
}
});
}
function check_all_items() {
var chk_all = document.getElementById('check_all'),
checks = document.getElementsByTagName('input'),
total = 0,
i;
if ( chk_all.checked === true ) {
for ( i=0; i < checks.length; i++ ) {
if ( checks[i].name.indexOf( 'id' ) !== -1) {
checks[i].checked = true;
total++;
}
}
}
else {
for ( i=0; i < checks.length; i++ ) {
if ( checks[i].name.indexOf( 'id' ) !== -1) {
checks[i].checked = false;
}
}
}
init_grid_elements();
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
var History=BaseModel.extend(LoggableMixin).extend({defaults:{id:"",name:"",state:"",diskSize:0,deleted:false,annotation:null,message:null},urlRoot:"api/histories/",url:function(){return"api/histories/"+this.get("id")},initialize:function(a,b){this.log(this+".initialize:",a,b);this.hdas=new HDACollection();if(b&&b.length){this.hdas.reset(b);this.checkForUpdates()}},loadFromApi:function(a,c){var b=this;b.attributes.id=a;jQuery.when(jQuery.ajax("api/users/current"),b.fetch()).then(function(e,d){b.attributes.user=e[0];b.trigger("loaded:user",e[0]);b.trigger("loaded",d[0])}).then(function(){jQuery.ajax(b.url()+"/contents?"+jQuery.param({ids:b.hdaIdsFromStateIds().join(",")})).success(function(d){b.hdas.reset(d);b.checkForUpdates();b.trigger("loaded:hdas",d);if(c){callback(b)}})})},hdaIdsFromStateIds:function(){return _.reduce(_.values(this.get("state_ids")),function(b,a){return b.concat(a)})},checkForUpdates:function(a){if(this.hdas.running().length){this.stateUpdater()}else{this.trigger("ready")}return this},stateUpdater:function(){var c=this,a=this.get("state"),b=this.get("state_ids");jQuery.ajax("api/histories/"+this.get("id")).success(function(d){c.set(d);c.log("current history state:",c.get("state"),"(was)",a,"new size:",c.get("nice_size"));var e=[];_.each(_.keys(d.state_ids),function(g){var f=_.difference(d.state_ids[g],b[g]);e=e.concat(f)});if(e.length){c.updateHdas(e)}if((c.get("state")===HistoryDatasetAssociation.STATES.RUNNING)||(c.get("state")===HistoryDatasetAssociation.STATES.QUEUED)){setTimeout(function(){c.stateUpdater()},History.UPDATE_DELAY)}else{c.trigger("ready")}}).error(function(f,d,e){if(!((f.readyState===0)&&(f.status===0))){alert(_l("Error getting history updates from the server.")+"\n"+e)}})},updateHdas:function(b){var a=this;jQuery.ajax({url:this.url()+"/contents?"+jQuery.param({ids:b.join(",")}),error:function(f,c,d){var e="ERROR updating hdas from api history contents:";a.log(e,b,f,c,d);alert(e+b.join(","))},success:function(d,c,f){a.log(a+".updateHdas, success:",d,c,f);var e=[];_.each(d,function(h,i){var g=a.hdas.get(h.id);if(g){a.log("found existing model in list for id "+h.id+", updating...:");g.set(h)}else{a.log("NO existing model for id "+h.id+", creating...:");e.push(h)}});if(e.length){a.addHdas(e)}}})},addHdas:function(a){var b=this;_.each(a,function(c,d){var e=b.hdas.hidToCollectionIndex(c.hid);c.history_id=b.get("id");b.hdas.add(new HistoryDatasetAssociation(c),{at:e,silent:true})});b.hdas.trigger("add",a)},toString:function(){var a=(this.get("name"))?(","+this.get("name")):("");return"History("+this.get("id")+a+")"}});History.UPDATE_DELAY=4000;var HistoryCollection=Backbone.Collection.extend(LoggableMixin).extend({model:History,urlRoot:"api/histories"});
var History=BaseModel.extend(LoggableMixin).extend({defaults:{id:"",name:"",state:"",diskSize:0,deleted:false,annotation:null,message:null},urlRoot:"api/histories/",url:function(){return"api/histories/"+this.get("id")},initialize:function(a,b){this.log(this+".initialize:",a,b);this.hdas=new HDACollection();if(b){if(_.isArray(b)){this.hdas.reset(b);this.checkForUpdates()}else{if(_.isString(b)&&(b.match(/error/i))){alert(_l("Error loading bootstrapped history")+":\n"+b)}}}},loadFromApi:function(a,c){var b=this;b.attributes.id=a;jQuery.when(jQuery.ajax("api/users/current"),b.fetch()).then(function(e,d){b.attributes.user=e[0];b.trigger("loaded:user",e[0]);b.trigger("loaded",d[0])}).then(function(){jQuery.ajax(b.url()+"/contents?"+jQuery.param({ids:b.hdaIdsFromStateIds().join(",")})).success(function(d){b.hdas.reset(d);b.checkForUpdates();b.trigger("loaded:hdas",d);if(c){callback(b)}})})},hdaIdsFromStateIds:function(){return _.reduce(_.values(this.get("state_ids")),function(b,a){return b.concat(a)})},checkForUpdates:function(a){if(this.hdas.running().length){this.stateUpdater()}else{this.trigger("ready")}return this},stateUpdater:function(){var c=this,a=this.get("state"),b=this.get("state_ids");jQuery.ajax("api/histories/"+this.get("id")).success(function(d){c.set(d);c.log("current history state:",c.get("state"),"(was)",a,"new size:",c.get("nice_size"));var e=[];_.each(_.keys(d.state_ids),function(g){var f=_.difference(d.state_ids[g],b[g]);e=e.concat(f)});if(e.length){c.fetchHdaUpdates(e)}if((c.get("state")===HistoryDatasetAssociation.STATES.RUNNING)||(c.get("state")===HistoryDatasetAssociation.STATES.QUEUED)){setTimeout(function(){c.stateUpdater()},History.UPDATE_DELAY)}else{c.trigger("ready")}}).error(function(f,d,e){if(!((f.readyState===0)&&(f.status===0))){alert(_l("Error getting history updates from the server.")+"\n"+e)}})},fetchHdaUpdates:function(b){var a=this;jQuery.ajax({url:this.url()+"/contents?"+jQuery.param({ids:b.join(",")}),error:function(h,c,d){if((h.readyState===0)&&(h.status===0)){return}var f=JSON.parse(h.responseText);if(_.isArray(f)){var e=_.groupBy(f,function(i){if(_.has(i,"error")){return"errored"}return"ok"});a.log("fetched, errored datasets:",e.errored);a.updateHdas(f)}else{var g=_l("ERROR updating hdas from api history contents")+": ";a.log(g,b,h,c,d,errorJSON);alert(g+b.join(","))}},success:function(d,c,e){a.log(a+".fetchHdaUpdates, success:",c,e);a.updateHdas(d)}})},updateHdas:function(a){var c=this,b=[];c.log(c+".updateHdas:",a);_.each(a,function(e,f){var d=c.hdas.get(e.id);if(d){c.log("found existing model in list for id "+e.id+", updating...:");d.set(e)}else{c.log("NO existing model for id "+e.id+", creating...:");b.push(e)}});if(b.length){c.addHdas(b)}},addHdas:function(a){var b=this;_.each(a,function(c,d){var e=b.hdas.hidToCollectionIndex(c.hid);c.history_id=b.get("id");b.hdas.add(new HistoryDatasetAssociation(c),{at:e,silent:true})});b.hdas.trigger("add",a)},toString:function(){var a=(this.get("name"))?(","+this.get("name")):("");return"History("+this.get("id")+a+")"}});History.UPDATE_DELAY=4000;var HistoryCollection=Backbone.Collection.extend(LoggableMixin).extend({model:History,urlRoot:"api/histories"});
+1 -1
View File
@@ -1 +1 @@
define(["base","libs/underscore","viz/trackster/slotting","viz/trackster/painters","viz/trackster/tracks","viz/visualization"],function(g,c,h,e,b,d){var a=b.object_from_template;var f=g.Base.extend({initialize:function(j){this.baseURL=j},createButtonMenu:function(){var j=this,k=create_icon_buttons_menu([{icon_class:"plus-button",title:"Add tracks",on_click:function(){d.select_datasets(select_datasets_url,add_track_async_url,{"f-dbkey":view.dbkey},function(l){c.each(l,function(m){view.add_drawable(a(m,view,view))})})}},{icon_class:"block--plus",title:"Add group",on_click:function(){view.add_drawable(new b.DrawableGroup(view,view,{name:"New Group"}))}},{icon_class:"bookmarks",title:"Bookmarks",on_click:function(){parent.force_right_panel(($("div#right").css("right")=="0px"?"hide":"show"))}},{icon_class:"globe",title:"Circster",on_click:function(){window.location=j.baseURL+"visualization/circster?id="+view.vis_id}},{icon_class:"disk--arrow",title:"Save",on_click:function(){show_modal("Saving...","progress");var l=[];$(".bookmark").each(function(){l.push({position:$(this).children(".position").text(),annotation:$(this).children(".annotation").text()})});var m=(view.overview_drawable?view.overview_drawable.name:null),n={view:view.to_dict(),viewport:{chrom:view.chrom,start:view.low,end:view.high,overview:m},bookmarks:l};$.ajax({url:galaxy_paths.get("visualization_url"),type:"POST",dataType:"json",data:{id:view.vis_id,title:view.name,dbkey:view.dbkey,type:"trackster",vis_json:JSON.stringify(n)}}).success(function(o){hide_modal();view.vis_id=o.vis_id;view.has_changes=false;window.history.pushState({},"",o.url+window.location.hash)}).error(function(){show_modal("Could Not Save","Could not save visualization. Please try again later.",{Close:hide_modal})})}},{icon_class:"cross-circle",title:"Close",on_click:function(){window.location=j.baseURL+"visualization/list"}}],{tooltip_config:{placement:"bottom"}});this.buttonMenu=k;return k},add_bookmarks:function(){var j=this,k=this.baseURL;show_modal("Select dataset for new bookmarks","progress");$.ajax({url:this.baseURL+"/visualization/list_histories",data:{"f-dbkey":view.dbkey},error:function(){alert("Grid failed")},success:function(l){show_modal("Select dataset for new bookmarks",l,{Cancel:function(){hide_modal()},Insert:function(){$("input[name=id]:checked,input[name=ldda_ids]:checked").first().each(function(){var m,n=$(this).val();if($(this).attr("name")==="id"){m={hda_id:n}}else{m={ldda_id:n}}$.ajax({url:this.baseURL+"/visualization/bookmarks_from_dataset",data:m,dataType:"json"}).then(function(o){for(i=0;i<o.data.length;i++){var p=o.data[i];j.add_bookmark(p[0],p[1])}})});hide_modal()}})}})},add_bookmark:function(n,l,j){var p=$("#bookmarks-container"),r=$("<div/>").addClass("bookmark").appendTo(p);var s=$("<div/>").addClass("position").appendTo(r),o=$("<a href=''/>").text(n).appendTo(s).click(function(){view.go_to(n);return false}),m=$("<div/>").text(l).appendTo(r);if(j){var q=$("<div/>").addClass("delete-icon-container").prependTo(r).click(function(){r.slideUp("fast");r.remove();view.has_changes=true;return false}),k=$("<a href=''/>").addClass("icon-button delete").appendTo(q);m.make_text_editable({num_rows:3,use_textarea:true,help_text:"Edit bookmark note"}).addClass("annotation")}view.has_changes=true;return r},create_visualization:function(o,j,n,p,m){var l=this,k=new b.TracksterView(o);k.editor=true;$.when(k.load_chroms_deferred).then(function(A){if(j){var y=j.chrom,q=j.start,v=j.end,s=j.overview;if(y&&(q!==undefined)&&v){k.change_chrom(y,q,v)}}else{k.change_chrom(A[0].chrom)}if(n){var t,r,u;for(var w=0;w<n.length;w++){k.add_drawable(a(n[w],k,k))}}k.update_intro_div();var z;for(var w=0;w<k.drawables.length;w++){if(k.drawables[w].name===s){k.set_overview(k.drawables[w]);break}}if(p){var x;for(var w=0;w<p.length;w++){x=p[w];l.add_bookmark(x.position,x.annotation,m)}}k.has_changes=false});return k},init_keyboard_nav:function(j){$(document).keydown(function(k){if($(k.srcElement).is(":input")){return}switch(k.which){case 37:j.move_fraction(0.25);break;case 38:var l=Math.round(j.viewport_container.height()/15);j.viewport_container.scrollTop(j.viewport_container.scrollTop()-20);break;case 39:j.move_fraction(-0.25);break;case 40:var l=Math.round(j.viewport_container.height()/15);j.viewport_container.scrollTop(j.viewport_container.scrollTop()+20);break}})}});return{object_from_template:a,TracksterUI:f}});
define(["base","libs/underscore","viz/trackster/slotting","viz/trackster/painters","viz/trackster/tracks","viz/visualization"],function(g,c,h,e,b,d){var a=b.object_from_template;var f=g.Base.extend({initialize:function(j){this.baseURL=j},createButtonMenu:function(){var j=this,k=create_icon_buttons_menu([{icon_class:"plus-button",title:"Add tracks",on_click:function(){d.select_datasets(select_datasets_url,add_track_async_url,{"f-dbkey":view.dbkey},function(l){c.each(l,function(m){view.add_drawable(a(m,view,view))})})}},{icon_class:"block--plus",title:"Add group",on_click:function(){view.add_drawable(new b.DrawableGroup(view,view,{name:"New Group"}))}},{icon_class:"bookmarks",title:"Bookmarks",on_click:function(){parent.force_right_panel(($("div#right").css("right")=="0px"?"hide":"show"))}},{icon_class:"globe",title:"Circster",on_click:function(){window.location=j.baseURL+"visualization/circster?id="+view.vis_id}},{icon_class:"disk--arrow",title:"Save",on_click:function(){show_modal("Saving...","progress");var l=[];$(".bookmark").each(function(){l.push({position:$(this).children(".position").text(),annotation:$(this).children(".annotation").text()})});var m=(view.overview_drawable?view.overview_drawable.name:null),n={view:view.to_dict(),viewport:{chrom:view.chrom,start:view.low,end:view.high,overview:m},bookmarks:l};$.ajax({url:galaxy_paths.get("visualization_url"),type:"POST",dataType:"json",data:{id:view.vis_id,title:view.name,dbkey:view.dbkey,type:"trackster",vis_json:JSON.stringify(n)}}).success(function(o){hide_modal();view.vis_id=o.vis_id;view.has_changes=false;window.history.pushState({},"",o.url+window.location.hash)}).error(function(){show_modal("Could Not Save","Could not save visualization. Please try again later.",{Close:hide_modal})})}},{icon_class:"cross-circle",title:"Close",on_click:function(){window.location=j.baseURL+"visualization/list"}}],{tooltip_config:{placement:"bottom"}});this.buttonMenu=k;return k},add_bookmarks:function(){var j=this,k=this.baseURL;show_modal("Select dataset for new bookmarks","progress");$.ajax({url:this.baseURL+"/visualization/list_histories",data:{"f-dbkey":view.dbkey},error:function(){alert("Grid failed")},success:function(l){show_modal("Select dataset for new bookmarks",l,{Cancel:function(){hide_modal()},Insert:function(){$("input[name=id]:checked,input[name=ldda_ids]:checked").first().each(function(){var m,n=$(this).val();if($(this).attr("name")==="id"){m={hda_id:n}}else{m={ldda_id:n}}$.ajax({url:this.baseURL+"/visualization/bookmarks_from_dataset",data:m,dataType:"json"}).then(function(o){for(i=0;i<o.data.length;i++){var p=o.data[i];j.add_bookmark(p[0],p[1])}})});hide_modal()}})}})},add_bookmark:function(n,l,j){var p=$("#bookmarks-container"),r=$("<div/>").addClass("bookmark").appendTo(p);var s=$("<div/>").addClass("position").appendTo(r),o=$("<a href=''/>").text(n).appendTo(s).click(function(){view.go_to(n);return false}),m=$("<div/>").text(l).appendTo(r);if(j){var q=$("<div/>").addClass("delete-icon-container").prependTo(r).click(function(){r.slideUp("fast");r.remove();view.has_changes=true;return false}),k=$("<a href=''/>").addClass("icon-button delete").appendTo(q);m.make_text_editable({num_rows:3,use_textarea:true,help_text:"Edit bookmark note"}).addClass("annotation")}view.has_changes=true;return r},create_visualization:function(o,j,n,p,m){var l=this,k=new b.TracksterView(o);k.editor=true;$.when(k.load_chroms_deferred).then(function(A){if(j){var y=j.chrom,q=j.start,v=j.end,s=j.overview;if(y&&(q!==undefined)&&v){k.change_chrom(y,q,v)}else{k.change_chrom(A[0].chrom)}}else{k.change_chrom(A[0].chrom)}if(n){var t,r,u;for(var w=0;w<n.length;w++){k.add_drawable(a(n[w],k,k))}}k.update_intro_div();var z;for(var w=0;w<k.drawables.length;w++){if(k.drawables[w].name===s){k.set_overview(k.drawables[w]);break}}if(p){var x;for(var w=0;w<p.length;w++){x=p[w];l.add_bookmark(x.position,x.annotation,m)}}k.has_changes=false});return k},init_keyboard_nav:function(j){$(document).keydown(function(k){if($(k.srcElement).is(":input")){return}switch(k.which){case 37:j.move_fraction(0.25);break;case 38:var l=Math.round(j.viewport_container.height()/15);j.viewport_container.scrollTop(j.viewport_container.scrollTop()-20);break;case 39:j.move_fraction(-0.25);break;case 40:var l=Math.round(j.viewport_container.height()/15);j.viewport_container.scrollTop(j.viewport_container.scrollTop()+20);break}})}});return{object_from_template:a,TracksterUI:f}});
+32 -518
View File
@@ -52,23 +52,8 @@
</%def>
<%def name="grid_javascripts()">
${h.js("libs/jquery/jquery.autocomplete", "galaxy.autocom_tagging", "libs/jquery/jquery.rating" )}
${h.js("libs/jquery/jquery.autocomplete", "galaxy.autocom_tagging", "libs/jquery/jquery.rating", "galaxy.grids" )}
<script type="text/javascript">
// This is necessary so that, when nested arrays are used in ajax/post/get methods, square brackets ('[]') are
// not appended to the identifier of a nested array.
jQuery.ajaxSettings.traditional = true;
## TODO: generalize and move into galaxy.base.js
$(document).ready(function() {
init_grid_elements();
init_grid_controls();
// Initialize text filters to select text on click and use normal font when user is typing.
$('input[type=text]').each(function() {
$(this).click(function() { $(this).select(); } )
.keyup(function () { $(this).css("font-style", "normal"); })
});
});
## TODO: Can this be moved into base.mako? Also, this is history-specific grid code.
%if refresh_frames:
%if 'masthead' in refresh_frames:
@@ -90,7 +75,7 @@
}
else {
// TODO: redirecting to root should be done on the server side so that page
// doesn't have to load.
// does not have to load.
// No history frame, so refresh to root to see history.
window.top.location.href = "${h.url_for( controller='root' )}";
@@ -105,146 +90,21 @@
}
%endif
%endif
//
// Code to handle grid operations: filtering, sorting, paging, and operations.
//
// Operations that are async (AJAX) compatible.
var async_ops = {};
%for operation in [op for op in grid.operations if op.async_compatible]:
async_ops['${operation.label.lower()}'] = "True";
%endfor
// Init operation buttons.
function init_operation_buttons() {
// Initialize operation buttons.
$('input[name=operation]:submit').each(function() {
$(this).click( function() {
var webapp = $("input[name=webapp]").attr("value");
var operation_name = $(this).val();
// 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 = [];
$('input[name=id]:checked').each(function() {
item_ids.push( $(this).val() );
});
do_operation(webapp, operation_name, item_ids);
});
});
};
// Initialize grid controls
function init_grid_controls() {
init_operation_buttons();
// 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() {
$(this).click( function() {
set_sort_condition( $(this).attr('sort_key') );
return false;
});
});
// Initialize page links.
$('.page-link > a').each( function() {
$(this).click( function() {
set_page( $(this).attr('page_num') );
return false;
});
});
// Initialize categorical filters.
$('.categorical-filter > a').each( function() {
$(this).click( function() {
set_categorical_filter( $(this).attr('filter_key'), $(this).attr('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) {
t.autocomplete( "${h.url_for( controller='tag', action='tag_autocomplete_data', item_class='History' )}",
{ selectFirst: false, autoFill: false, highlight: false, mustMatch: false });
}
var t2 = $("#input-name-filter");
if (t2.length) {
t2.autocomplete( "${h.url_for( controller='history', action='name_autocomplete_data' )}",
{ selectFirst: false, autoFill: false, highlight: false, mustMatch: false });
}
// Initialize standard, advanced search toggles.
$('.advanced-search-toggle').each( function() {
$(this).click( function() {
$("#standard-search").slideToggle('fast');
$('#advanced-search').slideToggle('fast');
return false;
});
});
}
// Initialize grid elements.
function init_grid_elements() {
// Initialize grid selection checkboxes.
$(".grid").each( function() {
var checkboxes = $(this).find("input.grid-row-select-checkbox");
var check_count = $(this).find("span.grid-selected-count");
var update_checked = function() {
check_count.text( $(checkboxes).filter(":checked").length );
};
$(checkboxes).each( function() {
$(this).change(update_checked);
});
update_checked();
});
// 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 !== undefined && href.indexOf('operation=') != -1 ) {
$(this).click( function() {
do_operation_from_href( $(this).attr('href') );
return false;
});
}
});
// Initialize ratings.
$('.community_rating_star').rating({});
// Initialize item menu operations.
make_popup_menus();
}
// Needed URLs for grid history searching.
var history_tag_autocomplete_url = "${h.url_for( controller='tag', action='tag_autocomplete_data', item_class='History' )}",
history_name_autocomplete_url = "${h.url_for( controller='history', action='name_autocomplete_data' )}";
//
// Create grid object.
//
// Operations that are async (AJAX) compatible.
var async_ops = [];
%for operation in [op for op in grid.operations if op.async_compatible]:
async_ops.push('${operation.label.lower()}');
%endfor
// Filter values for categorical filters.
var categorical_filters = {};
%for column in grid.columns:
@@ -253,367 +113,22 @@
categorical_filters['${column.key}'] = ${column.key}_filters;
%endif
%endfor
// Initialize URL args with filter arguments.
var url_args_init = ${h.to_json_string( cur_filter_dict )},
url_args = {};
// Place "f-" in front of all filter arguments.
for (arg in url_args_init) {
url_args["f-" + arg] = url_args_init[arg];
}
// Add sort argument to URL args.
url_args['sort'] = "${sort_key}";
// Add show_item_checkboxes argument to URL args.
url_args['show_item_checkboxes'] = ("${context.get('show_item_checkboxes', False)}" === "True");
// 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 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 = [];
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='javascript:void(0);'><span class='delete-search-icon' /></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;
for (var 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();
}
// 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 !== undefined && tag_value != "" ? ":" + tag_value : "");
$('#advanced-search').show('fast');
add_filter_condition("tags", tag, 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 = (new_sort.substring(0,1) == '-') ? "&uarr;" : "&darr;";
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(webapp, operation, item_ids) {
operation = operation.toLowerCase();
// Update URL args.
url_args["webapp"] = webapp;
url_args["operation"] = operation;
url_args["id"] = item_ids;
// If operation cannot be performed asynchronously, redirect to location. Otherwise do operation.
var no_async = ( async_ops[operation] === undefined || async_ops[operation] === null);
if (no_async) {
go_to_URL();
} else {
update_grid(true);
delete url_args['webapp'];
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;
var webapp = 'galaxy';
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 id parm; get id value.
id = href_parms[index].split('=')[1];
} else if (href_parms[index].indexOf('webapp') != -1) {
// Found webapp parm; get webapp value.
webapp = href_parms[index].split('=')[1];
}
}
// Do operation.
do_operation(webapp, 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 = "";
for (var 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" );
$('.loading-elt-overlay').show(); // Show overlay to indicate loading and prevent user actions.
$.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 and footer.
$('#grid-table-body').html(parsed_response_text[0]);
// FIXME: this does not work at all; what's needed is a function
// that updates page links when number of pages changes.
$('#grid-table-footer').html(parsed_response_text[1]);
// Trigger custom event to indicate grid body has changed.
$('#grid-table-body').trigger('update');
// Init grid.
init_grid_elements();
init_operation_buttons();
make_popup_menus();
// Hide loading overlay.
$('.loading-elt-overlay').hide();
// Show message if there is one.
var message = $.trim( parsed_response_text[2] );
if (message != "") {
$('#grid-message').html( message ).show();
setTimeout( function() { $('#grid-message').hide(); }, 5000);
}
}
});
}
function check_all_items() {
var chk_all = document.getElementById('check_all');
var checks = document.getElementsByTagName('input');
//var boxLength = checks.length;
var total = 0;
if ( chk_all.checked == true ) {
for ( i=0; i < checks.length; i++ ) {
if ( checks[i].name.indexOf( 'id' ) != -1) {
checks[i].checked = true;
total++;
}
}
}
else {
for ( i=0; i < checks.length; i++ ) {
if ( checks[i].name.indexOf( 'id' ) != -1) {
checks[i].checked = false
}
}
}
init_grid_elements();
}
/** Returns true if string denotes true. */
var is_true = function(s) { return _.indexOf(['True', 'true', 't'], s) !== -1; };
// Create grid.
var grid = new Grid({
url_base: '${h.url_for()}',
async: is_true('${grid.use_async}'),
async_ops: async_ops,
categorical_filters: categorical_filters,
filters: ${h.to_json_string( cur_filter_dict )},
sort_key: '${sort_key}',
show_item_checkboxes: is_true('${context.get('show_item_checkboxes', False)}'),
cur_page: ${cur_page_num},
num_pages: ${num_pages}
});
</script>
</%def>
@@ -700,7 +215,6 @@
show_item_checkboxes = True
%>
<form action="${url()}" method="post" onsubmit="return false;">
<input type="hidden" name="webapp" value="${webapp}"/>
<table id="grid-table" class="grid">
<thead id="grid-table-header">
<tr>