mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
237 lines
10 KiB
JavaScript
237 lines
10 KiB
JavaScript
//==============================================================================
|
|
/*
|
|
TODO:
|
|
?? superclass dataloader, subclass lazydataloader??
|
|
|
|
*/
|
|
//==============================================================================
|
|
/**
|
|
* Object to progressively load JSON data from a REST url, delaying some time between loading chunks
|
|
*
|
|
* Will load size amount of data every delay ms, starting at start and ending at total.
|
|
*
|
|
* NOTE: Data from ajax loading is aggregated in a list, with one element for each ajax response.
|
|
* It's up to the calling code to combine the results in a meaningful, correct way.
|
|
*
|
|
* example:
|
|
* var loader = new scatterplot.LazyDataLoader({
|
|
* //logger : console,
|
|
* url : ( apiDatasetsURL + '/' + hda.id + '?data_type=raw_data'
|
|
* + '&columns=[10,14]' ),
|
|
* total : hda.metadata_data_lines,
|
|
* size : 500,
|
|
*
|
|
* initialize : function( config ){
|
|
* // ... do some stuff
|
|
* },
|
|
*
|
|
* buildUrl : function( start, size ){
|
|
* // change the formation of start, size in query string
|
|
* return loader.url + '&' + jQuery.param({
|
|
* start_val: start,
|
|
* max_vals: size
|
|
* });
|
|
* },
|
|
* });
|
|
*
|
|
* // you can use events
|
|
* $( loader ).bind( 'error', function( event, xhr, status, error ){
|
|
* alert( loader + ' ERROR:' + status + '\n' + error );
|
|
* // bail out...
|
|
* });
|
|
* $( loader ).bind( 'loaded.new', function( event, response ){
|
|
* console.info( 'new data available:', event, response );
|
|
* // ... do stuff with new data
|
|
* });
|
|
* $( loader ).bind( 'complete', function( event, allDataArray, total ){
|
|
* console.info( 'final load complete:', event, allDataArray, total );
|
|
* // ... do stuff with all data
|
|
* });
|
|
*
|
|
* // ...or use a callback called when all data is loaded
|
|
* loader.load( function( dataArray ){ console.debug( 'FINISHED!', x, y, z ); } );
|
|
*/
|
|
function LazyDataLoader( config ){
|
|
// for now assume:
|
|
// get, async, and params sent via url query string
|
|
// we want json
|
|
// we know the size of the data on the server beforehand
|
|
var loader = this,
|
|
// events to trigger when new or all data has been loaded
|
|
// new batches of data (including last). Will be sent: the ajax response data, start value, and size
|
|
LOADED_NEW_EVENT = 'loaded.new',
|
|
// all data has been loaded: the final loader's data array and the total
|
|
LOADED_ALL_EVENT = 'complete';
|
|
// error from ajax
|
|
ERROR_EVENT = 'error';
|
|
|
|
jQuery.extend( loader, LoggableMixin );
|
|
jQuery.extend( loader, {
|
|
|
|
//NOTE: the next two need to be sent in config (required)
|
|
// total size of data on server
|
|
total : undefined,
|
|
// url of service to get the data
|
|
url : undefined,
|
|
|
|
// holds the interval id for the current load delay
|
|
currentIntervalId : undefined,
|
|
|
|
// each load call will add an element to this array
|
|
// it's the responsibility of the code using this to combine them properly
|
|
data : [],
|
|
// ms btwn recursive loads
|
|
delay : 4000,
|
|
// starting line, element, whatever
|
|
start : 0,
|
|
// size to fetch per load
|
|
size : 4000,
|
|
|
|
// loader init func: extends loader with config and calls config.init if there
|
|
//@param {object} config : object containing variables to override (or additional)
|
|
initialize : function( config ){
|
|
jQuery.extend( loader, config );
|
|
|
|
// call the custom initialize function if any
|
|
// only dangerous if the user tries LazyDataLoader.prototype.init
|
|
if( config.hasOwnProperty( 'initialize' ) ){
|
|
config.initialize.call( loader, config );
|
|
}
|
|
this.log( this + ' initialized:', loader );
|
|
},
|
|
|
|
// returns query string formatted start and size (for the next fetch) appended to the loader.url
|
|
//OVERRIDE: to change how params are passed, param names, etc.
|
|
//@param {int} start : the line/row/datum indicating where in the dataset the next load begins
|
|
//@param {int} size : the number of lines/rows/data to get on the next load
|
|
buildUrl : function( start, size ){
|
|
// currently VERY SPECIFIC to using data_providers.py start_val, max_vals params
|
|
return this.url + '&' + jQuery.param({
|
|
start_val: start,
|
|
max_vals: size
|
|
});
|
|
},
|
|
|
|
//OVERRIDE: to handle ajax errors differently
|
|
ajaxErrorFn : function( xhr, status, error ){
|
|
console.error( 'ERROR fetching data:', error );
|
|
},
|
|
|
|
// converters passed to the jQuery ajax call for data type parsing
|
|
//OVERRIDE: to provide custom parsing
|
|
converters : {
|
|
'* text' : window.String,
|
|
'text html' : true,
|
|
'text xml' : jQuery.parseXML,
|
|
|
|
// add NaN, inf, -inf handling to jquery json parser (by default)
|
|
'text json' : function( json ){
|
|
json = json.replace( /NaN/g, 'null' );
|
|
json = json.replace( /-Infinity/g, 'null' );
|
|
json = json.replace( /Infinity/g, 'null' );
|
|
return jQuery.parseJSON( json );
|
|
}
|
|
},
|
|
|
|
// interface to begin load (and first recursive call)
|
|
//@param {Function} callback : function to execute when all data is loaded. callback is passed loader.data
|
|
load : function( callback ){
|
|
this.log( this + '.load' );
|
|
|
|
// ensure necessary stuff
|
|
if( !loader.url ){ throw( loader + ' requires a url' ); }
|
|
|
|
if( this.total === null ){
|
|
this.log( '\t total is null (will load all)' );
|
|
} else {
|
|
this.log( '\t total:', this.total );
|
|
}
|
|
//if( !loader.total ){ throw( loader + ' requires a total (total size of the data)' ); }
|
|
|
|
//FIRST RECURSION: start
|
|
var startingSize = loader.size;
|
|
if( ( loader.total !== null )
|
|
&& ( loader.total < loader.size ) ){
|
|
startingSize = loader.total;
|
|
}
|
|
loader.log( loader + '\t beginning recursion' );
|
|
loadHelper( loader.start, startingSize );
|
|
|
|
//FIRST, SUBSEQUENT RECURSION function
|
|
function loadHelper( start, size ){
|
|
loader.log( loader + '.loadHelper, start:', start, 'size:', size );
|
|
var url = loader.buildUrl( start, size );
|
|
loader.log( '\t url:', url );
|
|
|
|
jQuery.ajax({
|
|
url : loader.buildUrl( start, size ),
|
|
converters : loader.converters,
|
|
dataType : 'json',
|
|
|
|
error : function( xhr, status, error ){
|
|
loader.log( '\t ajax error, status:', status, 'error:', error );
|
|
if( loader.currentIntervalId ){
|
|
clearInterval( loader.currentIntervalId );
|
|
}
|
|
$( loader ).trigger( ERROR_EVENT, [ status, error ] );
|
|
loader.ajaxErrorFn( xhr, status, error );
|
|
},
|
|
|
|
success : function( response ){
|
|
loader.log( '\t ajax success, response:', response, 'next:', next, 'remainder:', remainder );
|
|
|
|
if( response !== null ){
|
|
// store the response as is in a new element
|
|
//TODO:?? store start, size as well?
|
|
loader.data.push( response );
|
|
|
|
//TODO: these might not be the best way to split this up
|
|
// fire the first load event (if this is the first batch) AND partial
|
|
$( loader ).trigger( LOADED_NEW_EVENT, [ response, start, size ] );
|
|
|
|
//RECURSION:
|
|
var next = start + size,
|
|
remainder = loader.size;
|
|
|
|
if( loader.total !== null ){
|
|
remainder = Math.min( loader.total - next, loader.size );
|
|
}
|
|
loader.log( '\t next recursion, start:', next, 'size:', remainder );
|
|
|
|
// if we haven't gotten everything yet, so set up for next recursive call and set the timer
|
|
if( loader.total === null || remainder > 0 ){
|
|
loader.currentIntervalId = setTimeout(
|
|
function(){ loadHelper( next, remainder ); },
|
|
loader.delay
|
|
);
|
|
loader.log( '\t currentIntervalId:', loader.currentIntervalId );
|
|
|
|
// otherwise (base-case), don't do anything
|
|
} else {
|
|
loadFinished();
|
|
}
|
|
|
|
} else { //response === null --> base-case, server sez nuthin left
|
|
loadFinished();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
//HANDLE BASE-CASE, LAST RECURSION
|
|
function loadFinished(){
|
|
loader.log( loader + '.loadHelper, has finished:', loader.data );
|
|
$( loader ).trigger( LOADED_ALL_EVENT, [ loader.data, loader.total ] );
|
|
if( callback ){ callback( loader.data ); }
|
|
}
|
|
},
|
|
|
|
toString : function(){ return 'LazyDataLoader'; }
|
|
});
|
|
|
|
loader.initialize( config );
|
|
return loader;
|
|
}
|
|
|
|
//==============================================================================
|