galaxy-commits
Threads by month
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2010 -----
- December
- November
- October
- September
- August
- July
- June
- May
March 2014
- 1 participants
- 170 discussions
3 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/d847b6985330/
Changeset: d847b6985330
User: jmchilton
Date: 2014-02-20 16:37:17
Summary: Extend SecurityHelper to allow generation of different "kind"s of keys.
There has been talk about including the object's class into the hash when generating keys so the ids are not overlapping between model types. This change could allow that, but my immediate desire is to just to create single-purpose per job keys to allow external job running code to access files on behalf of a given job.
Affected #: 1 file
diff -r 9c673e0ea8919fea6941f24fdf6936686840bb71 -r d847b69853302d9a127c31c612dd19cc7ffa9f61 lib/galaxy/web/security/__init__.py
--- a/lib/galaxy/web/security/__init__.py
+++ b/lib/galaxy/web/security/__init__.py
@@ -1,3 +1,4 @@
+import collections
import os, os.path, logging
import pkg_resources
@@ -37,27 +38,32 @@
self.id_secret = config['id_secret']
self.id_cipher = Blowfish.new( self.id_secret )
- def encode_id( self, obj_id ):
+ per_kind_id_secret_base = config.get( 'per_kind_id_secret_base', self.id_secret )
+ self.id_ciphers_for_key = _cipher_cache( per_kind_id_secret_base )
+
+ def encode_id( self, obj_id, kind=None ):
+ id_cipher = self.__id_cipher( kind )
# Convert to string
s = str( obj_id )
# Pad to a multiple of 8 with leading "!"
s = ( "!" * ( 8 - len(s) % 8 ) ) + s
# Encrypt
- return self.id_cipher.encrypt( s ).encode( 'hex' )
+ return id_cipher.encrypt( s ).encode( 'hex' )
- def encode_dict_ids( self, a_dict ):
+ def encode_dict_ids( self, a_dict, kind=None ):
"""
Encode all ids in dictionary. Ids are identified by (a) an 'id' key or
(b) a key that ends with '_id'
"""
for key, val in a_dict.items():
if key == 'id' or key.endswith('_id'):
- a_dict[ key ] = self.encode_id( val )
+ a_dict[ key ] = self.encode_id( val, kind=kind )
return a_dict
- def decode_id( self, obj_id ):
- return int( self.id_cipher.decrypt( obj_id.decode( 'hex' ) ).lstrip( "!" ) )
+ def decode_id( self, obj_id, kind=None ):
+ id_cipher = self.__id_cipher( kind )
+ return int( id_cipher.decrypt( obj_id.decode( 'hex' ) ).lstrip( "!" ) )
def encode_guid( self, session_key ):
# Session keys are strings
@@ -73,3 +79,19 @@
def get_new_guid( self ):
# Generate a unique, high entropy 128 bit random number
return get_random_bytes( 16 )
+
+ def __id_cipher( self, kind ):
+ if not kind:
+ id_cipher = self.id_cipher
+ else:
+ id_cipher = self.id_ciphers_for_key[ kind ]
+ return id_cipher
+
+
+class _cipher_cache( collections.defaultdict ):
+
+ def __init__( self, secret_base ):
+ self.secret_base = secret_base
+
+ def __missing__( self, key ):
+ return Blowfish.new( self.secret_base + "__" + key )
https://bitbucket.org/galaxy/galaxy-central/commits/58acdb670e4b/
Changeset: 58acdb670e4b
User: jmchilton
Date: 2014-02-20 16:37:17
Summary: Implement API allowing access to a job's files.
Provides a mechanism for remote job execution mechanisms to read and write files on behalf of an "active" jobs.
For a variety of reasons simply providing access to datasets is insuccifient for this - there are working directory files needed for metadata calculation, inputs and outputs may be of intermediate form (task split files for instance), tool files, location files, etc.... Therefore this API endpoint provides access to a jobs view of these files.
Some attempt is made to verify the paths that are written to are valid for the supplied job (they either correspond to the output datasets of the job or the working directory of the job). Authorizing suchs paths for reading is much more difficult and left undone due to the unstructure and arbitrary nature of .loc files.
To implement this securely and with minimal configuration required - single-purpose "job_key"s are generated to authenticate these API calls. Each key allow usage of the API only for a single, active job.
Update LWR job runner to be able to leverage this API to stage files remotely via HTTP for "remote_transfer" actions (added recently to the LWR client).
Affected #: 6 files
diff -r d847b69853302d9a127c31c612dd19cc7ffa9f61 -r 58acdb670e4b8ea9a35d3b19aaab1e66c5b75009 lib/galaxy/jobs/runners/lwr.py
--- a/lib/galaxy/jobs/runners/lwr.py
+++ b/lib/galaxy/jobs/runners/lwr.py
@@ -28,6 +28,10 @@
NO_REMOTE_GALAXY_FOR_METADATA_MESSAGE = "LWR misconfiguration - LWR client configured to set metadata remotely, but remote LWR isn't properly configured with a galaxy_home directory."
NO_REMOTE_DATATYPES_CONFIG = "LWR client is configured to use remote datatypes configuration when setting metadata externally, but LWR is not configured with this information. Defaulting to datatypes_conf.xml."
+# Is there a good way to infer some default for this? Can only use
+# url_for from web threads. https://gist.github.com/jmchilton/9098762
+DEFAULT_GALAXY_URL = "http://localhost:8080"
+
class LwrJobRunner( AsynchronousJobRunner ):
"""
@@ -35,13 +39,14 @@
"""
runner_name = "LWRRunner"
- def __init__( self, app, nworkers, transport=None, cache=None, url=None ):
+ def __init__( self, app, nworkers, transport=None, cache=None, url=None, galaxy_url=DEFAULT_GALAXY_URL ):
"""Start the job runner """
super( LwrJobRunner, self ).__init__( app, nworkers )
self.async_status_updates = dict()
self._init_monitor_thread()
self._init_worker_threads()
client_manager_kwargs = {'transport_type': transport, 'cache': string_as_bool_or_none(cache), "url": url}
+ self.galaxy_url = galaxy_url
self.client_manager = build_client_manager(**client_manager_kwargs)
def url_to_destination( self, url ):
@@ -224,7 +229,21 @@
return self.get_client( job_destination_params, job_id )
def get_client( self, job_destination_params, job_id ):
- return self.client_manager.get_client( job_destination_params, str( job_id ) )
+ # Cannot use url_for outside of web thread.
+ #files_endpoint = url_for( controller="job_files", job_id=encoded_job_id )
+
+ encoded_job_id = self.app.security.encode_id(job_id)
+ job_key = self.app.security.encode_id( job_id, kind="jobs_files" )
+ files_endpoint = "%s/api/jobs/%s/files?job_key=%s" % (
+ self.galaxy_url,
+ encoded_job_id,
+ job_key
+ )
+ get_client_kwds = dict(
+ job_id=str( job_id ),
+ files_endpoint=files_endpoint,
+ )
+ return self.client_manager.get_client( job_destination_params, **get_client_kwds )
def finish_job( self, job_state ):
stderr = stdout = ''
diff -r d847b69853302d9a127c31c612dd19cc7ffa9f61 -r 58acdb670e4b8ea9a35d3b19aaab1e66c5b75009 lib/galaxy/model/__init__.py
--- a/lib/galaxy/model/__init__.py
+++ b/lib/galaxy/model/__init__.py
@@ -266,6 +266,16 @@
self.handler = None
self.exit_code = None
+ @property
+ def finished( self ):
+ states = self.states
+ return self.state in [
+ states.OK,
+ states.ERROR,
+ states.DELETED,
+ states.DELETED_NEW,
+ ]
+
# TODO: Add accessors for members defined in SQL Alchemy for the Job table and
# for the mapper defined to the Job table.
def get_external_output_metadata( self ):
diff -r d847b69853302d9a127c31c612dd19cc7ffa9f61 -r 58acdb670e4b8ea9a35d3b19aaab1e66c5b75009 lib/galaxy/web/__init__.py
--- a/lib/galaxy/web/__init__.py
+++ b/lib/galaxy/web/__init__.py
@@ -20,3 +20,4 @@
from framework import _future_expose_api
from framework import _future_expose_api_anonymous
from framework import _future_expose_api_raw
+from framework import _future_expose_api_raw_anonymous
diff -r d847b69853302d9a127c31c612dd19cc7ffa9f61 -r 58acdb670e4b8ea9a35d3b19aaab1e66c5b75009 lib/galaxy/web/framework/__init__.py
--- a/lib/galaxy/web/framework/__init__.py
+++ b/lib/galaxy/web/framework/__init__.py
@@ -278,6 +278,10 @@
return _future_expose_api( func, to_json=False, user_required=True )
+def _future_expose_api_raw_anonymous( func ):
+ return _future_expose_api( func, to_json=False, user_required=False )
+
+
# TODO: rename as expose_api and make default.
def _future_expose_api( func, to_json=True, user_required=True ):
"""
diff -r d847b69853302d9a127c31c612dd19cc7ffa9f61 -r 58acdb670e4b8ea9a35d3b19aaab1e66c5b75009 lib/galaxy/webapps/galaxy/api/job_files.py
--- /dev/null
+++ b/lib/galaxy/webapps/galaxy/api/job_files.py
@@ -0,0 +1,142 @@
+""" API for asynchronous job running mechanisms can use to fetch or put files
+related to running and queued jobs.
+"""
+import os
+import shutil
+
+from galaxy import exceptions
+from galaxy import util
+from galaxy import model
+from galaxy.web.base.controller import BaseAPIController
+from galaxy.web import _future_expose_api_anonymous as expose_api_anonymous
+from galaxy.web import _future_expose_api_raw_anonymous as expose_api_raw_anonymous
+
+
+import logging
+log = logging.getLogger( __name__ )
+
+
+class JobFilesAPIController( BaseAPIController ):
+ """ This job files controller allows remote job running mechanisms to
+ read and modify the current state of files for queued and running jobs.
+ It is certainly not meant to represent part of Galaxy's stable, user
+ facing API.
+
+ Furthermore, even if a user key corresponds to the user running the job,
+ it should not be accepted for authorization - this API allows access to
+ low-level unfiltered files and such authorization would break Galaxy's
+ security model for tool execution.
+ """
+
+ @expose_api_raw_anonymous
+ def index( self, trans, job_id, **kwargs ):
+ """
+ index( self, trans, job_id, **kwargs )
+ * GET /api/jobs/{job_id}/files
+ Get a file required to staging a job (proper datasets, extra inputs,
+ task-split inputs, working directory files).
+
+ :type job_id: str
+ :param job_id: encoded id string of the job
+ :type path: str
+ :param path: Path to file.
+ :type job_key: str
+ :param job_key: A key used to authenticate this request as acting on
+ behalf or a job runner for the specified job.
+ ..note:
+ This API method is intended only for consumption by job runners,
+ not end users.
+
+ :rtype: binary
+ :returns: contents of file
+ """
+ self.__authorize_job_access( trans, job_id, **kwargs )
+ path = kwargs.get("path", None)
+ return open(path, 'rb')
+
+ @expose_api_anonymous
+ def create( self, trans, job_id, payload, **kwargs ):
+ """
+ create( self, trans, job_id, payload, **kwargs )
+ * POST /api/jobs/{job_id}/files
+ Populate an output file (formal dataset, task split part, working
+ directory file (such as those related to metadata)). This should be
+ a multipart post with a 'file' parameter containing the contents of
+ the actual file to create.
+
+ :type job_id: str
+ :param job_id: encoded id string of the job
+ :type payload: dict
+ :param payload: dictionary structure containing::
+ 'job_key' = Key authenticating
+ 'path' = Path to file to create.
+
+ ..note:
+ This API method is intended only for consumption by job runners,
+ not end users.
+
+ :rtype: dict
+ :returns: an okay message
+ """
+ job = self.__authorize_job_access( trans, job_id, **payload )
+ path = payload.get( "path" )
+ self.__check_job_can_write_to_path( trans, job, path )
+
+ # Is this writing an unneeded file? Should this just copy in Python?
+ input_file = payload.get( "file", payload.get( "__file", None ) ).file
+ try:
+ shutil.copyfile( input_file.name, path )
+ finally:
+ input_file.close()
+ return {"message": "ok"}
+
+ def __authorize_job_access(self, trans, encoded_job_id, **kwargs):
+ for key in [ "path", "job_key" ]:
+ if key not in kwargs:
+ error_message = "Job files action requires a valid '%s'." % key
+ raise exceptions.ObjectAttributeMissingException( error_message )
+
+ job_id = trans.security.decode_id( encoded_job_id )
+ job_key = trans.security.encode_id( job_id, kind="jobs_files" )
+ if not util.safe_str_cmp( kwargs[ "job_key" ], job_key ):
+ raise exceptions.ItemAccessibilityException("Invalid job_key supplied.")
+
+ # Verify job is active. Don't update the contents of complete jobs.
+ job = trans.sa_session.query( model.Job ).get( job_id )
+ if job.finished:
+ error_message = "Attempting to read or modify the files of a job that has already completed."
+ raise exceptions.MessageException( error_message )
+ return job
+
+ def __check_job_can_write_to_path( self, trans, job, path ):
+ """ Verify an idealized job runner should actually be able to write to
+ the specified path - it must be a dataset output, a dataset "extra
+ file", or a some place in the working directory of this job.
+
+ Would like similar checks for reading the unstructured nature of loc
+ files make this very difficult. (See abandoned work here
+ https://gist.github.com/jmchilton/9103619.)
+ """
+ in_work_dir = self.__in_working_directory( job, path, trans.app )
+ if not in_work_dir and not self.__is_output_dataset_path( job, path ):
+ raise exceptions.ItemAccessibilityException("Job is not authorized to write to supplied path.")
+
+ def __is_output_dataset_path( self, job, path ):
+ """ Check if is an output path for this job or a file in the an
+ output's extra files path.
+ """
+ da_lists = [ job.output_datasets, job.output_library_datasets ]
+ for da_list in da_lists:
+ for job_dataset_association in da_list:
+ dataset = job_dataset_association.dataset
+ if not dataset:
+ continue
+ if os.path.abspath( dataset.file_name ) == os.path.abspath( path ):
+ return True
+ elif util.in_directory( path, dataset.extra_files_path ):
+ return True
+ return False
+
+ def __in_working_directory( self, job, path, app ):
+ working_directory = app.object_store.get_filename(job, base_dir='job_work', dir_only=True, extra_dir=str(job.id))
+ return util.in_directory( path, working_directory )
diff -r d847b69853302d9a127c31c612dd19cc7ffa9f61 -r 58acdb670e4b8ea9a35d3b19aaab1e66c5b75009 lib/galaxy/webapps/galaxy/buildapp.py
--- a/lib/galaxy/webapps/galaxy/buildapp.py
+++ b/lib/galaxy/webapps/galaxy/buildapp.py
@@ -233,12 +233,20 @@
'permissions',
path_prefix='/api/libraries/:library_id',
parent_resources=dict( member_name='library', collection_name='libraries' ) )
-
- webapp.mapper.resource( 'job',
- 'jobs',
+
+ webapp.mapper.resource( 'job',
+ 'jobs',
path_prefix='/api' )
webapp.mapper.connect( 'job_search', '/api/jobs/search', controller='jobs', action='search', conditions=dict( method=['POST'] ) )
+ # Job files controllers. Only for consumption by remote job runners.
+ webapp.mapper.resource( 'file',
+ 'files',
+ controller="job_files",
+ name_prefix="job_",
+ path_prefix='/api/jobs/:job_id',
+ parent_resources=dict( member_name="job", collection_name="jobs")
+ )
_add_item_extended_metadata_controller( webapp,
name_prefix="library_dataset_",
https://bitbucket.org/galaxy/galaxy-central/commits/2727f1617161/
Changeset: 2727f1617161
User: jmchilton
Date: 2014-03-17 01:54:02
Summary: Merged in jmchilton/galaxy-central-fork-1 (pull request #327)
Implement job files API.
Affected #: 7 files
diff -r 6495ceccc87d467f73d76e21f801acc78934fc8f -r 2727f16171610e0e27420d780e9a5ed56f1908f5 lib/galaxy/jobs/runners/lwr.py
--- a/lib/galaxy/jobs/runners/lwr.py
+++ b/lib/galaxy/jobs/runners/lwr.py
@@ -28,6 +28,10 @@
NO_REMOTE_GALAXY_FOR_METADATA_MESSAGE = "LWR misconfiguration - LWR client configured to set metadata remotely, but remote LWR isn't properly configured with a galaxy_home directory."
NO_REMOTE_DATATYPES_CONFIG = "LWR client is configured to use remote datatypes configuration when setting metadata externally, but LWR is not configured with this information. Defaulting to datatypes_conf.xml."
+# Is there a good way to infer some default for this? Can only use
+# url_for from web threads. https://gist.github.com/jmchilton/9098762
+DEFAULT_GALAXY_URL = "http://localhost:8080"
+
class LwrJobRunner( AsynchronousJobRunner ):
"""
@@ -35,13 +39,14 @@
"""
runner_name = "LWRRunner"
- def __init__( self, app, nworkers, transport=None, cache=None, url=None ):
+ def __init__( self, app, nworkers, transport=None, cache=None, url=None, galaxy_url=DEFAULT_GALAXY_URL ):
"""Start the job runner """
super( LwrJobRunner, self ).__init__( app, nworkers )
self.async_status_updates = dict()
self._init_monitor_thread()
self._init_worker_threads()
client_manager_kwargs = {'transport_type': transport, 'cache': string_as_bool_or_none(cache), "url": url}
+ self.galaxy_url = galaxy_url
self.client_manager = build_client_manager(**client_manager_kwargs)
def url_to_destination( self, url ):
@@ -224,7 +229,21 @@
return self.get_client( job_destination_params, job_id )
def get_client( self, job_destination_params, job_id ):
- return self.client_manager.get_client( job_destination_params, str( job_id ) )
+ # Cannot use url_for outside of web thread.
+ #files_endpoint = url_for( controller="job_files", job_id=encoded_job_id )
+
+ encoded_job_id = self.app.security.encode_id(job_id)
+ job_key = self.app.security.encode_id( job_id, kind="jobs_files" )
+ files_endpoint = "%s/api/jobs/%s/files?job_key=%s" % (
+ self.galaxy_url,
+ encoded_job_id,
+ job_key
+ )
+ get_client_kwds = dict(
+ job_id=str( job_id ),
+ files_endpoint=files_endpoint,
+ )
+ return self.client_manager.get_client( job_destination_params, **get_client_kwds )
def finish_job( self, job_state ):
stderr = stdout = ''
diff -r 6495ceccc87d467f73d76e21f801acc78934fc8f -r 2727f16171610e0e27420d780e9a5ed56f1908f5 lib/galaxy/model/__init__.py
--- a/lib/galaxy/model/__init__.py
+++ b/lib/galaxy/model/__init__.py
@@ -268,6 +268,16 @@
self.handler = None
self.exit_code = None
+ @property
+ def finished( self ):
+ states = self.states
+ return self.state in [
+ states.OK,
+ states.ERROR,
+ states.DELETED,
+ states.DELETED_NEW,
+ ]
+
# TODO: Add accessors for members defined in SQL Alchemy for the Job table and
# for the mapper defined to the Job table.
def get_external_output_metadata( self ):
diff -r 6495ceccc87d467f73d76e21f801acc78934fc8f -r 2727f16171610e0e27420d780e9a5ed56f1908f5 lib/galaxy/web/__init__.py
--- a/lib/galaxy/web/__init__.py
+++ b/lib/galaxy/web/__init__.py
@@ -20,3 +20,4 @@
from framework import _future_expose_api
from framework import _future_expose_api_anonymous
from framework import _future_expose_api_raw
+from framework import _future_expose_api_raw_anonymous
diff -r 6495ceccc87d467f73d76e21f801acc78934fc8f -r 2727f16171610e0e27420d780e9a5ed56f1908f5 lib/galaxy/web/framework/__init__.py
--- a/lib/galaxy/web/framework/__init__.py
+++ b/lib/galaxy/web/framework/__init__.py
@@ -278,6 +278,10 @@
return _future_expose_api( func, to_json=False, user_required=True )
+def _future_expose_api_raw_anonymous( func ):
+ return _future_expose_api( func, to_json=False, user_required=False )
+
+
# TODO: rename as expose_api and make default.
def _future_expose_api( func, to_json=True, user_required=True ):
"""
diff -r 6495ceccc87d467f73d76e21f801acc78934fc8f -r 2727f16171610e0e27420d780e9a5ed56f1908f5 lib/galaxy/web/security/__init__.py
--- a/lib/galaxy/web/security/__init__.py
+++ b/lib/galaxy/web/security/__init__.py
@@ -1,3 +1,4 @@
+import collections
import os, os.path, logging
import pkg_resources
@@ -37,27 +38,32 @@
self.id_secret = config['id_secret']
self.id_cipher = Blowfish.new( self.id_secret )
- def encode_id( self, obj_id ):
+ per_kind_id_secret_base = config.get( 'per_kind_id_secret_base', self.id_secret )
+ self.id_ciphers_for_key = _cipher_cache( per_kind_id_secret_base )
+
+ def encode_id( self, obj_id, kind=None ):
+ id_cipher = self.__id_cipher( kind )
# Convert to string
s = str( obj_id )
# Pad to a multiple of 8 with leading "!"
s = ( "!" * ( 8 - len(s) % 8 ) ) + s
# Encrypt
- return self.id_cipher.encrypt( s ).encode( 'hex' )
+ return id_cipher.encrypt( s ).encode( 'hex' )
- def encode_dict_ids( self, a_dict ):
+ def encode_dict_ids( self, a_dict, kind=None ):
"""
Encode all ids in dictionary. Ids are identified by (a) an 'id' key or
(b) a key that ends with '_id'
"""
for key, val in a_dict.items():
if key == 'id' or key.endswith('_id'):
- a_dict[ key ] = self.encode_id( val )
+ a_dict[ key ] = self.encode_id( val, kind=kind )
return a_dict
- def decode_id( self, obj_id ):
- return int( self.id_cipher.decrypt( obj_id.decode( 'hex' ) ).lstrip( "!" ) )
+ def decode_id( self, obj_id, kind=None ):
+ id_cipher = self.__id_cipher( kind )
+ return int( id_cipher.decrypt( obj_id.decode( 'hex' ) ).lstrip( "!" ) )
def encode_guid( self, session_key ):
# Session keys are strings
@@ -73,3 +79,19 @@
def get_new_guid( self ):
# Generate a unique, high entropy 128 bit random number
return get_random_bytes( 16 )
+
+ def __id_cipher( self, kind ):
+ if not kind:
+ id_cipher = self.id_cipher
+ else:
+ id_cipher = self.id_ciphers_for_key[ kind ]
+ return id_cipher
+
+
+class _cipher_cache( collections.defaultdict ):
+
+ def __init__( self, secret_base ):
+ self.secret_base = secret_base
+
+ def __missing__( self, key ):
+ return Blowfish.new( self.secret_base + "__" + key )
diff -r 6495ceccc87d467f73d76e21f801acc78934fc8f -r 2727f16171610e0e27420d780e9a5ed56f1908f5 lib/galaxy/webapps/galaxy/api/job_files.py
--- /dev/null
+++ b/lib/galaxy/webapps/galaxy/api/job_files.py
@@ -0,0 +1,142 @@
+""" API for asynchronous job running mechanisms can use to fetch or put files
+related to running and queued jobs.
+"""
+import os
+import shutil
+
+from galaxy import exceptions
+from galaxy import util
+from galaxy import model
+from galaxy.web.base.controller import BaseAPIController
+from galaxy.web import _future_expose_api_anonymous as expose_api_anonymous
+from galaxy.web import _future_expose_api_raw_anonymous as expose_api_raw_anonymous
+
+
+import logging
+log = logging.getLogger( __name__ )
+
+
+class JobFilesAPIController( BaseAPIController ):
+ """ This job files controller allows remote job running mechanisms to
+ read and modify the current state of files for queued and running jobs.
+ It is certainly not meant to represent part of Galaxy's stable, user
+ facing API.
+
+ Furthermore, even if a user key corresponds to the user running the job,
+ it should not be accepted for authorization - this API allows access to
+ low-level unfiltered files and such authorization would break Galaxy's
+ security model for tool execution.
+ """
+
+ @expose_api_raw_anonymous
+ def index( self, trans, job_id, **kwargs ):
+ """
+ index( self, trans, job_id, **kwargs )
+ * GET /api/jobs/{job_id}/files
+ Get a file required to staging a job (proper datasets, extra inputs,
+ task-split inputs, working directory files).
+
+ :type job_id: str
+ :param job_id: encoded id string of the job
+ :type path: str
+ :param path: Path to file.
+ :type job_key: str
+ :param job_key: A key used to authenticate this request as acting on
+ behalf or a job runner for the specified job.
+ ..note:
+ This API method is intended only for consumption by job runners,
+ not end users.
+
+ :rtype: binary
+ :returns: contents of file
+ """
+ self.__authorize_job_access( trans, job_id, **kwargs )
+ path = kwargs.get("path", None)
+ return open(path, 'rb')
+
+ @expose_api_anonymous
+ def create( self, trans, job_id, payload, **kwargs ):
+ """
+ create( self, trans, job_id, payload, **kwargs )
+ * POST /api/jobs/{job_id}/files
+ Populate an output file (formal dataset, task split part, working
+ directory file (such as those related to metadata)). This should be
+ a multipart post with a 'file' parameter containing the contents of
+ the actual file to create.
+
+ :type job_id: str
+ :param job_id: encoded id string of the job
+ :type payload: dict
+ :param payload: dictionary structure containing::
+ 'job_key' = Key authenticating
+ 'path' = Path to file to create.
+
+ ..note:
+ This API method is intended only for consumption by job runners,
+ not end users.
+
+ :rtype: dict
+ :returns: an okay message
+ """
+ job = self.__authorize_job_access( trans, job_id, **payload )
+ path = payload.get( "path" )
+ self.__check_job_can_write_to_path( trans, job, path )
+
+ # Is this writing an unneeded file? Should this just copy in Python?
+ input_file = payload.get( "file", payload.get( "__file", None ) ).file
+ try:
+ shutil.copyfile( input_file.name, path )
+ finally:
+ input_file.close()
+ return {"message": "ok"}
+
+ def __authorize_job_access(self, trans, encoded_job_id, **kwargs):
+ for key in [ "path", "job_key" ]:
+ if key not in kwargs:
+ error_message = "Job files action requires a valid '%s'." % key
+ raise exceptions.ObjectAttributeMissingException( error_message )
+
+ job_id = trans.security.decode_id( encoded_job_id )
+ job_key = trans.security.encode_id( job_id, kind="jobs_files" )
+ if not util.safe_str_cmp( kwargs[ "job_key" ], job_key ):
+ raise exceptions.ItemAccessibilityException("Invalid job_key supplied.")
+
+ # Verify job is active. Don't update the contents of complete jobs.
+ job = trans.sa_session.query( model.Job ).get( job_id )
+ if job.finished:
+ error_message = "Attempting to read or modify the files of a job that has already completed."
+ raise exceptions.MessageException( error_message )
+ return job
+
+ def __check_job_can_write_to_path( self, trans, job, path ):
+ """ Verify an idealized job runner should actually be able to write to
+ the specified path - it must be a dataset output, a dataset "extra
+ file", or a some place in the working directory of this job.
+
+ Would like similar checks for reading the unstructured nature of loc
+ files make this very difficult. (See abandoned work here
+ https://gist.github.com/jmchilton/9103619.)
+ """
+ in_work_dir = self.__in_working_directory( job, path, trans.app )
+ if not in_work_dir and not self.__is_output_dataset_path( job, path ):
+ raise exceptions.ItemAccessibilityException("Job is not authorized to write to supplied path.")
+
+ def __is_output_dataset_path( self, job, path ):
+ """ Check if is an output path for this job or a file in the an
+ output's extra files path.
+ """
+ da_lists = [ job.output_datasets, job.output_library_datasets ]
+ for da_list in da_lists:
+ for job_dataset_association in da_list:
+ dataset = job_dataset_association.dataset
+ if not dataset:
+ continue
+ if os.path.abspath( dataset.file_name ) == os.path.abspath( path ):
+ return True
+ elif util.in_directory( path, dataset.extra_files_path ):
+ return True
+ return False
+
+ def __in_working_directory( self, job, path, app ):
+ working_directory = app.object_store.get_filename(job, base_dir='job_work', dir_only=True, extra_dir=str(job.id))
+ return util.in_directory( path, working_directory )
diff -r 6495ceccc87d467f73d76e21f801acc78934fc8f -r 2727f16171610e0e27420d780e9a5ed56f1908f5 lib/galaxy/webapps/galaxy/buildapp.py
--- a/lib/galaxy/webapps/galaxy/buildapp.py
+++ b/lib/galaxy/webapps/galaxy/buildapp.py
@@ -261,6 +261,14 @@
path_prefix='/api' )
webapp.mapper.connect( 'job_search', '/api/jobs/search', controller='jobs', action='search', conditions=dict( method=['POST'] ) )
+ # Job files controllers. Only for consumption by remote job runners.
+ webapp.mapper.resource( 'file',
+ 'files',
+ controller="job_files",
+ name_prefix="job_",
+ path_prefix='/api/jobs/:job_id',
+ parent_resources=dict( member_name="job", collection_name="jobs")
+ )
_add_item_extended_metadata_controller( webapp,
name_prefix="library_dataset_",
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: jeremy goecks: Trackster: fix bug in edff37d that prevented naming and saving new track visualization.
by commits-noreply@bitbucket.org 15 Mar '14
by commits-noreply@bitbucket.org 15 Mar '14
15 Mar '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/6495ceccc87d/
Changeset: 6495ceccc87d
User: jeremy goecks
Date: 2014-03-16 05:06:07
Summary: Trackster: fix bug in edff37d that prevented naming and saving new track visualization.
Affected #: 1 file
diff -r 6f0f77a940fc4966ce5534a06866ae57899dd867 -r 6495ceccc87d467f73d76e21f801acc78934fc8f static/scripts/viz/trackster/tracks.js
--- a/static/scripts/viz/trackster/tracks.js
+++ b/static/scripts/viz/trackster/tracks.js
@@ -894,12 +894,13 @@
// Define track configuration
this.config = config_mod.ConfigSettingCollection.from_models_and_saved_values( [
+ { key: 'name', label: 'Name', type: 'text', default_value: '' },
{ key: 'a_color', label: 'A Color', type: 'color', default_value: "#FF0000" },
{ key: 'c_color', label: 'C Color', type: 'color', default_value: "#00FF00" },
{ key: 'g_color', label: 'G Color', type: 'color', default_value: "#0000FF" },
{ key: 't_color', label: 'T Color', type: 'color', default_value: "#FF00FF" },
{ key: 'n_color', label: 'N Color', type: 'color', default_value: "#AAAAAA" }
- ], obj_dict.prefs);
+ ], { name: obj_dict.name });
},
render: function() {
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: jeremy goecks: Config.js: make copy of model to avoid by reference errors. Remove debug statements.
by commits-noreply@bitbucket.org 15 Mar '14
by commits-noreply@bitbucket.org 15 Mar '14
15 Mar '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/6f0f77a940fc/
Changeset: 6f0f77a940fc
User: jeremy goecks
Date: 2014-03-15 18:26:03
Summary: Config.js: make copy of model to avoid by reference errors. Remove debug statements.
Affected #: 2 files
diff -r 9fd3b0fc3a70b40d8f35e72c4ef2513fdb03a852 -r 6f0f77a940fc4966ce5534a06866ae57899dd867 static/scripts/utils/config.js
--- a/static/scripts/utils/config.js
+++ b/static/scripts/utils/config.js
@@ -128,13 +128,10 @@
* and a saved_values dictionary.
*/
from_models_and_saved_values: function(models, saved_values) {
- // Update models with saved values.
+ // If there are saved values, copy models and update with saved values.
if (saved_values) {
- _.each(models, function(m) {
- if (saved_values[m.key]) {
- // Found saved value, so update model.
- m.value = saved_values[m.key];
- }
+ models = _.map(models, function(m) {
+ return _.extend({}, m, { value: saved_values[m.key] });
});
}
diff -r 9fd3b0fc3a70b40d8f35e72c4ef2513fdb03a852 -r 6f0f77a940fc4966ce5534a06866ae57899dd867 static/scripts/viz/trackster/tracks.js
--- a/static/scripts/viz/trackster/tracks.js
+++ b/static/scripts/viz/trackster/tracks.js
@@ -208,7 +208,6 @@
// -- Set up drawable configuration. --
this.config = config_mod.ConfigSettingCollection.from_models_and_saved_values(this.config_params, obj_dict.prefs);
- this.config.each(function(s) { console.log(s.id, s.get('value')) })
// If there's no saved name, use object name.
if (!this.config.get_value('name')) {
@@ -2688,7 +2687,6 @@
* Use from_dict to recreate object.
*/
to_dict: function() {
- console.log(this.config.to_key_value_dict());
return {
track_type: this.get_type(),
dataset: {
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: davebgx: Fix misquoted command for tool dependency environment variable inheritance.
by commits-noreply@bitbucket.org 14 Mar '14
by commits-noreply@bitbucket.org 14 Mar '14
14 Mar '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/9fd3b0fc3a70/
Changeset: 9fd3b0fc3a70
User: davebgx
Date: 2014-03-14 21:27:57
Summary: Fix misquoted command for tool dependency environment variable inheritance.
Affected #: 1 file
diff -r 61a423fddc6b82f9bc626318fddbf5c31a967c90 -r 9fd3b0fc3a70b40d8f35e72c4ef2513fdb03a852 lib/tool_shed/galaxy_install/tool_dependencies/fabric_util.py
--- a/lib/tool_shed/galaxy_install/tool_dependencies/fabric_util.py
+++ b/lib/tool_shed/galaxy_install/tool_dependencies/fabric_util.py
@@ -462,7 +462,7 @@
inherited_env_var_name = env_var_value.split( '[' )[1].split( ']' )[0]
to_replace = '$ENV[%s]' % inherited_env_var_name
# Build a command line that outputs VARIABLE_NAME: <the value of the variable>.
- set_prior_environment_commands.append( 'echo "%s: $%s"' % ( inherited_env_var_name, inherited_env_var_name ) )
+ set_prior_environment_commands.append( 'echo %s: $%s' % ( inherited_env_var_name, inherited_env_var_name ) )
command = ' ; '.join( set_prior_environment_commands )
# Run the command and capture the output.
command_return = handle_command( app, tool_dependency, install_dir, command, return_output=True )
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
14 Mar '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/61a423fddc6b/
Changeset: 61a423fddc6b
User: carlfeberhard
Date: 2014-03-14 21:03:48
Summary: Visualization Framework: add an 'embeddable' flag to visualization config files indicating whether the visualization can reliably be rendered outside a page or iframe and within another page; pass the embeddable flag to the links rendered from the registry; (more information and example at https://bitbucket.org/carlfeberhard/galaxy-embedded-visualization-template/…)
Affected #: 2 files
diff -r b8f9e583b04024a8e181f5c0ef149b5aad51dba7 -r 61a423fddc6b82f9bc626318fddbf5c31a967c90 config/plugins/visualizations/visualization.dtd
--- a/config/plugins/visualizations/visualization.dtd
+++ b/config/plugins/visualizations/visualization.dtd
@@ -3,10 +3,14 @@
<!-- visualization
name: the title/display name of the visualization (e.g. 'Trackster', 'Fastq Stats', etc.) REQUIRED
disabled: if included (value does not matter), this attribute will prevent the visualization being loaded
+ embeddable: if included (value does not matter), indicates that this visualization can be rendered as a DOM
+ fragment and won't render to a full page when passed the variable 'embedded' in the query string.
+ DEFAULT false.
--><!ATTLIST visualization
name CDATA #REQUIRED
disabled CDATA #IMPLIED
+ embeddable CDATA #IMPLIED
><!ELEMENT description (#PCDATA)>
diff -r b8f9e583b04024a8e181f5c0ef149b5aad51dba7 -r 61a423fddc6b82f9bc626318fddbf5c31a967c90 lib/galaxy/visualization/registry.py
--- a/lib/galaxy/visualization/registry.py
+++ b/lib/galaxy/visualization/registry.py
@@ -211,11 +211,13 @@
url = self.get_visualization_url( trans, target_object, visualization_name, param_data )
display_name = visualization.config.get( 'name', None )
render_target = visualization.config.get( 'render_target', 'galaxy_main' )
+ embeddable = visualization.config.get( 'embeddable', False )
# remap some of these vars for direct use in ui.js, PopupMenu (e.g. text->html)
return {
- 'href' : url,
- 'html' : display_name,
- 'target': render_target
+ 'href' : url,
+ 'html' : display_name,
+ 'target' : render_target,
+ 'embeddable': embeddable
}
return None
@@ -394,6 +396,14 @@
if not returned[ 'name' ]:
raise ParsingException( 'visualization needs a name attribute' )
+ # record the embeddable flag - defaults to false
+ # this is a design by contract promise that the visualization can be rendered inside another page
+ # often by rendering only a DOM fragment. Since this is an advanced feature that requires a bit more
+ # work from the creator's side - it defaults to False
+ returned[ 'embeddable' ] = False
+ if 'embeddable' in xml_tree.attrib:
+ returned[ 'embeddable' ] = xml_tree.attrib.get( 'embeddable', False ) == 'true'
+
# a (for now) text description of what the visualization does
description = xml_tree.find( 'description' )
returned[ 'description' ] = description.text.strip() if description is not None else None
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: carlfeberhard: Scatterplot: wind down current development, fix label assignment, flatten model config, style fixes
by commits-noreply@bitbucket.org 14 Mar '14
by commits-noreply@bitbucket.org 14 Mar '14
14 Mar '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/b8f9e583b040/
Changeset: b8f9e583b040
User: carlfeberhard
Date: 2014-03-14 18:44:26
Summary: Scatterplot: wind down current development, fix label assignment, flatten model config, style fixes
Affected #: 8 files
diff -r 707ba3eee2abb828ac262e76c5b9fbbc6c85cd03 -r b8f9e583b04024a8e181f5c0ef149b5aad51dba7 config/plugins/visualizations/scatterplot/Gruntfile.js
--- a/config/plugins/visualizations/scatterplot/Gruntfile.js
+++ b/config/plugins/visualizations/scatterplot/Gruntfile.js
@@ -35,6 +35,7 @@
uglify: {
// uglify the concat single file directly into the static dir
options: {
+ // uncomment these to allow better source mapping during development
//mangle : false,
//beautify : true
},
diff -r 707ba3eee2abb828ac262e76c5b9fbbc6c85cd03 -r b8f9e583b04024a8e181f5c0ef149b5aad51dba7 config/plugins/visualizations/scatterplot/src/handlebars/chartcontrol.handlebars
--- a/config/plugins/visualizations/scatterplot/src/handlebars/chartcontrol.handlebars
+++ b/config/plugins/visualizations/scatterplot/src/handlebars/chartcontrol.handlebars
@@ -34,13 +34,13 @@
<div data-config-key="X-axis-label"class="text-input form-input"><label for="X-axis-label">Re-label the X axis: </label>
- <input type="text" name="X-axis-label" id="X-axis-label" value="{{x.label}}" />
+ <input type="text" name="X-axis-label" id="X-axis-label" value="{{xLabel}}" /><p class="form-help help-text-small"></p></div><div data-config-key="Y-axis-label" class="text-input form-input"><label for="Y-axis-label">Re-label the Y axis: </label>
- <input type="text" name="Y-axis-label" id="Y-axis-label" value="{{y.label}}" />
+ <input type="text" name="Y-axis-label" id="Y-axis-label" value="{{yLabel}}" /><p class="form-help help-text-small"></p></div>
diff -r 707ba3eee2abb828ac262e76c5b9fbbc6c85cd03 -r b8f9e583b04024a8e181f5c0ef149b5aad51dba7 config/plugins/visualizations/scatterplot/src/handlebars/datacontrol.handlebars
--- a/config/plugins/visualizations/scatterplot/src/handlebars/datacontrol.handlebars
+++ b/config/plugins/visualizations/scatterplot/src/handlebars/datacontrol.handlebars
@@ -17,8 +17,8 @@
</div><p class="help-text help-text-small">
- <b>Note</b>: If it can be determined from the dataset's filetype that column is not numeric, that
- column choice may be disabled for either the x or y axis.
+ <b>Note</b>: If it can be determined from the dataset's filetype that a column is not numeric,
+ that column choice may be disabled for either the x or y axis.
</p><button class="render-button btn btn-primary active">Draw</button>
diff -r 707ba3eee2abb828ac262e76c5b9fbbc6c85cd03 -r b8f9e583b04024a8e181f5c0ef149b5aad51dba7 config/plugins/visualizations/scatterplot/src/scatterplot-config-editor.js
--- a/config/plugins/visualizations/scatterplot/src/scatterplot-config-editor.js
+++ b/config/plugins/visualizations/scatterplot/src/scatterplot-config-editor.js
@@ -1,5 +1,6 @@
/* =============================================================================
todo:
+ localize
import button(display), func(model) - when user doesn't match
Move margins into wid/hi calcs (so final svg dims are w/h)
Better separation of AJAX in scatterplot.js (maybe pass in function?)
@@ -141,18 +142,18 @@
/** tab content to control how the chart is rendered (data glyph size, chart size, etc.) */
_render_chartControls : function( $where ){
+//TODO: as controls on actual chart
$where = $where || this.$el;
var editor = this,
config = this.model.get( 'config' ),
$chartControls = $where.find( '#chart-control' );
-
+
// ---- skeleton/form for controls
$chartControls.html( ScatterplotConfigEditor.templates.chartControl( config ) );
//console.debug( '$chartControl:', $chartControls );
// ---- slider controls
// limits for controls (by control/chartConfig id)
- //TODO: as class attribute
var controlRanges = {
'datapointSize' : { min: 2, max: 10, step: 1 },
'width' : { min: 200, max: 800, step: 20 },
diff -r 707ba3eee2abb828ac262e76c5b9fbbc6c85cd03 -r b8f9e583b04024a8e181f5c0ef149b5aad51dba7 config/plugins/visualizations/scatterplot/src/scatterplot-display.js
--- a/config/plugins/visualizations/scatterplot/src/scatterplot-display.js
+++ b/config/plugins/visualizations/scatterplot/src/scatterplot-display.js
@@ -184,7 +184,7 @@
//console.debug( JSON.stringify( stats, null, ' ' ) );
var config = this.model.get( 'config' ),
$statsTable = this.$el.find( '.stats-display' ),
- xLabel = config.x.label, yLabel = config.y.label,
+ xLabel = config.xLabel, yLabel = config.yLabel,
$table = $( '<table/>' ).addClass( 'table' )
.append([ '<thead><th></th><th>', xLabel, '</th><th>', yLabel, '</th></thead>' ].join( '' ))
.append( _.map( stats, function( stat, key ){
diff -r 707ba3eee2abb828ac262e76c5b9fbbc6c85cd03 -r b8f9e583b04024a8e181f5c0ef149b5aad51dba7 config/plugins/visualizations/scatterplot/src/scatterplot.js
--- a/config/plugins/visualizations/scatterplot/src/scatterplot.js
+++ b/config/plugins/visualizations/scatterplot/src/scatterplot.js
@@ -73,19 +73,19 @@
// .................................................................... axes
var axis = { x : {}, y : {} };
- //console.log( 'x.ticks:', config.x.ticks );
- //console.log( 'y.ticks:', config.y.ticks );
+ //console.log( 'xTicks:', config.xTicks );
+ //console.log( 'yTicks:', config.yTicks );
axis.x.fn = d3.svg.axis()
.orient( 'bottom' )
.scale( interpolaterFns.x )
- .ticks( config.x.ticks )
+ .ticks( config.xTicks )
// this will convert thousands -> k, millions -> M, etc.
.tickFormat( d3.format( 's' ) );
axis.y.fn = d3.svg.axis()
.orient( 'left' )
.scale( interpolaterFns.y )
- .ticks( config.y.ticks )
+ .ticks( config.yTicks )
.tickFormat( d3.format( 's' ) );
axis.x.g = content.append( 'g' )
@@ -103,8 +103,9 @@
var padding = 6;
// x-axis label
axis.x.label = svg.append( 'text' )
+ .attr( 'id', 'x-axis-label' )
.attr( 'class', 'axis-label' )
- .text( config.x.label )
+ .text( config.xLabel )
// align to the top-middle
.attr( 'text-anchor', 'middle' )
.attr( 'dominant-baseline', 'text-after-edge' )
@@ -117,8 +118,9 @@
// y-axis label
// place 4 pixels left of the axis.y.g left edge
axis.y.label = svg.append( 'text' )
+ .attr( 'id', 'y-axis-label' )
.attr( 'class', 'axis-label' )
- .text( config.y.label )
+ .text( config.yLabel )
// align to bottom-middle
.attr( 'text-anchor', 'middle' )
.attr( 'dominant-baseline', 'text-before-edge' )
diff -r 707ba3eee2abb828ac262e76c5b9fbbc6c85cd03 -r b8f9e583b04024a8e181f5c0ef149b5aad51dba7 config/plugins/visualizations/scatterplot/static/scatterplot-edit.js
--- a/config/plugins/visualizations/scatterplot/static/scatterplot-edit.js
+++ b/config/plugins/visualizations/scatterplot/static/scatterplot-edit.js
@@ -1,1 +1,1 @@
-function scatterplot(a,b,c){function d(){var a={v:{},h:{}};return a.v.lines=p.selectAll("line.v-grid-line").data(m.x.ticks(q.x.fn.ticks()[0])),a.v.lines.enter().append("svg:line").classed("grid-line v-grid-line",!0),a.v.lines.attr("x1",m.x).attr("x2",m.x).attr("y1",0).attr("y2",b.height),a.v.lines.exit().remove(),a.h.lines=p.selectAll("line.h-grid-line").data(m.y.ticks(q.y.fn.ticks()[0])),a.h.lines.enter().append("svg:line").classed("grid-line h-grid-line",!0),a.h.lines.attr("x1",0).attr("x2",b.width).attr("y1",m.y).attr("y2",m.y),a.h.lines.exit().remove(),a}function e(){return t.attr("cx",function(a,b){return m.x(j(a,b))}).attr("cy",function(a,b){return m.y(k(a,b))}).style("display","block").filter(function(){var a=d3.select(this).attr("cx"),c=d3.select(this).attr("cy");return 0>a||a>b.width?!0:0>c||c>b.height?!0:!1}).style("display","none")}function f(){$(".chart-info-box").remove(),q.redraw(),e(),s=d(),$(o.node()).trigger("zoom.scatterplot",{scale:n.scale(),translate:n.translate()})}function g(a,c,d){return c+=8,$(['<div class="chart-info-box" style="position: absolute">',void 0!==b.idColumn?"<div>"+d[b.idColumn]+"</div>":"","<div>",j(d),"</div>","<div>",k(d),"</div>","</div>"].join("")).css({top:a,left:c,"z-index":2})}var h=function(a,b){return"translate("+a+","+b+")"},i=function(a,b,c){return"rotate("+a+","+b+","+c+")"},j=function(a){return a[b.xColumn]},k=function(a){return a[b.yColumn]},l={x:{extent:d3.extent(c,j)},y:{extent:d3.extent(c,k)}},m={x:d3.scale.linear().domain(l.x.extent).range([0,b.width]),y:d3.scale.linear().domain(l.y.extent).range([b.height,0])},n=d3.behavior.zoom().x(m.x).y(m.y).scaleExtent([1,30]).scale(b.scale||1).translate(b.translate||[0,0]),o=d3.select(a).attr("class","scatterplot").attr("width","100%").attr("height",b.height+(b.margin.top+b.margin.bottom)),p=o.append("g").attr("class","content").attr("transform",h(b.margin.left,b.margin.top)).call(n);p.append("rect").attr("class","zoom-rect").attr("width",b.width).attr("height",b.height).style("fill","transparent");var q={x:{},y:{}};q.x.fn=d3.svg.axis().orient("bottom").scale(m.x).ticks(b.x.ticks).tickFormat(d3.format("s")),q.y.fn=d3.svg.axis().orient("left").scale(m.y).ticks(b.y.ticks).tickFormat(d3.format("s")),q.x.g=p.append("g").attr("class","x axis").attr("transform",h(0,b.height)).call(q.x.fn),q.y.g=p.append("g").attr("class","y axis").call(q.y.fn);var r=6;q.x.label=o.append("text").attr("class","axis-label").text(b.x.label).attr("text-anchor","middle").attr("dominant-baseline","text-after-edge").attr("x",b.width/2+b.margin.left).attr("y",b.height+b.margin.bottom+b.margin.top-r),q.y.label=o.append("text").attr("class","axis-label").text(b.y.label).attr("text-anchor","middle").attr("dominant-baseline","text-before-edge").attr("x",r).attr("y",b.height/2).attr("transform",i(-90,r,b.height/2)),q.redraw=function(){o.select(".x.axis").call(q.x.fn),o.select(".y.axis").call(q.y.fn)};var s=d(),t=p.selectAll(".glyph").data(c).enter().append("svg:circle").classed("glyph",!0).attr("cx",function(a,b){return m.x(j(a,b))}).attr("cy",function(a,b){return m.y(k(a,b))}).attr("r",0);t.transition().duration(b.animDuration).attr("r",b.datapointSize),e(),n.on("zoom",f),t.on("mouseover",function(a,c){var d=d3.select(this);d.classed("highlight",!0).style("fill","red").style("fill-opacity",1),p.append("line").attr("stroke","red").attr("stroke-width",1).attr("x1",d.attr("cx")-b.datapointSize).attr("y1",d.attr("cy")).attr("x2",0).attr("y2",d.attr("cy")).classed("hoverline",!0),d.attr("cy")<b.height&&p.append("line").attr("stroke","red").attr("stroke-width",1).attr("x1",d.attr("cx")).attr("y1",+d.attr("cy")+b.datapointSize).attr("x2",d.attr("cx")).attr("y2",b.height).classed("hoverline",!0);var e=this.getBoundingClientRect();$("body").append(g(e.top,e.right,a)),$(o.node()).trigger("mouseover-datapoint.scatterplot",[this,a,c])}),t.on("mouseout",function(){d3.select(this).classed("highlight",!1).style("fill","black").style("fill-opacity",.2),p.selectAll(".hoverline").remove(),$(".chart-info-box").remove()})}this.scatterplot=this.scatterplot||{},this.scatterplot.chartcontrol=Handlebars.template(function(a,b,c,d,e){this.compilerInfo=[4,">= 1.0.0"],c=this.merge(c,a.helpers),e=e||{};var f,g="",h="function",i=this.escapeExpression;return g+='<p class="help-text">\n Use the following controls to how the chart is displayed.\n The slide controls can be moved by the mouse or, if the \'handle\' is in focus, your keyboard\'s arrow keys.\n Move the focus between controls by using the tab or shift+tab keys on your keyboard.\n Use the \'Draw\' button to render (or re-render) the chart with the current settings.\n</p>\n\n<div data-config-key="datapointSize" class="form-input numeric-slider-input">\n <label for="datapointSize">Size of data point: </label>\n <div class="slider-output">',(f=c.datapointSize)?f=f.call(b,{hash:{},data:e}):(f=b.datapointSize,f=typeof f===h?f.apply(b):f),g+=i(f)+'</div>\n <div class="slider"></div>\n <p class="form-help help-text-small">\n Size of the graphic representation of each data point\n </p>\n</div>\n\n<div data-config-key="width" class="form-input numeric-slider-input">\n <label for="width">Chart width: </label>\n <div class="slider-output">',(f=c.width)?f=f.call(b,{hash:{},data:e}):(f=b.width,f=typeof f===h?f.apply(b):f),g+=i(f)+'</div>\n <div class="slider"></div>\n <p class="form-help help-text-small">\n (not including chart margins and axes)\n </p>\n</div>\n\n<div data-config-key="height" class="form-input numeric-slider-input">\n <label for="height">Chart height: </label>\n <div class="slider-output">',(f=c.height)?f=f.call(b,{hash:{},data:e}):(f=b.height,f=typeof f===h?f.apply(b):f),g+=i(f)+'</div>\n <div class="slider"></div>\n <p class="form-help help-text-small">\n (not including chart margins and axes)\n </p>\n</div>\n\n<div data-config-key="X-axis-label"class="text-input form-input">\n <label for="X-axis-label">Re-label the X axis: </label>\n <input type="text" name="X-axis-label" id="X-axis-label" value="'+i((f=b.x,f=null==f||f===!1?f:f.label,typeof f===h?f.apply(b):f))+'" />\n <p class="form-help help-text-small"></p>\n</div>\n\n<div data-config-key="Y-axis-label" class="text-input form-input">\n <label for="Y-axis-label">Re-label the Y axis: </label>\n <input type="text" name="Y-axis-label" id="Y-axis-label" value="'+i((f=b.y,f=null==f||f===!1?f:f.label,typeof f===h?f.apply(b):f))+'" />\n <p class="form-help help-text-small"></p>\n</div>\n\n<button class="render-button btn btn-primary active">Draw</button>\n'}),this.scatterplot.datacontrol=Handlebars.template(function(a,b,c,d,e){this.compilerInfo=[4,">= 1.0.0"],c=this.merge(c,a.helpers),e=e||{};var f,g="",h="function";return g+='<p class="help-text">\n Use the following control to change which columns are used by the chart. Click any cell\n from the last three rows of the table to select the column for the appropriate data.\n Use the \'Draw\' button to render (or re-render) the chart with the current settings.\n</p>\n\n<ul class="help-text" style="margin-left: 8px">\n <li><b>X Column</b>: which column values will be used for the x axis of the chart.</li>\n <li><b>Y Column</b>: which column values will be used for the y axis of the chart.</li>\n <li><b>ID Column</b>: an additional column value displayed when the user hovers over a data point.\n It may be useful to select unique or categorical identifiers here (such as gene ids).\n </li>\n</ul>\n\n<div class="column-selection">\n <pre class="peek">',(f=c.peek)?f=f.call(b,{hash:{},data:e}):(f=b.peek,f=typeof f===h?f.apply(b):f),(f||0===f)&&(g+=f),g+='</pre>\n</div>\n\n<p class="help-text help-text-small">\n <b>Note</b>: If it can be determined from the dataset\'s filetype that column is not numeric, that\n column choice may be disabled for either the x or y axis.\n</p>\n\n<button class="render-button btn btn-primary active">Draw</button>\n'}),this.scatterplot.editor=Handlebars.template(function(a,b,c,d,e){this.compilerInfo=[4,">= 1.0.0"],c=this.merge(c,a.helpers),e=e||{};var f="";return f+='<div class="scatterplot-editor tabbable tabs-left">\n \n <ul class="nav nav-tabs">\n \n <li class="active">\n <a title="Use this tab to change which data are used"\n href="#data-control" data-toggle="tab">Data Controls</a>\n </li>\n <li>\n <a title="Use this tab to change how the chart is drawn"\n href="#chart-control" data-toggle="tab" >Chart Controls</a>\n </li>\n \n <li class="disabled">\n <a title="This tab will display the chart"\n href="#chart-display" data-toggle="tab">Chart</a>\n </li>\n \n <li class="file-controls">\n<!-- <button class="copy-btn btn btn-default"\n title="Save this as a new visualization">Save to new</button>-->\n <button class="save-btn btn btn-default">Save</button>\n </li>\n </ul>\n\n \n <div class="tab-content">\n \n <div id="data-control" class="scatterplot-config-control tab-pane active">\n \n </div>\n \n \n <div id="chart-control" class="scatterplot-config-control tab-pane">\n \n </div>\n\n \n <div id="chart-display" class="scatterplot-display tab-pane"></div>\n\n </div>\n</div>\n'});var ScatterplotConfigEditor=Backbone.View.extend(LoggableMixin).extend({className:"scatterplot-control-form",initialize:function(a){if(this.model||(this.model=new Visualization({type:"scatterplot"})),this.log(this+".initialize, attributes:",a),!a||!a.dataset)throw new Error("ScatterplotConfigEditor requires a dataset");this.dataset=a.dataset,this.log("dataset:",this.dataset),this.display=new ScatterplotDisplay({dataset:a.dataset,model:this.model})},render:function(){this.$el.empty().append(ScatterplotConfigEditor.templates.mainLayout({})),this.model.id&&(this.$el.find(".copy-btn").show(),this.$el.find(".save-btn").text("Update saved")),this.$el.find("[title]").tooltip(),this._render_dataControl(),this._render_chartControls(),this._render_chartDisplay();var a=this.model.get("config");return this.model.id&&_.isFinite(a.xColumn)&&_.isFinite(a.yColumn)&&this.renderChart(),this},_getColumnIndecesByType:function(){var a={numeric:[],text:[],all:[]};return _.each(this.dataset.metadata_column_types||[],function(b,c){"int"===b||"float"===b?a.numeric.push(c):("str"===b||"list"===b)&&a.text.push(c),a.all.push(c)}),a.numeric.length<2&&(a.numeric=[]),a},_render_dataControl:function(a){a=a||this.$el;var b=this,c=this.model.get("config"),d=this._getColumnIndecesByType(),e=a.find(".tab-pane#data-control");return e.html(ScatterplotConfigEditor.templates.dataControl({peek:this.dataset.peek})),e.find(".peek").peekControl({controls:[{label:"X Column",id:"xColumn",selected:c.xColumn,disabled:d.text},{label:"Y Column",id:"yColumn",selected:c.yColumn,disabled:d.text},{label:"ID Column",id:"idColumn",selected:c.idColumn}]}).on("peek-control.change",function(a,c){b.model.set("config",c)}).on("peek-control.rename",function(){}),e.find("[title]").tooltip(),e},_render_chartControls:function(a){function b(){var a=$(this),b=a.slider("value");c.model.set("config",_.object([[a.parent().data("config-key"),b]])),a.siblings(".slider-output").text(b)}a=a||this.$el;var c=this,d=this.model.get("config"),e=a.find("#chart-control");e.html(ScatterplotConfigEditor.templates.chartControl(d));var f={datapointSize:{min:2,max:10,step:1},width:{min:200,max:800,step:20},height:{min:200,max:800,step:20}};e.find(".numeric-slider-input").each(function(){var a=$(this),c=a.attr("data-config-key"),e=_.extend(f[c],{value:d[c],change:b,slide:b});a.find(".slider").slider(e),a.children(".slider-output").text(d[c])});var g=this.dataset.metadata_column_names||[],h=d.xLabel||g[d.xColumn]||"X",i=d.yLabel||g[d.yColumn]||"Y";return e.find('input[name="X-axis-label"]').val(h).on("change",function(){c.model.set("config",{xLabel:$(this).val()})}),e.find('input[name="Y-axis-label"]').val(i).on("change",function(){c.model.set("config",{yLabel:$(this).val()})}),e.find("[title]").tooltip(),e},_render_chartDisplay:function(a){a=a||this.$el;var b=a.find(".tab-pane#chart-display");return this.display.setElement(b),this.display.render(),b.find("[title]").tooltip(),b},events:{"change #include-id-checkbox":"toggleThirdColumnSelector","click #data-control .render-button":"renderChart","click #chart-control .render-button":"renderChart","click .save-btn":"saveVisualization"},saveVisualization:function(){var a=this;this.model.save().fail(function(b,c,d){console.error(b,c,d),a.trigger("save:error",view),alert("Error loading data:\n"+b.responseText)}).then(function(){a.display.render()})},toggleThirdColumnSelector:function(){this.$el.find('select[name="idColumn"]').parent().toggle()},renderChart:function(){this.$el.find(".nav li.disabled").removeClass("disabled"),this.$el.find("ul.nav").find('a[href="#chart-display"]').tab("show"),this.display.fetchData()},toString:function(){return"ScatterplotConfigEditor("+(this.dataset?this.dataset.id:"")+")"}});ScatterplotConfigEditor.templates={mainLayout:scatterplot.editor,dataControl:scatterplot.datacontrol,chartControl:scatterplot.chartcontrol};var ScatterplotDisplay=Backbone.View.extend({initialize:function(a){this.data=null,this.dataset=a.dataset,this.lineCount=this.dataset.metadata_data_lines||null},fetchData:function(){this.showLoadingIndicator();var a=this,b=this.model.get("config"),c=jQuery.getJSON("/api/datasets/"+this.dataset.id,{data_type:"raw_data",provider:"dataset-column",limit:b.pagination.perPage,offset:b.pagination.currPage*b.pagination.perPage});return c.done(function(b){a.data=b.data,a.trigger("data:fetched",a),a.renderData()}),c.fail(function(b,c,d){console.error(b,c,d),a.trigger("data:error",a),alert("Error loading data:\n"+b.responseText)}),c},showLoadingIndicator:function(){this.$el.find(".scatterplot-data-info").html(['<div class="loading-indicator">','<span class="fa fa-spinner fa-spin"></span>','<span class="loading-indicator-message">loading...</span>',"</div>"].join(""))},template:function(){var a=['<div class="controls clear">','<div class="right">','<p class="scatterplot-data-info"></p>','<button class="stats-toggle-btn">Stats</button>','<button class="rerender-btn">Redraw</button>',"</div>",'<div class="left">','<div class="page-control"></div>',"</div>","</div>","<svg/>",'<div class="stats-display"></div>'].join("");return a},render:function(){return this.$el.addClass("scatterplot-display").html(this.template()),this.data&&this.renderData(),this},renderData:function(){this.renderLeftControls(),this.renderRightControls(),this.renderPlot(this.data),this.getStats()},renderLeftControls:function(){var a=this,b=this.model.get("config");return this.$el.find(".controls .left .page-control").pagination({startingPage:b.pagination.currPage,perPage:b.pagination.perPage,totalDataSize:this.lineCount,currDataSize:this.data.length}).off().on("pagination.page-change",function(c,d){b.pagination.currPage=d,a.model.set("config",{pagination:b.pagination}),a.resetZoom(),a.fetchData()}),this},renderRightControls:function(){var a=this;this.setLineInfo(this.data),this.$el.find(".stats-toggle-btn").off().click(function(){a.toggleStats()}),this.$el.find(".rerender-btn").off().click(function(){a.resetZoom(),a.renderPlot(this.data)})},renderPlot:function(){var a=this,b=this.$el.find("svg");this.toggleStats(!1),b.off().empty().show().on("zoom.scatterplot",function(b,c){a.model.set("config",c)}),scatterplot(b.get(0),this.model.get("config"),this.data)},setLineInfo:function(a,b){if(a){var c=this.model.get("config"),d=this.lineCount||"an unknown total",e=c.pagination.currPage*c.pagination.perPage,f=e+a.length;this.$el.find(".controls p.scatterplot-data-info").text([e+1,"to",f,"of",d].join(" "))}else this.$el.find(".controls p.scatterplot-data-info").html(b||"");return this},resetZoom:function(a,b){return a=void 0!==a?a:1,b=void 0!==b?b:[0,0],this.model.set("config",{scale:a,translate:b}),this},getStats:function(){if(this.data){var a=this,b=this.model.get("config"),c=new Worker("/plugins/visualizations/scatterplot/static/worker-stats.js");c.postMessage({data:this.data,keys:[b.xColumn,b.yColumn]}),c.onerror=function(){c.terminate()},c.onmessage=function(b){a.renderStats(b.data)}}},renderStats:function(a){var b=this.model.get("config"),c=this.$el.find(".stats-display"),d=b.x.label,e=b.y.label,f=$("<table/>").addClass("table").append(["<thead><th></th><th>",d,"</th><th>",e,"</th></thead>"].join("")).append(_.map(a,function(a,b){return $(["<tr><td>",b,"</td><td>",a[0],"</td><td>",a[1],"</td></tr>"].join(""))}));c.empty().append(f)},toggleStats:function(a){var b=this.$el.find(".stats-display");a=void 0===a?b.is(":hidden"):a,a?(this.$el.find("svg").hide(),b.show(),this.$el.find(".controls .stats-toggle-btn").text("Plot")):(b.hide(),this.$el.find("svg").show(),this.$el.find(".controls .stats-toggle-btn").text("Stats"))},toString:function(){return"ScatterplotView()"}}),ScatterplotModel=Visualization.extend({defaults:{type:"scatterplot",config:{pagination:{currPage:0,perPage:3e3},width:400,height:400,margin:{top:16,right:16,bottom:40,left:54},x:{ticks:10,label:"X"},y:{ticks:10,label:"Y"},datapointSize:4,animDuration:500,scale:1,translate:[0,0]}}});
\ No newline at end of file
+function scatterplot(a,b,c){function d(){var a={v:{},h:{}};return a.v.lines=p.selectAll("line.v-grid-line").data(m.x.ticks(q.x.fn.ticks()[0])),a.v.lines.enter().append("svg:line").classed("grid-line v-grid-line",!0),a.v.lines.attr("x1",m.x).attr("x2",m.x).attr("y1",0).attr("y2",b.height),a.v.lines.exit().remove(),a.h.lines=p.selectAll("line.h-grid-line").data(m.y.ticks(q.y.fn.ticks()[0])),a.h.lines.enter().append("svg:line").classed("grid-line h-grid-line",!0),a.h.lines.attr("x1",0).attr("x2",b.width).attr("y1",m.y).attr("y2",m.y),a.h.lines.exit().remove(),a}function e(){return t.attr("cx",function(a,b){return m.x(j(a,b))}).attr("cy",function(a,b){return m.y(k(a,b))}).style("display","block").filter(function(){var a=d3.select(this).attr("cx"),c=d3.select(this).attr("cy");return 0>a||a>b.width?!0:0>c||c>b.height?!0:!1}).style("display","none")}function f(){$(".chart-info-box").remove(),q.redraw(),e(),s=d(),$(o.node()).trigger("zoom.scatterplot",{scale:n.scale(),translate:n.translate()})}function g(a,c,d){return c+=8,$(['<div class="chart-info-box" style="position: absolute">',void 0!==b.idColumn?"<div>"+d[b.idColumn]+"</div>":"","<div>",j(d),"</div>","<div>",k(d),"</div>","</div>"].join("")).css({top:a,left:c,"z-index":2})}var h=function(a,b){return"translate("+a+","+b+")"},i=function(a,b,c){return"rotate("+a+","+b+","+c+")"},j=function(a){return a[b.xColumn]},k=function(a){return a[b.yColumn]},l={x:{extent:d3.extent(c,j)},y:{extent:d3.extent(c,k)}},m={x:d3.scale.linear().domain(l.x.extent).range([0,b.width]),y:d3.scale.linear().domain(l.y.extent).range([b.height,0])},n=d3.behavior.zoom().x(m.x).y(m.y).scaleExtent([1,30]).scale(b.scale||1).translate(b.translate||[0,0]),o=d3.select(a).attr("class","scatterplot").attr("width","100%").attr("height",b.height+(b.margin.top+b.margin.bottom)),p=o.append("g").attr("class","content").attr("transform",h(b.margin.left,b.margin.top)).call(n);p.append("rect").attr("class","zoom-rect").attr("width",b.width).attr("height",b.height).style("fill","transparent");var q={x:{},y:{}};q.x.fn=d3.svg.axis().orient("bottom").scale(m.x).ticks(b.xTicks).tickFormat(d3.format("s")),q.y.fn=d3.svg.axis().orient("left").scale(m.y).ticks(b.yTicks).tickFormat(d3.format("s")),q.x.g=p.append("g").attr("class","x axis").attr("transform",h(0,b.height)).call(q.x.fn),q.y.g=p.append("g").attr("class","y axis").call(q.y.fn);var r=6;q.x.label=o.append("text").attr("id","x-axis-label").attr("class","axis-label").text(b.xLabel).attr("text-anchor","middle").attr("dominant-baseline","text-after-edge").attr("x",b.width/2+b.margin.left).attr("y",b.height+b.margin.bottom+b.margin.top-r),q.y.label=o.append("text").attr("id","y-axis-label").attr("class","axis-label").text(b.yLabel).attr("text-anchor","middle").attr("dominant-baseline","text-before-edge").attr("x",r).attr("y",b.height/2).attr("transform",i(-90,r,b.height/2)),q.redraw=function(){o.select(".x.axis").call(q.x.fn),o.select(".y.axis").call(q.y.fn)};var s=d(),t=p.selectAll(".glyph").data(c).enter().append("svg:circle").classed("glyph",!0).attr("cx",function(a,b){return m.x(j(a,b))}).attr("cy",function(a,b){return m.y(k(a,b))}).attr("r",0);t.transition().duration(b.animDuration).attr("r",b.datapointSize),e(),n.on("zoom",f),t.on("mouseover",function(a,c){var d=d3.select(this);d.classed("highlight",!0).style("fill","red").style("fill-opacity",1),p.append("line").attr("stroke","red").attr("stroke-width",1).attr("x1",d.attr("cx")-b.datapointSize).attr("y1",d.attr("cy")).attr("x2",0).attr("y2",d.attr("cy")).classed("hoverline",!0),d.attr("cy")<b.height&&p.append("line").attr("stroke","red").attr("stroke-width",1).attr("x1",d.attr("cx")).attr("y1",+d.attr("cy")+b.datapointSize).attr("x2",d.attr("cx")).attr("y2",b.height).classed("hoverline",!0);var e=this.getBoundingClientRect();$("body").append(g(e.top,e.right,a)),$(o.node()).trigger("mouseover-datapoint.scatterplot",[this,a,c])}),t.on("mouseout",function(){d3.select(this).classed("highlight",!1).style("fill","black").style("fill-opacity",.2),p.selectAll(".hoverline").remove(),$(".chart-info-box").remove()})}this.scatterplot=this.scatterplot||{},this.scatterplot.chartcontrol=Handlebars.template(function(a,b,c,d,e){this.compilerInfo=[4,">= 1.0.0"],c=this.merge(c,a.helpers),e=e||{};var f,g="",h="function",i=this.escapeExpression;return g+='<p class="help-text">\n Use the following controls to how the chart is displayed.\n The slide controls can be moved by the mouse or, if the \'handle\' is in focus, your keyboard\'s arrow keys.\n Move the focus between controls by using the tab or shift+tab keys on your keyboard.\n Use the \'Draw\' button to render (or re-render) the chart with the current settings.\n</p>\n\n<div data-config-key="datapointSize" class="form-input numeric-slider-input">\n <label for="datapointSize">Size of data point: </label>\n <div class="slider-output">',(f=c.datapointSize)?f=f.call(b,{hash:{},data:e}):(f=b.datapointSize,f=typeof f===h?f.apply(b):f),g+=i(f)+'</div>\n <div class="slider"></div>\n <p class="form-help help-text-small">\n Size of the graphic representation of each data point\n </p>\n</div>\n\n<div data-config-key="width" class="form-input numeric-slider-input">\n <label for="width">Chart width: </label>\n <div class="slider-output">',(f=c.width)?f=f.call(b,{hash:{},data:e}):(f=b.width,f=typeof f===h?f.apply(b):f),g+=i(f)+'</div>\n <div class="slider"></div>\n <p class="form-help help-text-small">\n (not including chart margins and axes)\n </p>\n</div>\n\n<div data-config-key="height" class="form-input numeric-slider-input">\n <label for="height">Chart height: </label>\n <div class="slider-output">',(f=c.height)?f=f.call(b,{hash:{},data:e}):(f=b.height,f=typeof f===h?f.apply(b):f),g+=i(f)+'</div>\n <div class="slider"></div>\n <p class="form-help help-text-small">\n (not including chart margins and axes)\n </p>\n</div>\n\n<div data-config-key="X-axis-label"class="text-input form-input">\n <label for="X-axis-label">Re-label the X axis: </label>\n <input type="text" name="X-axis-label" id="X-axis-label" value="',(f=c.xLabel)?f=f.call(b,{hash:{},data:e}):(f=b.xLabel,f=typeof f===h?f.apply(b):f),g+=i(f)+'" />\n <p class="form-help help-text-small"></p>\n</div>\n\n<div data-config-key="Y-axis-label" class="text-input form-input">\n <label for="Y-axis-label">Re-label the Y axis: </label>\n <input type="text" name="Y-axis-label" id="Y-axis-label" value="',(f=c.yLabel)?f=f.call(b,{hash:{},data:e}):(f=b.yLabel,f=typeof f===h?f.apply(b):f),g+=i(f)+'" />\n <p class="form-help help-text-small"></p>\n</div>\n\n<button class="render-button btn btn-primary active">Draw</button>\n'}),this.scatterplot.datacontrol=Handlebars.template(function(a,b,c,d,e){this.compilerInfo=[4,">= 1.0.0"],c=this.merge(c,a.helpers),e=e||{};var f,g="",h="function";return g+='<p class="help-text">\n Use the following control to change which columns are used by the chart. Click any cell\n from the last three rows of the table to select the column for the appropriate data.\n Use the \'Draw\' button to render (or re-render) the chart with the current settings.\n</p>\n\n<ul class="help-text" style="margin-left: 8px">\n <li><b>X Column</b>: which column values will be used for the x axis of the chart.</li>\n <li><b>Y Column</b>: which column values will be used for the y axis of the chart.</li>\n <li><b>ID Column</b>: an additional column value displayed when the user hovers over a data point.\n It may be useful to select unique or categorical identifiers here (such as gene ids).\n </li>\n</ul>\n\n<div class="column-selection">\n <pre class="peek">',(f=c.peek)?f=f.call(b,{hash:{},data:e}):(f=b.peek,f=typeof f===h?f.apply(b):f),(f||0===f)&&(g+=f),g+='</pre>\n</div>\n\n<p class="help-text help-text-small">\n <b>Note</b>: If it can be determined from the dataset\'s filetype that a column is not numeric,\n that column choice may be disabled for either the x or y axis.\n</p>\n\n<button class="render-button btn btn-primary active">Draw</button>\n'}),this.scatterplot.editor=Handlebars.template(function(a,b,c,d,e){this.compilerInfo=[4,">= 1.0.0"],c=this.merge(c,a.helpers),e=e||{};var f="";return f+='<div class="scatterplot-editor tabbable tabs-left">\n \n <ul class="nav nav-tabs">\n \n <li class="active">\n <a title="Use this tab to change which data are used"\n href="#data-control" data-toggle="tab">Data Controls</a>\n </li>\n <li>\n <a title="Use this tab to change how the chart is drawn"\n href="#chart-control" data-toggle="tab" >Chart Controls</a>\n </li>\n \n <li class="disabled">\n <a title="This tab will display the chart"\n href="#chart-display" data-toggle="tab">Chart</a>\n </li>\n \n <li class="file-controls">\n<!-- <button class="copy-btn btn btn-default"\n title="Save this as a new visualization">Save to new</button>-->\n <button class="save-btn btn btn-default">Save</button>\n </li>\n </ul>\n\n \n <div class="tab-content">\n \n <div id="data-control" class="scatterplot-config-control tab-pane active">\n \n </div>\n \n \n <div id="chart-control" class="scatterplot-config-control tab-pane">\n \n </div>\n\n \n <div id="chart-display" class="scatterplot-display tab-pane"></div>\n\n </div>\n</div>\n'});var ScatterplotConfigEditor=Backbone.View.extend(LoggableMixin).extend({className:"scatterplot-control-form",initialize:function(a){if(this.model||(this.model=new Visualization({type:"scatterplot"})),this.log(this+".initialize, attributes:",a),!a||!a.dataset)throw new Error("ScatterplotConfigEditor requires a dataset");this.dataset=a.dataset,this.log("dataset:",this.dataset),this.display=new ScatterplotDisplay({dataset:a.dataset,model:this.model})},render:function(){this.$el.empty().append(ScatterplotConfigEditor.templates.mainLayout({})),this.model.id&&(this.$el.find(".copy-btn").show(),this.$el.find(".save-btn").text("Update saved")),this.$el.find("[title]").tooltip(),this._render_dataControl(),this._render_chartControls(),this._render_chartDisplay();var a=this.model.get("config");return this.model.id&&_.isFinite(a.xColumn)&&_.isFinite(a.yColumn)&&this.renderChart(),this},_getColumnIndecesByType:function(){var a={numeric:[],text:[],all:[]};return _.each(this.dataset.metadata_column_types||[],function(b,c){"int"===b||"float"===b?a.numeric.push(c):("str"===b||"list"===b)&&a.text.push(c),a.all.push(c)}),a.numeric.length<2&&(a.numeric=[]),a},_render_dataControl:function(a){a=a||this.$el;var b=this,c=this.model.get("config"),d=this._getColumnIndecesByType(),e=a.find(".tab-pane#data-control");return e.html(ScatterplotConfigEditor.templates.dataControl({peek:this.dataset.peek})),e.find(".peek").peekControl({controls:[{label:"X Column",id:"xColumn",selected:c.xColumn,disabled:d.text},{label:"Y Column",id:"yColumn",selected:c.yColumn,disabled:d.text},{label:"ID Column",id:"idColumn",selected:c.idColumn}]}).on("peek-control.change",function(a,c){b.model.set("config",c)}).on("peek-control.rename",function(){}),e.find("[title]").tooltip(),e},_render_chartControls:function(a){function b(){var a=$(this),b=a.slider("value");c.model.set("config",_.object([[a.parent().data("config-key"),b]])),a.siblings(".slider-output").text(b)}a=a||this.$el;var c=this,d=this.model.get("config"),e=a.find("#chart-control");e.html(ScatterplotConfigEditor.templates.chartControl(d));var f={datapointSize:{min:2,max:10,step:1},width:{min:200,max:800,step:20},height:{min:200,max:800,step:20}};e.find(".numeric-slider-input").each(function(){var a=$(this),c=a.attr("data-config-key"),e=_.extend(f[c],{value:d[c],change:b,slide:b});a.find(".slider").slider(e),a.children(".slider-output").text(d[c])});var g=this.dataset.metadata_column_names||[],h=d.xLabel||g[d.xColumn]||"X",i=d.yLabel||g[d.yColumn]||"Y";return e.find('input[name="X-axis-label"]').val(h).on("change",function(){c.model.set("config",{xLabel:$(this).val()})}),e.find('input[name="Y-axis-label"]').val(i).on("change",function(){c.model.set("config",{yLabel:$(this).val()})}),e.find("[title]").tooltip(),e},_render_chartDisplay:function(a){a=a||this.$el;var b=a.find(".tab-pane#chart-display");return this.display.setElement(b),this.display.render(),b.find("[title]").tooltip(),b},events:{"change #include-id-checkbox":"toggleThirdColumnSelector","click #data-control .render-button":"renderChart","click #chart-control .render-button":"renderChart","click .save-btn":"saveVisualization"},saveVisualization:function(){var a=this;this.model.save().fail(function(b,c,d){console.error(b,c,d),a.trigger("save:error",view),alert("Error loading data:\n"+b.responseText)}).then(function(){a.display.render()})},toggleThirdColumnSelector:function(){this.$el.find('select[name="idColumn"]').parent().toggle()},renderChart:function(){this.$el.find(".nav li.disabled").removeClass("disabled"),this.$el.find("ul.nav").find('a[href="#chart-display"]').tab("show"),this.display.fetchData()},toString:function(){return"ScatterplotConfigEditor("+(this.dataset?this.dataset.id:"")+")"}});ScatterplotConfigEditor.templates={mainLayout:scatterplot.editor,dataControl:scatterplot.datacontrol,chartControl:scatterplot.chartcontrol};var ScatterplotDisplay=Backbone.View.extend({initialize:function(a){this.data=null,this.dataset=a.dataset,this.lineCount=this.dataset.metadata_data_lines||null},fetchData:function(){this.showLoadingIndicator();var a=this,b=this.model.get("config"),c=jQuery.getJSON("/api/datasets/"+this.dataset.id,{data_type:"raw_data",provider:"dataset-column",limit:b.pagination.perPage,offset:b.pagination.currPage*b.pagination.perPage});return c.done(function(b){a.data=b.data,a.trigger("data:fetched",a),a.renderData()}),c.fail(function(b,c,d){console.error(b,c,d),a.trigger("data:error",a),alert("Error loading data:\n"+b.responseText)}),c},showLoadingIndicator:function(){this.$el.find(".scatterplot-data-info").html(['<div class="loading-indicator">','<span class="fa fa-spinner fa-spin"></span>','<span class="loading-indicator-message">loading...</span>',"</div>"].join(""))},template:function(){var a=['<div class="controls clear">','<div class="right">','<p class="scatterplot-data-info"></p>','<button class="stats-toggle-btn">Stats</button>','<button class="rerender-btn">Redraw</button>',"</div>",'<div class="left">','<div class="page-control"></div>',"</div>","</div>","<svg/>",'<div class="stats-display"></div>'].join("");return a},render:function(){return this.$el.addClass("scatterplot-display").html(this.template()),this.data&&this.renderData(),this},renderData:function(){this.renderLeftControls(),this.renderRightControls(),this.renderPlot(this.data),this.getStats()},renderLeftControls:function(){var a=this,b=this.model.get("config");return this.$el.find(".controls .left .page-control").pagination({startingPage:b.pagination.currPage,perPage:b.pagination.perPage,totalDataSize:this.lineCount,currDataSize:this.data.length}).off().on("pagination.page-change",function(c,d){b.pagination.currPage=d,a.model.set("config",{pagination:b.pagination}),a.resetZoom(),a.fetchData()}),this},renderRightControls:function(){var a=this;this.setLineInfo(this.data),this.$el.find(".stats-toggle-btn").off().click(function(){a.toggleStats()}),this.$el.find(".rerender-btn").off().click(function(){a.resetZoom(),a.renderPlot(this.data)})},renderPlot:function(){var a=this,b=this.$el.find("svg");this.toggleStats(!1),b.off().empty().show().on("zoom.scatterplot",function(b,c){a.model.set("config",c)}),scatterplot(b.get(0),this.model.get("config"),this.data)},setLineInfo:function(a,b){if(a){var c=this.model.get("config"),d=this.lineCount||"an unknown total",e=c.pagination.currPage*c.pagination.perPage,f=e+a.length;this.$el.find(".controls p.scatterplot-data-info").text([e+1,"to",f,"of",d].join(" "))}else this.$el.find(".controls p.scatterplot-data-info").html(b||"");return this},resetZoom:function(a,b){return a=void 0!==a?a:1,b=void 0!==b?b:[0,0],this.model.set("config",{scale:a,translate:b}),this},getStats:function(){if(this.data){var a=this,b=this.model.get("config"),c=new Worker("/plugins/visualizations/scatterplot/static/worker-stats.js");c.postMessage({data:this.data,keys:[b.xColumn,b.yColumn]}),c.onerror=function(){c.terminate()},c.onmessage=function(b){a.renderStats(b.data)}}},renderStats:function(a){var b=this.model.get("config"),c=this.$el.find(".stats-display"),d=b.xLabel,e=b.yLabel,f=$("<table/>").addClass("table").append(["<thead><th></th><th>",d,"</th><th>",e,"</th></thead>"].join("")).append(_.map(a,function(a,b){return $(["<tr><td>",b,"</td><td>",a[0],"</td><td>",a[1],"</td></tr>"].join(""))}));c.empty().append(f)},toggleStats:function(a){var b=this.$el.find(".stats-display");a=void 0===a?b.is(":hidden"):a,a?(this.$el.find("svg").hide(),b.show(),this.$el.find(".controls .stats-toggle-btn").text("Plot")):(b.hide(),this.$el.find("svg").show(),this.$el.find(".controls .stats-toggle-btn").text("Stats"))},toString:function(){return"ScatterplotView()"}}),ScatterplotModel=Visualization.extend({defaults:{type:"scatterplot",config:{pagination:{currPage:0,perPage:3e3},width:400,height:400,margin:{top:16,right:16,bottom:40,left:54},xTicks:10,xLabel:"X",yTicks:10,yLabel:"Y",datapointSize:4,animDuration:500,scale:1,translate:[0,0]}}});
\ No newline at end of file
diff -r 707ba3eee2abb828ac262e76c5b9fbbc6c85cd03 -r b8f9e583b04024a8e181f5c0ef149b5aad51dba7 config/plugins/visualizations/scatterplot/static/scatterplot.css
--- a/config/plugins/visualizations/scatterplot/static/scatterplot.css
+++ b/config/plugins/visualizations/scatterplot/static/scatterplot.css
@@ -5,10 +5,11 @@
margin-bottom: 16px;
overflow: auto;
background-color: #ebd9b2;
- padding : 8px 12px 8px 12px;
+ padding : 12px 12px 4px 12px;
}
.chart-header h2 {
+ margin-top: 0px;
margin-bottom: 4px;
}
@@ -63,7 +64,7 @@
.file-controls .btn {
height: 24px;
padding: 0px 10px 0px 10px;
- line-height: 24px;
+ line-height: 20px;
}
.file-controls .copy-btn {
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: carlfeberhard: History panels: persist search visibility, improve model switching, consistently apply icon button groups, fix style on only-child buttons, improve docs and private function names
by commits-noreply@bitbucket.org 14 Mar '14
by commits-noreply@bitbucket.org 14 Mar '14
14 Mar '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/707ba3eee2ab/
Changeset: 707ba3eee2ab
User: carlfeberhard
Date: 2014-03-14 17:26:23
Summary: History panels: persist search visibility, improve model switching, consistently apply icon button groups, fix style on only-child buttons, improve docs and private function names
Affected #: 11 files
diff -r 1ed94d89769ed2b51232c6c714c3bb21d93cc3ce -r 707ba3eee2abb828ac262e76c5b9fbbc6c85cd03 static/scripts/mvc/history/annotated-history-panel.js
--- a/static/scripts/mvc/history/annotated-history-panel.js
+++ b/static/scripts/mvc/history/annotated-history-panel.js
@@ -33,16 +33,23 @@
HDAViewClass : hdaBase.HDABaseView,
// ------------------------------------------------------------------------ panel rendering
+ /** render with history data
+ * In this override:
+ * replace the datasets list with a table,
+ * add the history annotation,
+ * and move the search controls
+ * @returns {jQuery} dom fragment as temporary container to be swapped out later
+ */
renderModel : function( ){
- // huh?
+ // why do we need this here? why isn't className being applied?
this.$el.addClass( this.className );
var $newRender = readonlyPanel.ReadOnlyHistoryPanel.prototype.renderModel.call( this ),
// move datasets from div to table
- $datasetsList = $newRender.find( this.datasetsSelector ),
+ $datasetsList = this.$datasetsList( $newRender ),
$datasetsTable = $( '<table/>' ).addClass( 'datasets-list datasets-table' );
$datasetsTable.append( $datasetsList.children() );
- $newRender.find( this.datasetsSelector ).replaceWith( $datasetsTable );
- //TODO: it's possible to do this with css only, right?
+ $datasetsList.replaceWith( $datasetsTable );
+ //TODO: it's possible to do this with css only, right? display: table-cell, etc.?
// add history annotation under subtitle
$newRender.find( '.history-subtitle' ).after( this.renderHistoryAnnotation() );
@@ -50,11 +57,11 @@
// hide search button, move search bar beneath controls (instead of above title), show, and set up
$newRender.find( '.history-search-btn' ).hide();
$newRender.find( '.history-controls' ).after( $newRender.find( '.history-search-controls' ).show() );
- this.setUpSearchInput( $newRender.find( '.history-search-input' ) );
return $newRender;
},
+ /** render the history's annotation as it's own field */
renderHistoryAnnotation : function(){
var annotation = this.model.get( 'annotation' );
if( !annotation ){ return null; }
@@ -63,10 +70,13 @@
].join( '' ));
},
+ /** Set up/render a view for each HDA to be shown, init with model and listeners.
+ * In this override, add table header cells to indicate the dataset, annotation columns
+ */
renderHdas : function( $whereTo ){
$whereTo = $whereTo || this.$el;
var hdaViews = readonlyPanel.ReadOnlyHistoryPanel.prototype.renderHdas.call( this, $whereTo );
- $whereTo.find( this.datasetsSelector ).prepend( $( '<tr/>' ).addClass( 'headers' ).append([
+ this.$datasetsList( $whereTo ).prepend( $( '<tr/>' ).addClass( 'headers' ).append([
$( '<th/>' ).text( _l( 'Dataset' ) ),
$( '<th/>' ).text( _l( 'Annotation' ) )
]));
@@ -74,6 +84,9 @@
},
// ------------------------------------------------------------------------ hda sub-views
+ /** attach an hdaView to the panel
+ * In this override, wrap the hdaView in a table row and cell, adding a 2nd cell for the hda annotation
+ */
attachHdaView : function( hdaView, $whereTo ){
$whereTo = $whereTo || this.$el;
// build a row around the dataset with the std hdaView in the first cell and the annotation in the next
@@ -87,7 +100,7 @@
.addClass( stateClass? stateClass.replace( '-', '-color-' ): '' ),
$( '<td/>' ).addClass( 'additional-info' ).text( annotation )
]);
- $whereTo.find( this.datasetsSelector ).append( $tr );
+ this.$datasetsList( $whereTo ).append( $tr );
},
// ------------------------------------------------------------------------ panel events
diff -r 1ed94d89769ed2b51232c6c714c3bb21d93cc3ce -r 707ba3eee2abb828ac262e76c5b9fbbc6c85cd03 static/scripts/mvc/history/current-history-panel.js
--- a/static/scripts/mvc/history/current-history-panel.js
+++ b/static/scripts/mvc/history/current-history-panel.js
@@ -53,10 +53,6 @@
// ......................................................................... SET UP
/** Set up the view, set up storage, bind listeners to HDACollection events
* @param {Object} attributes
- * @config {Object} urlTemplates.hda nested object containing url templates for HDAViews
- * @throws 'needs urlTemplates' if urlTemplates.history or urlTemplates.hda aren't present
- * @see PersistentStorage
- * @see Backbone.View#initialize
*/
initialize : function( attributes ){
attributes = attributes || {};
@@ -68,10 +64,6 @@
}, _.pick( attributes, _.keys( HistoryPanelPrefs.prototype.defaults ) )));
hpanel.HistoryPanel.prototype.initialize.call( this, attributes );
- if( this.model ){
- console.debug( this.model );
- this.model.checkForUpdates();
- }
},
// ------------------------------------------------------------------------ loading history/hda models
@@ -119,10 +111,12 @@
},
/** release/free/shutdown old models and set up panel for new models */
- setModel : function( newHistoryJSON, newHdaJSON, attributes ){
- attributes = attributes || {};
- hpanel.HistoryPanel.prototype.setModel.call( this, newHistoryJSON, newHdaJSON, attributes );
- this.model.checkForUpdates();
+ setModel : function( model, attributes, render ){
+ hpanel.HistoryPanel.prototype.setModel.call( this, model, attributes, render );
+ if( this.model ){
+ this.log( 'checking for updates' );
+ this.model.checkForUpdates();
+ }
return this;
},
@@ -148,7 +142,6 @@
this.removeHdaView( this.hdaViews[ hda.id ] );
}
}, this );
-
},
// ------------------------------------------------------------------------ panel rendering
@@ -172,7 +165,6 @@
// fade out existing, swap with the new, fade in, set up behaviours
$( panel ).queue( 'fx', [
function( next ){
- //panel.$el.fadeTo( panel.fxSpeed, 0.0001, next );
if( speed && panel.$el.is( ':visible' ) ){
panel.$el.fadeOut( speed, next );
} else {
@@ -208,10 +200,19 @@
/** perform additional rendering based on preferences */
renderBasedOnPrefs : function(){
if( this.preferences.get( 'searching' ) ){
- this.showSearchControls( 0 );
+ this.toggleSearchControls( 0, true );
}
},
+ /** In this override, save the search control visibility state to preferences */
+ toggleSearchControls : function( eventOrSpeed, show ){
+ var visible = hpanel.HistoryPanel.prototype.toggleSearchControls.call( this, eventOrSpeed, show );
+ this.preferences.set( 'searching', visible );
+ },
+
+ /** render the tag sub-view controller
+ * In this override, get and set current panel preferences when editor is used
+ */
_renderTags : function( $where ){
var panel = this;
// render tags and show/hide based on preferences
@@ -225,6 +226,9 @@
panel.preferences.set( 'tagsEditorShown', tagsEditor.hidden );
});
},
+ /** render the annotation sub-view controller
+ * In this override, get and set current panel preferences when editor is used
+ */
_renderAnnotation : function( $where ){
var panel = this;
// render annotation and show/hide based on preferences
@@ -300,6 +304,7 @@
}
});
+
//==============================================================================
return {
CurrentHistoryPanel : CurrentHistoryPanel
diff -r 1ed94d89769ed2b51232c6c714c3bb21d93cc3ce -r 707ba3eee2abb828ac262e76c5b9fbbc6c85cd03 static/scripts/mvc/history/history-panel.js
--- a/static/scripts/mvc/history/history-panel.js
+++ b/static/scripts/mvc/history/history-panel.js
@@ -7,7 +7,7 @@
TODO:
============================================================================= */
-/** @class View/Controller for the history model.
+/** @class Editable View/Controller for the history model.
* @name HistoryPanel
*
* Allows:
@@ -15,8 +15,6 @@
* changing the name
* displaying and editing tags and annotations
* multi-selection and operations on mulitple hdas
- * Does not allow:
- * changing the name
*
* @augments Backbone.View
* @borrows LoggableMixin#logger as #logger
@@ -65,8 +63,8 @@
this.model.on( 'change:nice_size', this.updateHistoryDiskSize, this );
- this.model.hdas.on( 'change:deleted', this.handleHdaDeletionChange, this );
- this.model.hdas.on( 'change:visible', this.handleHdaVisibleChange, this );
+ this.model.hdas.on( 'change:deleted', this._handleHdaDeletionChange, this );
+ this.model.hdas.on( 'change:visible', this._handleHdaVisibleChange, this );
this.model.hdas.on( 'change:purged', function( hda ){
// hafta get the new nice-size w/o the purged hda
this.model.fetch();
@@ -74,8 +72,12 @@
},
// ------------------------------------------------------------------------ panel rendering
- /** render with history data */
+ /** render with history data
+ * In this override, add tags, annotations, and multi select
+ * @returns {jQuery} dom fragment as temporary container to be swapped out later
+ */
renderModel : function( ){
+//TODO: can't call ReadOnlyHistoryPanel?
var $newRender = $( '<div/>' );
$newRender.append( HistoryPanel.templates.historyPanel( this.model.toJSON() ) );
@@ -95,6 +97,7 @@
return $newRender;
},
+ /** render the tags sub-view controller */
_renderTags : function( $where ){
var panel = this;
this.tagsEditor = new TagsEditor({
@@ -115,6 +118,7 @@
}).appendTo( $where.find( '.history-secondary-actions' ) )
});
},
+ /** render the annotation sub-view controller */
_renderAnnotation : function( $where ){
var panel = this;
this.annotationEditor = new AnnotationEditor({
@@ -145,11 +149,12 @@
});
},
- /** Set up HistoryPanel js/widget behaviours */
+ /** Set up HistoryPanel js/widget behaviours
+ * In this override, add the multi select popup menu and make the name editable
+ */
_setUpBehaviours : function( $where ){
- //TODO: these should be either sub-MVs, or handled by events
$where = $where || this.$el;
- $where.find( '[title]' ).tooltip({ placement: 'bottom' });
+ readonlyPanel.ReadOnlyHistoryPanel.prototype._setUpBehaviours.call( this, $where );
// anon users shouldn't have access to any of the following
if( !this.model ){
@@ -184,6 +189,9 @@
});
},
+ /** return a new popup menu for choosing a multi selection action
+ * ajax calls made for multiple datasets are queued
+ */
_setUpDatasetActionsPopup : function( $where ){
var panel = this;
( new PopupMenu( $where.find( '.history-dataset-action-popup-btn' ), [
@@ -226,7 +234,7 @@
/** If this hda is deleted and we're not showing deleted hdas, remove the view
* @param {HistoryDataAssociation} the hda to check
*/
- handleHdaDeletionChange : function( hda ){
+ _handleHdaDeletionChange : function( hda ){
if( hda.get( 'deleted' ) && !this.storage.get( 'show_deleted' ) ){
this.removeHdaView( this.hdaViews[ hda.id ] );
} // otherwise, the hdaView rendering should handle it
@@ -236,7 +244,7 @@
/** If this hda is hidden and we're not showing hidden hdas, remove the view
* @param {HistoryDataAssociation} the hda to check
*/
- handleHdaVisibleChange : function( hda ){
+ _handleHdaVisibleChange : function( hda ){
if( hda.hidden() && !this.storage.get( 'show_hidden' ) ){
this.removeHdaView( this.hdaViews[ hda.id ] );
} // otherwise, the hdaView rendering should handle it
@@ -245,7 +253,7 @@
/** Create an HDA view for the given HDA (but leave attachment for addHdaView above)
* @param {HistoryDatasetAssociation} hda
*/
- createHdaView : function( hda ){
+ _createHdaView : function( hda ){
var hdaId = hda.get( 'id' ),
hdaView = new this.HDAViewClass({
model : hda,
@@ -315,7 +323,7 @@
hdaView.remove();
delete panel.hdaViews[ hdaView.model.id ];
if( _.isEmpty( panel.hdaViews ) ){
- panel.$el.find( panel.emptyMsgSelector ).fadeIn( panel.fxSpeed, function(){
+ panel.$emptyMessage().fadeIn( panel.fxSpeed, function(){
panel.trigger( 'empty-history', panel );
});
}
@@ -337,6 +345,7 @@
},
// ........................................................................ multi-select of hdas
+//TODO: to toggle showOrHide pattern
/** show selectors on all visible hdas and associated controls */
showSelectors : function( speed ){
this.selecting = true;
diff -r 1ed94d89769ed2b51232c6c714c3bb21d93cc3ce -r 707ba3eee2abb828ac262e76c5b9fbbc6c85cd03 static/scripts/mvc/history/readonly-history-panel.js
--- a/static/scripts/mvc/history/readonly-history-panel.js
+++ b/static/scripts/mvc/history/readonly-history-panel.js
@@ -15,26 +15,43 @@
},
/** add an hda id to the hash of expanded hdas */
addExpandedHda : function( id ){
- this.save( 'expandedHdas', _.extend( this.get( 'expandedHdas' ), _.object([ id ], [ true ]) ) );
+ var key = 'expandedHdas';
+ this.save( key, _.extend( this.get( key ), _.object([ id ], [ true ]) ) );
},
/** remove an hda id from the hash of expanded hdas */
removeExpandedHda : function( id ){
- this.save( 'expandedHdas', _.omit( this.get( 'expandedHdas' ), id ) );
+ var key = 'expandedHdas';
+ this.save( key, _.omit( this.get( key ), id ) );
},
toString : function(){
return 'HistoryPrefs(' + this.id + ')';
}
});
+// class lvl for access w/o instantiation
+HistoryPrefs.storageKeyPrefix = 'history:';
/** key string to store each histories settings under */
HistoryPrefs.historyStorageKey = function historyStorageKey( historyId ){
- // class lvl for access w/o instantiation
if( !historyId ){
throw new Error( 'HistoryPrefs.historyStorageKey needs valid id: ' + historyId );
}
// single point of change
- return ( 'history:' + historyId );
+ return ( HistoryPrefs.storageKeyPrefix + historyId );
};
+/** return the existing storage for the history with the given id (or create one if it doesn't exist) */
+HistoryPrefs.get = function get( historyId ){
+ return new HistoryPrefs({ id: HistoryPrefs.historyStorageKey( historyId ) });
+};
+/** clear all history related items in sessionStorage */
+HistoryPrefs.clearAll = function clearAll( historyId ){
+ for( var key in sessionStorage ){
+ if( key.indexOf( HistoryPrefs.storageKeyPrefix ) === 0 ){
+ sessionStorage.removeItem( key );
+ }
+ }
+};
+
+
/* =============================================================================
TODO:
@@ -44,6 +61,8 @@
*
* Allows:
* changing the loaded history
+ * searching hdas
+ * displaying data, info, and download
* Does not allow:
* changing the name
*
@@ -55,8 +74,8 @@
var ReadOnlyHistoryPanel = Backbone.View.extend( LoggableMixin ).extend(
/** @lends ReadOnlyHistoryPanel.prototype */{
- ///** logger used to record this.log messages, commonly set to console */
- //// comment this out to suppress log output
+ /** logger used to record this.log messages, commonly set to console */
+ // comment this out to suppress log output
//logger : console,
/** class to use for constructing the HDA views */
@@ -68,15 +87,14 @@
/** (in ms) that jquery effects will use */
fxSpeed : 'fast',
- datasetsSelector : '.datasets-list',
- msgsSelector : '.message-container',
- emptyMsgSelector : '.empty-history-message',
+ /** string to display when the model has no hdas */
emptyMsg : _l( 'This history is empty' ),
+ /** string to no hdas match the search terms */
noneFoundMsg : _l( 'No matching datasets found' ),
// ......................................................................... SET UP
/** Set up the view, set up storage, bind listeners to HDACollection events
- * @param {Object} attributes
+ * @param {Object} attributes optional settings for the panel
*/
initialize : function( attributes ){
attributes = attributes || {};
@@ -86,35 +104,34 @@
}
this.log( this + '.initialize:', attributes );
- // ---- set up instance vars
+ // ---- instance vars
// control contents/behavior based on where (and in what context) the panel is being used
/** where should pages from links be displayed? (default to new tab/window) */
this.linkTarget = attributes.linkTarget || '_blank';
/** how quickly should jquery fx run? */
this.fxSpeed = _.has( attributes, 'fxSpeed' )?( attributes.fxSpeed ):( this.fxSpeed );
+ /** filters for displaying hdas */
+ this.filters = [];
+ this.searchFor = '';
+
+ /** a function to locate the container to scroll to effectively scroll the panel */
+//TODO: rename
+ this.findContainerFn = attributes.findContainerFn;
+ // generally this is $el.parent() - but may be $el or $el.parent().parent() depending on the context
+
// ---- sub views and saved elements
/** map of hda model ids to hda views */
this.hdaViews = {};
/** loading indicator */
this.indicator = new LoadingIndicator( this.$el );
- /** filters for displaying hdas */
- this.filters = [];
- this.searchFor = '';
-
+ // ----- set up panel listeners, handle models passed on init, and call any ready functions
this._setUpListeners();
-
- // ---- handle models passed on init
- if( this.model ){
- if( this.logger ){
- this.model.logger = this.logger;
- }
- this._setUpWebStorage( attributes.initiallyExpanded, attributes.show_deleted, attributes.show_hidden );
- this._setUpModelEventHandlers();
- }
+ // don't render when setting the first time
+ var modelOptions = _.pick( attributes, 'initiallyExpanded', 'show_deleted', 'show_hidden' );
+ this.setModel( this.model, modelOptions, false );
//TODO: remove?
- // ---- and any run functions
if( attributes.onready ){
attributes.onready.call( this );
}
@@ -125,25 +142,23 @@
* @fires: empty-history when switching to a history with no HDAs or creating a new history
*/
_setUpListeners : function(){
- // ---- event handlers for self
this.on( 'error', function( model, xhr, options, msg, details ){
this.errorHandler( model, xhr, options, msg, details );
});
this.on( 'loading-history', function(){
// show the loading indicator when loading a new history starts...
- this.showLoadingIndicator( 'loading history...', 40 );
+ this._showLoadingIndicator( 'loading history...', 40 );
});
this.on( 'loading-done', function(){
// ...hiding it again when loading is done (or there's been an error)
- this.hideLoadingIndicator( 40 );
+ this._hideLoadingIndicator( 40 );
if( _.isEmpty( this.hdaViews ) ){
this.trigger( 'empty-history', this );
}
});
- // throw the first render up as a diff namespace using once
- // (for outside consumption)
+ // throw the first render up as a diff namespace using once (for outside consumption)
this.once( 'rendered', function(){
this.trigger( 'rendered:initial', this );
return false;
@@ -155,6 +170,7 @@
this.log( this + '', arguments );
}, this );
}
+ return this;
},
//TODO: see base-mvc
@@ -166,7 +182,7 @@
//},
// ........................................................................ error handling
- /** Event listener to handle errors (from the panel, the history, or the history's HDAs)
+ /** Event handler for errors (from the panel, the history, or the history's HDAs)
* @param {Model or View} model the (Backbone) source of the error
* @param {XMLHTTPRequest} xhr any ajax obj. assoc. with the error
* @param {Object} options the options map commonly used with bbone ajax
@@ -174,7 +190,8 @@
* @param {Object} msg optional object containing error details
*/
errorHandler : function( model, xhr, options, msg, details ){
- var parsed = this._parseErrorMessage( model, xhr, options, msg, details );
+ console.error( model, xhr, options, msg, details );
+//TODO: getting JSON parse errors from jq migrate
// interrupted ajax
if( xhr && xhr.status === 0 && xhr.readyState === 0 ){
@@ -185,8 +202,9 @@
// otherwise, show an error message inside the panel
} else {
+ var parsed = this._parseErrorMessage( model, xhr, options, msg, details );
// it's possible to have a triggered error before the message container is rendered - wait for it to show
- if( !this.$el.find( this.msgsSelector ).is( ':visible' ) ){
+ if( !this.$messages().is( ':visible' ) ){
this.once( 'rendered', function(){
this.displayMessage( 'error', parsed.message, parsed.details );
});
@@ -239,34 +257,35 @@
/** loads a history & hdas w/ details (but does not make them the current history) */
loadHistoryWithHDADetails : function( historyId, attributes, historyFn, hdaFn ){
//console.info( 'loadHistoryWithHDADetails:', historyId, attributes, historyFn, hdaFn );
- var panel = this,
- // will be called to get hda ids that need details from the api
- hdaDetailIds = function( historyData ){
+ var hdaDetailIds = function( historyData ){
+ // will be called to get hda ids that need details from the api
//TODO: non-visible HDAs are getting details loaded... either stop loading them at all or filter ids thru isVisible
- return panel.getExpandedHdaIds( historyData.id );
+ return _.keys( HistoryPrefs.get( historyData.id ).get( 'expandedHdas' ) );
};
return this.loadHistory( historyId, attributes, historyFn, hdaFn, hdaDetailIds );
},
/** loads a history & hdas w/ NO details (but does not make them the current history) */
loadHistory : function( historyId, attributes, historyFn, hdaFn, hdaDetailIds ){
- this.trigger( 'loading-history', this );
+ var panel = this;
attributes = attributes || {};
- var panel = this;
+
+ panel.trigger( 'loading-history', panel );
//console.info( 'loadHistory:', historyId, attributes, historyFn, hdaFn, hdaDetailIds );
var xhr = historyModel.History.getHistoryData( historyId, {
historyFn : historyFn,
hdaFn : hdaFn,
hdaDetailIds : attributes.initiallyExpanded || hdaDetailIds
});
- return this._loadHistoryFromXHR( xhr, attributes )
+
+ return panel._loadHistoryFromXHR( xhr, attributes )
.fail( function( xhr, where, history ){
// throw an error up for the error handler
panel.trigger( 'error', panel, xhr, attributes, _l( 'An error was encountered while ' + where ),
{ historyId: historyId, history: history || {} });
})
.always( function(){
- // bc hideLoadingIndicator relies on this firing
+ // bc _hideLoadingIndicator relies on this firing
panel.trigger( 'loading-done', panel );
});
},
@@ -275,7 +294,7 @@
_loadHistoryFromXHR : function( xhr, attributes ){
var panel = this;
xhr.then( function( historyJSON, hdaJSON ){
- panel.setModel( historyJSON, hdaJSON, attributes );
+ panel.JSONToModel( historyJSON, hdaJSON, attributes );
});
xhr.fail( function( xhr, where ){
// always render - whether we get a model or not
@@ -284,11 +303,59 @@
return xhr;
},
- /** release/free/shutdown old models and set up panel for new models */
- setModel : function( newHistoryJSON, newHdaJSON, attributes ){
+
+ /** create a new history model from JSON and call setModel on it */
+ JSONToModel : function( newHistoryJSON, newHdaJSON, attributes ){
+ this.log( 'JSONToModel:', newHistoryJSON, newHdaJSON, attributes );
+//TODO: Maybe better in History?
attributes = attributes || {};
- //console.info( 'setModel:', newHistoryJSON, newHdaJSON.length, attributes );
+ //this.log( 'JSONToModel:', newHistoryJSON, newHdaJSON.length, attributes );
+ // set up the new model and render
+ if( Galaxy && Galaxy.currUser ){
+//TODO: global
+ newHistoryJSON.user = Galaxy.currUser.toJSON();
+ }
+ var model = new historyModel.History( newHistoryJSON, newHdaJSON, attributes );
+ this.setModel( model );
+ return this;
+ },
+
+ /** release/free/shutdown old models and set up panel for new models
+ * @fires new-model with the panel as parameter
+ */
+ setModel : function( model, attributes, render ){
+ attributes = attributes || {};
+ render = ( render !== undefined )?( render ):( true );
+ this.log( 'setModel:', model, attributes, render );
+
+ this.freeModel();
+ this.selectedHdaIds = [];
+
+ if( model ){
+ // set up the new model with user, logger, storage, events
+ if( Galaxy && Galaxy.currUser ){
+//TODO: global
+ model.user = Galaxy.currUser.toJSON();
+ }
+ this.model = model;
+ if( this.logger ){
+ this.model.logger = this.logger;
+ }
+ this._setUpWebStorage( attributes.initiallyExpanded, attributes.show_deleted, attributes.show_hidden );
+ this._setUpModelEventHandlers();
+ this.trigger( 'new-model', this );
+ }
+
+ if( render ){
+//TODO: remove?
+ this.render();
+ }
+ return this;
+ },
+
+ /** free the current model and all listeners for it, free any hdaViews for the model */
+ freeModel : function(){
// stop/release the previous model, and clear cache to hda sub-views
if( this.model ){
this.model.clearUpdateTimeout();
@@ -297,22 +364,13 @@
//TODO: see base-mvc
//this.model.free();
}
+ this.freeHdaViews();
+ return this;
+ },
+
+ /** free any hdaViews the panel has */
+ freeHdaViews : function(){
this.hdaViews = {};
-
- // set up the new model and render
- if( Galaxy && Galaxy.currUser ){
-//TODO: global
- newHistoryJSON.user = Galaxy.currUser.toJSON();
- }
- this.model = new historyModel.History( newHistoryJSON, newHdaJSON, attributes );
- if( this.logger ){
- this.model.logger = this.logger;
- }
- this._setUpWebStorage( attributes.initiallyExpanded, attributes.show_deleted, attributes.show_hidden );
- this._setUpModelEventHandlers();
- this.selectedHdaIds = [];
- this.trigger( 'new-model', this );
- this.render();
return this;
},
@@ -324,12 +382,11 @@
* @see PersistentStorage
*/
_setUpWebStorage : function( initiallyExpanded, show_deleted, show_hidden ){
- //console.debug( '_setUpWebStorage', initiallyExpanded, show_deleted, show_hidden );
+ //this.log( '_setUpWebStorage', initiallyExpanded, show_deleted, show_hidden );
this.storage = new HistoryPrefs({
id: HistoryPrefs.historyStorageKey( this.model.get( 'id' ) )
});
-//TODO: move into HistoryPrefs
// expanded Hdas is a map of hda.ids -> a boolean repr'ing whether this hda's body is already expanded
// store any pre-expanded ids passed in
if( _.isObject( initiallyExpanded ) ){
@@ -348,34 +405,7 @@
this.trigger( 'new-storage', this.storage, this );
this.log( this + ' (init\'d) storage:', this.storage.get() );
- },
-
-//TODO: move into HistoryPrefs
- /** clear all stored history panel data */
- clearWebStorage : function(){
- for( var key in sessionStorage ){
- if( key.indexOf( 'history:' ) === 0 ){
- sessionStorage.removeItem( key );
- }
- }
- },
-
-//TODO: move into HistoryPrefs
- /** get all stored data as an Object for a history with the given id */
- getStoredOptions : function( historyId ){
- if( !historyId || historyId === 'current' ){
- return ( this.storage )?( this.storage.get() ):( {} );
- }
- //TODO: make storage engine generic
- var item = sessionStorage.getItem( HistoryPrefs.historyStorageKey( historyId ) );
- return ( item === null )?( {} ):( JSON.parse( item ) );
- },
-
-//TODO: move into HistoryPrefs
- /** get an array of expanded hda ids for the given history id */
- getExpandedHdaIds : function( historyId ){
- var expandedHdas = this.getStoredOptions( historyId ).expandedHdas;
- return (( _.isEmpty( expandedHdas ) )?( [] ):( _.keys( expandedHdas ) ));
+ return this;
},
// ------------------------------------------------------------------------ history/hda event listening
@@ -391,6 +421,7 @@
this.model.on( 'error error:hdas', function( model, xhr, options, msg ){
this.errorHandler( model, xhr, options, msg );
}, this );
+ return this;
},
// ------------------------------------------------------------------------ panel rendering
@@ -399,6 +430,7 @@
* @see Backbone.View#render
*/
render : function( speed, callback ){
+ this.log( 'render:', speed, callback );
// send a speed of 0 to have no fade in/out performed
speed = ( speed === undefined )?( this.fxSpeed ):( speed );
var panel = this,
@@ -414,7 +446,6 @@
// fade out existing, swap with the new, fade in, set up behaviours
$( panel ).queue( 'fx', [
function( next ){
- //panel.$el.fadeTo( panel.fxSpeed, 0.0001, next );
if( speed && panel.$el.is( ':visible' ) ){
panel.$el.fadeOut( speed, next );
} else {
@@ -446,7 +477,9 @@
return this;
},
- /** render with no history data */
+ /** render without history data
+ * @returns {jQuery} dom fragment with message container only
+ */
renderWithoutModel : function( ){
// we'll always need the message container
var $newRender = $( '<div/>' ),
@@ -455,8 +488,11 @@
return $newRender.append( $msgContainer );
},
- /** render with history data */
+ /** render with history data
+ * @returns {jQuery} dom fragment as temporary container to be swapped out later
+ */
renderModel : function( ){
+ // tmp div for final swap in render
var $newRender = $( '<div/>' );
// render based on anonymity, set up behaviors
@@ -485,13 +521,77 @@
//TODO: these should be either sub-MVs, or handled by events
$where = $where || this.$el;
$where.find( '[title]' ).tooltip({ placement: 'bottom' });
+ this._setUpSearchInput( $where.find( '.history-search-controls .history-search-input' ) );
+ return this;
+ },
+
+ // ------------------------------------------------------------------------ sub-$element shortcuts
+ /** the scroll container for this panel - can be $el, $el.parent(), or grandparent depending on context */
+ $container : function(){
+ return ( this.findContainerFn )?( this.findContainerFn.call( this ) ):( this.$el.parent() );
+ },
+ /** where hdaViews are attached */
+ $datasetsList : function( $where ){
+ return ( $where || this.$el ).find( '.datasets-list' );
+ },
+ /** container where panel messages are attached */
+ $messages : function( $where ){
+ return ( $where || this.$el ).find( '.message-container' );
+ },
+ /** the message displayed when no hdaViews can be shown (no hdas, none matching search) */
+ $emptyMessage : function( $where ){
+ return ( $where || this.$el ).find( '.empty-history-message' );
},
// ------------------------------------------------------------------------ hda sub-views
- /** Create an HDA view for the given HDA (but leave attachment for addHdaView above)
+ /** Set up/render a view for each HDA to be shown, init with model and listeners.
+ * HDA views are cached to the map this.hdaViews (using the model.id as key).
+ * @param {jQuery} $whereTo what dom element to prepend the HDA views to
+ * @returns the number of visible hda views
+ */
+ renderHdas : function( $whereTo ){
+ $whereTo = $whereTo || this.$el;
+ var panel = this,
+ newHdaViews = {},
+ // only render the shown hdas
+ //TODO: switch to more general filtered pattern
+ visibleHdas = this.model.hdas.getVisible(
+ this.storage.get( 'show_deleted' ),
+ this.storage.get( 'show_hidden' ),
+ this.filters
+ );
+ //this.log( 'renderHdas, visibleHdas:', visibleHdas, $whereTo );
+//TODO: prepend to sep div, add as one
+
+ this.$datasetsList( $whereTo ).empty();
+
+ if( visibleHdas.length ){
+ visibleHdas.each( function( hda ){
+ // render it (NOTE: reverse order, newest on top (prepend))
+ var hdaId = hda.get( 'id' ),
+ hdaView = panel._createHdaView( hda );
+ newHdaViews[ hdaId ] = hdaView;
+ if( _.contains( panel.selectedHdaIds, hdaId ) ){
+ hdaView.selected = true;
+ }
+ panel.attachHdaView( hdaView.render(), $whereTo );
+ });
+ panel.$emptyMessage( $whereTo ).hide();
+
+ } else {
+ //this.log( 'emptyMsg:', panel.$emptyMessage( $whereTo ) )
+ panel.$emptyMessage( $whereTo )
+ .text( ( this.model.hdas.length && this.searchFor )?( this.noneFoundMsg ):( this.emptyMsg ) )
+ .show();
+ }
+ this.hdaViews = newHdaViews;
+ return this.hdaViews;
+ },
+
+ /** Create an HDA view for the given HDA and set up listeners (but leave attachment for addHdaView)
* @param {HistoryDatasetAssociation} hda
*/
- createHdaView : function( hda ){
+ _createHdaView : function( hda ){
var hdaId = hda.get( 'id' ),
hdaView = new this.HDAViewClass({
model : hda,
@@ -510,66 +610,26 @@
* @param {HDAView} hdaView HDAView (base or edit) to listen to
*/
_setUpHdaListeners : function( hdaView ){
- var historyView = this;
+ var panel = this;
+ hdaView.on( 'error', function( model, xhr, options, msg ){
+ panel.errorHandler( model, xhr, options, msg );
+ });
// maintain a list of hdas whose bodies are expanded
hdaView.on( 'body-expanded', function( id ){
- historyView.storage.addExpandedHda( id );
+ panel.storage.addExpandedHda( id );
});
hdaView.on( 'body-collapsed', function( id ){
- historyView.storage.removeExpandedHda( id );
+ panel.storage.removeExpandedHda( id );
});
- hdaView.on( 'error', function( model, xhr, options, msg ){
- historyView.errorHandler( model, xhr, options, msg );
- });
+ return this;
},
- /** Set up/render a view for each HDA to be shown, init with model and listeners.
- * HDA views are cached to the map this.hdaViews (using the model.id as key).
- * @param {jQuery} $whereTo what dom element to prepend the HDA views to
- * @returns the number of visible hda views
- */
- renderHdas : function( $whereTo ){
- $whereTo = $whereTo || this.$el;
- var historyView = this,
- newHdaViews = {},
- // only render the shown hdas
- //TODO: switch to more general filtered pattern
- visibleHdas = this.model.hdas.getVisible(
- this.storage.get( 'show_deleted' ),
- this.storage.get( 'show_hidden' ),
- this.filters
- );
- //console.debug( 'renderHdas, visibleHdas:', visibleHdas, $whereTo );
-//TODO: prepend to sep div, add as one
- $whereTo.find( this.datasetsSelector ).empty();
-
- if( visibleHdas.length ){
- visibleHdas.each( function( hda ){
- // render it (NOTE: reverse order, newest on top (prepend))
- var hdaId = hda.get( 'id' ),
- hdaView = historyView.createHdaView( hda );
- newHdaViews[ hdaId ] = hdaView;
- if( _.contains( historyView.selectedHdaIds, hdaId ) ){
- hdaView.selected = true;
- }
- historyView.attachHdaView( hdaView.render(), $whereTo );
- });
- $whereTo.find( this.emptyMsgSelector ).hide();
-
- } else {
- //console.debug( 'emptyMsg:', $whereTo.find( this.emptyMsgSelector ) )
- $whereTo.find( this.emptyMsgSelector )
- .text( ( this.model.hdas.length && this.searchFor )?( this.noneFoundMsg ):( this.emptyMsg ) )
- .show();
- }
- this.hdaViews = newHdaViews;
- return this.hdaViews;
- },
-
+ /** attach an hdaView to the panel */
attachHdaView : function( hdaView, $whereTo ){
$whereTo = $whereTo || this.$el;
- var $datasetsList = $whereTo.find( this.datasetsSelector );
+ var $datasetsList = this.$datasetsList( $whereTo );
$datasetsList.prepend( hdaView.$el );
+ return this;
},
/** Add an hda view to the panel for the given hda
@@ -581,13 +641,13 @@
// don't add the view if it wouldn't be visible accrd. to current settings
if( !hda.isVisible( this.storage.get( 'show_deleted' ), this.storage.get( 'show_hidden' ) ) ){
- return;
+ return panel;
}
// create and prepend to current el, if it was empty fadeout the emptyMsg first
$({}).queue([
function fadeOutEmptyMsg( next ){
- var $emptyMsg = panel.$el.find( panel.emptyMsgSelector );
+ var $emptyMsg = panel.$emptyMessage();
if( $emptyMsg.is( ':visible' ) ){
$emptyMsg.fadeOut( panel.fxSpeed, next );
} else {
@@ -595,16 +655,20 @@
}
},
function createAndPrepend( next ){
+ var hdaView = panel._createHdaView( hda );
+ panel.hdaViews[ hda.id ] = hdaView;
+ hdaView.render().$el.hide();
panel.scrollToTop();
- var $whereTo = panel.$el.find( panel.datasetsSelector ),
- hdaView = panel.createHdaView( hda );
- panel.hdaViews[ hda.id ] = hdaView;
- hdaView.render().$el.hide().prependTo( $whereTo ).slideDown( panel.fxSpeed );
+ panel.attachHdaView( hdaView );
+ hdaView.$el.slideDown( panel.fxSpeed );
}
]);
+ return panel;
},
- /** alias to the model. Updates the hda list only (not the history) */
+ //TODO: removeHdaView?
+
+ /** convenience alias to the model. Updates the hda list only (not the history) */
refreshHdas : function( detailIds, options ){
if( this.model ){
return this.model.refresh( detailIds, options );
@@ -613,11 +677,20 @@
return $.when();
},
+ ///** use underscore's findWhere to find a view where the model matches the terms
+ // * note: finds and returns the _first_ matching
+ // */
+ //findHdaView : function( terms ){
+ // if( !this.model || !this.model.hdas.length ){ return undefined; }
+ // var model = this.model.hdas.findWhere( terms );
+ // return ( model )?( this.hdaViews[ model.id ] ):( undefined );
+ //},
+
// ------------------------------------------------------------------------ panel events
/** event map */
events : {
// allow (error) messages to be clicked away
- //TODO: switch to common close (X) idiom
+//TODO: switch to common close (X) idiom
'click .message-container' : 'clearMessages',
'click .history-search-btn' : 'toggleSearchControls'
},
@@ -628,6 +701,7 @@
item.toggleBodyVisibility( null, false );
});
this.storage.set( 'expandedHdas', {} );
+ return this;
},
/** Handle the user toggling the deleted visibility by:
@@ -654,7 +728,7 @@
return this.storage.get( 'show_hidden' );
},
- // ........................................................................ filters
+ // ........................................................................ hda search & filters
/** render a search input for filtering datasets shown
* (see the search section in the HDA model for implementation of the actual searching)
* return will start the search
@@ -662,12 +736,12 @@
* clicking the clear button will clear the search
* uses searchInput in ui.js
*/
- setUpSearchInput : function( $where ){
+ _setUpSearchInput : function( $where ){
var panel = this,
inputSelector = '.history-search-input';
function onFirstSearch( searchFor ){
- //console.debug( 'onFirstSearch', searchFor, panel );
+ //this.log( 'onFirstSearch', searchFor, panel );
if( panel.model.hdas.haveDetails() ){
panel.searchHdas( searchFor );
return;
@@ -692,33 +766,22 @@
});
return $where;
},
-//TODO: to hidden/shown plugin
- showSearchControls : function( speed ){
- speed = ( speed === undefined )?( this.fxSpeed ):( speed );
+ /** toggle showing/hiding the search controls (rendering first on the initial show)
+ * @param {Event or Number} eventOrSpeed variadic - if number the speed of the show/hide effect
+ * @param {boolean} show force show/hide with T/F
+ */
+ toggleSearchControls : function( eventOrSpeed, show ){
var $searchControls = this.$el.find( '.history-search-controls' ),
- $input = $searchControls.find( '.history-search-input' );
- // if it hasn't been rendered - do it now
- if( !$input.children().size() ){
- this.setUpSearchInput( $input );
+ speed = ( jQuery.type( eventOrSpeed ) === 'number' )?( eventOrSpeed ):( this.fxSpeed );
+ show = ( show !== undefined )?( show ):( !$searchControls.is( ':visible' ) );
+ if( show ){
+ $searchControls.slideDown( speed, function(){
+ $( this ).find( 'input' ).focus();
+ });
+ } else {
+ $searchControls.slideUp( speed );
}
- // then slide open, focusing on the input, and persisting the setting when it's done
- $searchControls.slideDown( speed, function(){
- $( this ).find( 'input' ).focus();
- });
- },
- hideSearchControls : function( speed ){
- speed = ( speed === undefined )?( this.fxSpeed ):( speed );
- this.$el.find( '.history-search-controls' ).slideUp( speed );
- },
-
- /** toggle showing/hiding the search controls (rendering first on the initial show) */
- toggleSearchControls : function( eventOrSpeed ){
- speed = ( jQuery.type( eventOrSpeed ) === 'number' )?( eventOrSpeed ):( this.fxSpeed );
- if( this.$el.find( '.history-search-controls' ).is( ':visible' ) ){
- this.hideSearchControls( speed );
- } else {
- this.showSearchControls( speed );
- }
+ return show;
},
/** filter hda view list to those that contain the searchFor terms
@@ -726,26 +789,28 @@
*/
searchHdas : function( searchFor ){
//note: assumes hda details are loaded
- //console.debug( 'onSearch', searchFor, this );
+ //this.log( 'onSearch', searchFor, this );
var panel = this;
this.searchFor = searchFor;
this.filters = [ function( hda ){ return hda.matchesAll( panel.searchFor ); } ];
this.trigger( 'search:searching', searchFor, this );
this.renderHdas();
+ return this;
},
/** clear the search filters and show all views that are normally shown */
clearHdaSearch : function( searchFor ){
- //console.debug( 'onSearchClear', this );
+ //this.log( 'onSearchClear', this );
this.searchFor = '';
this.filters = [];
this.trigger( 'search:clear', this );
this.renderHdas();
+ return this;
},
// ........................................................................ loading indicator
/** hide the panel and display a loading indicator (in the panel's parent) when history model's are switched */
- showLoadingIndicator : function( msg, speed, callback ){
+ _showLoadingIndicator : function( msg, speed, callback ){
speed = ( speed !== undefined )?( speed ):( this.fxSpeed );
if( !this.indicator ){
this.indicator = new LoadingIndicator( this.$el, this.$el.parent() );
@@ -759,7 +824,7 @@
},
/** hide the loading indicator */
- hideLoadingIndicator : function( speed, callback ){
+ _hideLoadingIndicator : function( speed, callback ){
speed = ( speed !== undefined )?( speed ):( this.fxSpeed );
if( this.indicator ){
this.indicator.hide( speed, callback );
@@ -770,16 +835,17 @@
/** Display a message in the top of the panel.
* @param {String} type type of message ('done', 'error', 'warning')
* @param {String} msg the message to display
+ * @param {Object or HTML} modal contents displayed when the user clicks 'details' in the message
*/
displayMessage : function( type, msg, details ){
//precondition: msgContainer must have been rendered even if there's no model
var panel = this;
- //console.debug( 'displayMessage', type, msg, details );
+ //this.log( 'displayMessage', type, msg, details );
this.scrollToTop();
- var $msgContainer = this.$el.find( this.msgsSelector ),
+ var $msgContainer = this.$messages(),
$msg = $( '<div/>' ).addClass( type + 'message' ).html( msg );
- //console.debug( ' ', $msgContainer );
+ //this.log( ' ', $msgContainer );
if( !_.isEmpty( details ) ){
var $detailsLink = $( '<a href="javascript:void(0)">Details</a>' )
@@ -832,47 +898,25 @@
/** Remove all messages from the panel.
*/
clearMessages : function(){
- var $msgContainer = this.$el.find( this.msgsSelector );
- $msgContainer.empty();
+ this.$messages().empty();
+ return this;
},
// ........................................................................ scrolling
/** get the current scroll position of the panel in its parent */
scrollPosition : function(){
- return this.$el.scrollTop();
+ return this.$container().scrollTop();
},
/** set the current scroll position of the panel in its parent */
scrollTo : function( pos ){
- this.$el.scrollTop( pos );
+ this.$container().scrollTop( pos );
return this;
},
/** Scrolls the panel to the top. */
scrollToTop : function(){
- this.$el.scrollTop( 0 );
- return this;
- },
-
- /** Scrolls the panel (the enclosing container - in gen., the page) so that some object
- * is displayed in the vertical middle.
- * NOTE: if no height is given the panel will scroll to objTop (putting it at the top).
- * @param {Number} top top offset of the object to view
- * @param {Number} height height of the object to view
- * @returns {HistoryPanel} the panel
- */
- scrollIntoView : function( top, height ){
- if( height === undefined ){
- this.scrollTo( top );
- return this;
- }
- // otherwise, place the object in the vertical middle
- var viewport = window,
- $panelContainer = this.$el.parent(),
- containerHeight = $( viewport ).innerHeight(),
- middleOffset = ( containerHeight / 2 ) - ( height / 2 );
-
- this.scrollTo( top - middleOffset );
+ this.$container().scrollTop( 0 );
return this;
},
@@ -885,8 +929,9 @@
if( ( !id ) || ( !this.hdaViews[ id ] ) ){
return this;
}
- var $viewEl = this.hdaViews[ id ].$el;
- this.scrollIntoView( $viewEl.offset().top, $viewEl.outerHeight() );
+ var view = this.hdaViews[ id ];
+ //this.scrollIntoView( $viewEl.offset().top );
+ this.scrollTo( view.el.offsetTop );
return this;
},
@@ -901,18 +946,18 @@
return this.scrollToId( hda.id );
},
+ // ........................................................................ misc
/** Return a string rep of the history */
toString : function(){
return 'ReadOnlyHistoryPanel(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';
}
});
-
-
//------------------------------------------------------------------------------ TEMPLATES
ReadOnlyHistoryPanel.templates = {
historyPanel : Handlebars.templates[ 'template-history-historyPanel' ]
};
+
//==============================================================================
return {
ReadOnlyHistoryPanel: ReadOnlyHistoryPanel
diff -r 1ed94d89769ed2b51232c6c714c3bb21d93cc3ce -r 707ba3eee2abb828ac262e76c5b9fbbc6c85cd03 static/scripts/mvc/visualization/scatterplotControlForm.js
--- a/static/scripts/mvc/visualization/scatterplotControlForm.js
+++ /dev/null
@@ -1,631 +0,0 @@
-/* =============================================================================
-todo:
- I'd like to move the svg creation out of the splot constr. to:
- allow adding splots to an existing canvas
- allow mult. splots sharing a canvas
-
-
- outside this:
- BUG: setting width, height in plot controls doesn't re-interpolate data locations!!
- BUG?: get metadata_column_names (from datatype if necessary)
- BUG: single vis in popupmenu should have tooltip with that name NOT 'Visualizations'
-
- wire label setters, anim setter
-
- TwoVarScatterplot:
- ??: maybe better to do this with a canvas...
- save as visualization
- to seperate file?
- remove underscore dependencies
- add interface to change values (seperate)?
- download svg -> base64 encode
- incorporate glyphs, glyph state renderers
-
- ScatterplotSettingsForm:
- some css bug that lowers the width of settings form when plot-controls tab is open
- causes chart to shift
- what can be abstracted/reused for other graphs?
- avoid direct manipulation of this.plot
- allow option to put plot into seperate tab of interface (for small multiples)
-
- provide callback in view to load data incrementally - for large sets
- paginate
- handle rerender
- use endpoint (here and on the server (fileptr))
- fetch (new?) data
- handle rerender
- use d3.TSV?
- render warning on long data (> maxDataPoints)
- adjust endpoint
-
- selectable list of preset column comparisons (rnaseq etc.)
- how to know what sort of Tabular the data is?
- smarter about headers
- validate columns selection (here or server)
-
- set stats column names by selected columns
- move chart into tabbed area...
-
- Scatterplot.mako:
- multiple plots on one page (small multiples)
- ?? ensure svg styles thru d3 or css?
- d3: configable (easily)
- css: standard - better maintenance
- ? override at config
-
-============================================================================= */
-/**
- * Scatterplot control UI as a backbone view
- * handles:
- * getting the desired data
- * configuring the plot display
- * showing (general) statistics
- *
- * initialize attributes REQUIRES a dataset and an apiDatasetsURL
- */
-var ScatterplotControlForm = Backbone.View.extend( LoggableMixin ).extend({
- //logger : console,
- className : 'scatterplot-control-form',
-
- //NOTE: should include time needed to render
- dataLoadDelay : 4000,
- dataLoadSize : 5000,
-
- loadingIndicatorImage : 'loading_small_white_bg.gif',
- fetchMsg : 'Fetching data...',
- renderMsg : 'Rendering...',
-
- initialize : function( attributes ){
- this.log( this + '.initialize, attributes:', attributes );
-
- this.dataset = null;
- this.chartConfig = null;
- this.chart = null;
- this.loader = null;
-
- // set up refs to the four tab areas
- this.$dataControl = null;
- this.$chartControl = null;
- this.$statsDisplay = null;
- this.$chartDisplay = null;
-
- this.dataFetch = null;
-
- this.initializeFromAttributes( attributes );
- this.initializeChart( attributes );
- this.initializeDataLoader( attributes );
- },
-
- initializeFromAttributes : function( attributes ){
- // required settings: ensure certain vars we need are passed in attributes
- if( !attributes || !attributes.dataset ){
- throw( "ScatterplotView requires a dataset" );
- } else {
- this.dataset = attributes.dataset;
- }
- if( jQuery.type( this.dataset.metadata_column_types ) === 'string' ){
- this.dataset.metadata_column_types = this.dataset.metadata_column_types.split( ', ' );
- }
- this.log( '\t dataset:', this.dataset );
-
- // attempt to get possible headers from the data's first line
- if( this.dataset.comment_lines && this.dataset.comment_lines.length ){
- //TODO:??
- var firstLine = this.dataset.comment_lines[0],
- possibleHeaders = firstLine.split( '\t' );
- if( possibleHeaders.length === this.dataset.metadata_column_types.length ){
- this.possibleHeaders = possibleHeaders;
- }
- }
-
- // passed from mako helper
- //TODO: integrate to galaxyPaths
- //TODO: ?? seems like data loader section would be better
- if( !attributes.apiDatasetsURL ){
- throw( "ScatterplotView requires a apiDatasetsURL" );
- } else {
- this.dataURL = attributes.apiDatasetsURL + '/' + this.dataset.id + '?';
- }
- this.log( '\t dataURL:', this.dataURL );
- },
-
- initializeChart : function( attributes ){
- // set up the basic chart infrastructure and config (if any)
- this.chartConfig = attributes.chartConfig || {};
- //if( this.logger ){ this.chartConfig.debugging = true; }
- this.log( '\t initial chartConfig:', this.chartConfig );
-
- this.chart = new TwoVarScatterplot( this.chartConfig );
- //TODO: remove 2nd ref, use this.chart.config
- this.chartConfig = this.chart.config;
- },
-
- initializeDataLoader : function( attributes ){
- // set up data loader
- var view = this;
- this.loader = new LazyDataLoader({
- //logger : ( this.logger )?( this.logger ):( null ),
- // we'll generate this when columns are chosen
- url : null,
- start : attributes.start || 0,
- //NOTE: metadata_data_lines can be null (so we won't know the total)
- total : attributes.total || this.dataset.metadata_data_lines,
- delay : this.dataLoadDelay,
- size : this.dataLoadSize,
-
- 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
- });
- }
- });
- $( this.loader ).bind( 'error', function( event, status, error ){
- view.log( 'ERROR:', status, error );
- alert( 'ERROR fetching data:\n' + status + '\n' + error );
- view.hideLoadingIndicator();
- });
- },
-
- // ------------------------------------------------------------------------- CONTROLS RENDERING
- render : function(){
- this.log( this + '.render' );
-
- // render the tab controls, areas and loading indicator
- this.$el.append( ScatterplotControlForm.templates.mainLayout({
- loadingIndicatorImagePath : galaxy_config.root + 'static/images/' + this.loadingIndicatorImage,
- message : ''
- }));
-
- // render the tab content
- this.$dataControl = this._render_dataControl();
- this.$chartControl = this._render_chartControl();
- this.$statsDisplay = this.$el.find( '.tab-pane#stats-display' );
- this.$chartDisplay = this._render_chartDisplay();
-
- // auto render if given both x, y column choices in query for page
- //TODO:?? add autoRender=1 to query maybe?
- if( this.chartConfig.xColumn && this.chartConfig.yColumn ){
- this.renderChart();
- }
-
- // set up behaviours
- this.$el.find( '[title]' ).tooltip();
-
- // uncomment any of the following to have that tab show on initial load (for testing)
- //this.$el.find( 'ul.nav' ).find( 'a[href="#data-control"]' ).tab( 'show' );
- //this.$el.find( 'ul.nav' ).find( 'a[href="#chart-control"]' ).tab( 'show' );
- //this.$el.find( 'ul.nav' ).find( 'a[href="#stats-display"]' ).tab( 'show' );
- //this.$el.find( 'ul.nav' ).find( 'a[href="#chart-display"]' ).tab( 'show' );
- return this;
- },
-
- _render_dataControl : function(){
- // controls for which columns are used to plot datapoints (and ids/additional info to attach if desired)
- var view = this,
- allColumns = [],
- numericColumns = [],
- usePossibleHeaders = ( this.possibleHeaders && this.$dataControl )?
- ( this.$dataControl.find( '#first-line-header-checkbox' ).is( ':checked' ) ):( false );
-
- // gather column indeces (from metadata_column_types) and names (from metadata_columnnames)
- _.each( this.dataset.metadata_column_types, function( type, index ){
- // use a 1 based index in names/values within the form (will be dec. when parsed out)
- var oneBasedIndex = index + 1,
- // default name is 'column <index>'...
- name = 'column ' + oneBasedIndex;
-
- // ...but label with the name if available...
- if( view.dataset.metadata_column_names ){
- name = view.dataset.metadata_column_names[ index ];
-
- // ...or, use the first line as headers if the user wants
- } else if( usePossibleHeaders ){
- name = view.possibleHeaders[ index ];
- }
-
- // cache all columns here
- allColumns.push({ index: oneBasedIndex, name: name });
-
- // filter numeric columns to their own list
- if( type === 'int' || type === 'float' ){
- numericColumns.push({ index: oneBasedIndex, name: name });
- }
- });
- //TODO: other vals: max_vals, start_val, pagination (chart-settings)
-
- // render the html
- var $dataControl = this.$el.find( '.tab-pane#data-control' );
- $dataControl.html( ScatterplotControlForm.templates.dataControl({
- allColumns : allColumns,
- numericColumns : numericColumns,
- possibleHeaders : ( this.possibleHeaders )?( this.possibleHeaders.join( ', ' ) ):( '' ),
- usePossibleHeaders : usePossibleHeaders
- }));
-
- if( !this.dataset.metadata_column_names && this.possibleHeaders ){
- $dataControl.find( '#first-line-header' ).show();
- }
-
- // preset to column selectors if they were passed in the config in the query string
- $dataControl.find( '#X-select' ).val( this.chartConfig.xColumn );
- $dataControl.find( '#Y-select' ).val( this.chartConfig.yColumn );
- if( this.chartConfig.idColumn !== undefined ){
- $dataControl.find( '#include-id-checkbox' )
- .attr( 'checked', true ).trigger( 'change' );
- $dataControl.find( '#ID-select' ).val( this.chartConfig.idColumn );
- }
-
- return $dataControl;
- },
-
- _render_chartControl : function(){
- // tab content to control how the chart is rendered (data glyph size, chart size, etc.)
- var view = this,
- $chartControl = this.$el.find( '.tab-pane#chart-control' ),
- // limits for controls (by control/chartConfig id)
- //TODO: move into TwoVarScatterplot
- controlRanges = {
- 'datapointSize' : { min: 2, max: 10, step: 1 },
- 'width' : { min: 200, max: 800, step: 20 },
- 'height' : { min: 200, max: 800, step: 20 }
- };
-
- // render the html
- $chartControl.append( ScatterplotControlForm.templates.chartControl( this.chartConfig ) );
-
- // set up behaviours, js on sliders
- $chartControl.find( '.numeric-slider-input' ).each( function(){
- var $this = $( this ),
- $output = $this.find( '.slider-output' ),
- $slider = $this.find( '.slider' ),
- id = $this.attr( 'id' );
- //chartControl.log( 'slider set up', 'this:', $this, 'slider:', $slider, 'id', id );
-
- // what to do when the slider changes: update display and update chartConfig
- //TODO: move out of loop
- function onSliderChange(){
- var $this = $( this ),
- newValue = $this.slider( 'value' );
- //chartControl.log( 'slider change', 'this:', $this, 'output:', $output, 'value', newValue );
- $output.text( newValue );
- //chartControl.chartConfig[ id ] = newValue;
- }
-
- $slider.slider( _.extend( controlRanges[ id ], {
- value : view.chartConfig[ id ],
- change : onSliderChange,
- slide : onSliderChange
- }));
- });
-
- return $chartControl;
- },
-
- _render_chartDisplay : function(){
- // render the tab content where the chart is displayed (but not the chart itself)
- var $chartDisplay = this.$el.find( '.tab-pane#chart-display' );
- $chartDisplay.append( ScatterplotControlForm.templates.chartDisplay( this.chartConfig ) );
- return $chartDisplay;
- },
-
- // ------------------------------------------------------------------------- EVENTS
- events : {
- 'change #include-id-checkbox' : 'toggleThirdColumnSelector',
- 'change #first-line-header-checkbox' : 'rerenderDataControl',
- 'click #data-control #render-button' : 'renderChart',
- 'click #chart-control #render-button' : 'changeChartSettings'
- },
-
- toggleThirdColumnSelector : function(){
- // show/hide the id selector on the data settings panel
- this.$el.find( 'select[name="ID"]' ).parent().toggle();
- },
-
- rerenderDataControl : function(){
- this.$dataControl = this._render_dataControl();
- },
-
- showLoadingIndicator : function( message, callback ){
- // display the loading indicator over the tab panels if hidden, update message (if passed)
- message = message || '';
- var indicator = this.$el.find( 'div#loading-indicator' );
- messageBox = indicator.find( '.loading-message' );
-
- if( indicator.is( ':visible' ) ){
- if( message ){
- messageBox.fadeOut( 'fast', function(){
- messageBox.text( message );
- messageBox.fadeIn( 'fast', callback );
- });
- } else {
- callback();
- }
-
- } else {
- if( message ){ messageBox.text( message ); }
- indicator.fadeIn( 'fast', callback );
- }
- },
-
- hideLoadingIndicator : function( callback ){
- this.$el.find( 'div#loading-indicator' ).fadeOut( 'fast', callback );
- },
-
- // ------------------------------------------------------------------------- CHART/STATS RENDERING
- renderChart : function(){
- // fetch the data, (re-)render the chart
- this.log( this + '.renderChart' );
-
- //TODO: separate data fetch
-
- // this is a complete re-render, so clear the prev. data
- this.data = null;
- this.meta = null;
-
- // update the chartConfig (here and chart) using chart settings
- //TODO: separate and improve (used in changeChartSettings too)
- _.extend( this.chartConfig, this.getChartSettings() );
- this.log( '\t chartConfig:', this.chartConfig );
- this.chart.updateConfig( this.chartConfig, false );
-
- // build the url with the current data settings
- this.loader.url = this.dataURL + '&' + jQuery.param( this.getDataSettings() );
- this.log( '\t loader: total lines:', this.loader.total, ' url:', this.loader.url );
-
- // bind the new data event to: aggregate data, update the chart and stats with new data
- var view = this;
- $( this.loader ).bind( 'loaded.new', function( event, response ){
- view.log( view + ' loaded.new', response );
-
- // aggregate data and meta
- view.postProcessDataFetchResponse( response );
- view.log( '\t postprocessed data:', view.data );
- view.log( '\t postprocessed meta:', view.meta );
-
- // update the chart and stats
- view.showLoadingIndicator( view.renderMsg, function(){
- view.chart.render( view.data, view.meta );
- view.renderStats( view.data, view.meta );
- view.hideLoadingIndicator();
- });
- });
- // when all data loaded - unbind (or we'll start doubling event handlers)
- $( this.loader ).bind( 'complete', function( event, data ){
- view.log( view + ' complete', data );
- $( view.loader ).unbind();
- });
-
- // begin loading the data, switch to the chart display tab
- view.showLoadingIndicator( view.fetchMsg, function(){
- view.$el.find( 'ul.nav' ).find( 'a[href="#chart-display"]' ).tab( 'show' );
- view.loader.load();
- });
- },
-
- renderStats : function(){
- this.log( this + '.renderStats' );
- // render the stats table in the stats panel
- //TODO: there's a better way
- this.$statsDisplay.html( ScatterplotControlForm.templates.statsDisplay({
- stats: [
- { name: 'Count', xval: this.meta[0].count, yval: this.meta[1].count },
- { name: 'Min', xval: this.meta[0].min, yval: this.meta[1].min },
- { name: 'Max', xval: this.meta[0].max, yval: this.meta[1].max },
- { name: 'Sum', xval: this.meta[0].sum, yval: this.meta[1].sum },
- { name: 'Mean', xval: this.meta[0].mean, yval: this.meta[1].mean },
- { name: 'Median', xval: this.meta[0].median, yval: this.meta[1].median }
- ]
- }));
- },
-
- changeChartSettings : function(){
- // re-render the chart with new chart settings and OLD data
- var view = this;
- newChartSettings = this.getChartSettings();
-
- // update the chart config from the chartSettings panel controls
- _.extend( this.chartConfig, newChartSettings );
- this.log( 'this.chartConfig:', this.chartConfig );
- this.chart.updateConfig( this.chartConfig, false );
-
- // if there's current data, call chart.render with it (no data fetch)
- if( view.data && view.meta ){
- view.showLoadingIndicator( view.renderMsg, function(){
- view.$el.find( 'ul.nav' ).find( 'a[href="#chart-display"]' ).tab( 'show' );
- view.chart.render( view.data, view.meta );
- view.hideLoadingIndicator();
- });
-
- // no current data, call renderChart instead (which will fetch data)
- } else {
- this.renderChart();
- }
- },
-
- // ------------------------------------------------------------------------- DATA AGGREGATION
- postProcessDataFetchResponse : function( response ){
- // the loader only returns new data - it's up to this to munge the fetches together properly
- //TODO: we're now storing data in two places: loader and here
- // can't we reduce incoming data into loader.data[0]? are there concurrency problems?
- this.postProcessData( response.data );
- this.postProcessMeta( response.meta );
- },
-
- postProcessData : function( newData ){
- // stack the column data on top of each other into this.data
- //this.log( this + '.postProcessData:', newData );
- var view = this;
-
- // if we already have data: aggregate
- if( view.data ){
- _.each( newData, function( newColData, colIndex ){
- //view.log( colIndex + ' data:', newColData );
- //TODO??: time, space efficiency of this?
- view.data[ colIndex ] = view.data[ colIndex ].concat( newColData );
- });
-
- // otherwise: assign (first load)
- } else {
- view.data = newData;
- }
- },
-
- postProcessMeta : function( newMeta ){
- // munge the meta data (stats) from the server fetches together
- //pre: this.data must be preprocessed (needed for medians)
- //this.log( this + '.postProcessMeta:', newMeta );
- var view = this,
- colTypes = this.dataset.metadata_column_types;
-
- // if we already have meta: aggregate
- if( view.meta ){
- _.each( newMeta, function( newColMeta, colIndex ){
- var colMeta = view.meta[ colIndex ],
- colType = colTypes[ colIndex ];
- //view.log( '\t ' + colIndex + ' postprocessing meta:', newColMeta );
- //view.log( colIndex + ' old meta:',
- // 'min:', colMeta.min,
- // 'max:', colMeta.max,
- // 'sum:', colMeta.sum,
- // 'mean:', colMeta.mean,
- // 'median:', colMeta.median
- //);
-
- //!TODO: at what point are we getting int/float overflow on these?!
- //??: need to be null safe?
- colMeta.count += ( newColMeta.count )?( newColMeta.count ):( 0 );
- //view.log( colIndex, 'count:', colMeta.count );
-
- if( ( colType === 'int' ) || ( colType === 'float' ) ){
- //view.log( colIndex + ' incoming meta:',
- // 'min:', newColMeta.min,
- // 'max:', newColMeta.max,
- // 'sum:', newColMeta.sum,
- // 'mean:', newColMeta.mean,
- // 'median:', newColMeta.median
- //);
-
- colMeta.min = Math.min( newColMeta.min, colMeta.min );
- colMeta.max = Math.max( newColMeta.max, colMeta.max );
- colMeta.sum = newColMeta.sum + colMeta.sum;
- colMeta.mean = ( colMeta.count )?( colMeta.sum / colMeta.count ):( null );
-
- // median's a pain bc of sorting (requires the data as well)
- var sortedCol = view.data[ colIndex ].slice().sort(),
- middleIndex = Math.floor( sortedCol.length / 2 );
-
- if( sortedCol.length % 2 === 0 ){
- colMeta.median = ( ( sortedCol[ middleIndex ] + sortedCol[( middleIndex + 1 )] ) / 2 );
-
- } else {
- colMeta.median = sortedCol[ middleIndex ];
- }
-
- //view.log( colIndex + ' new meta:',
- // 'min:', colMeta.min,
- // 'max:', colMeta.max,
- // 'sum:', colMeta.sum,
- // 'mean:', colMeta.mean,
- // 'median:', colMeta.median
- //);
- }
- });
-
- // otherwise: assign (first load)
- } else {
- view.meta = newMeta;
- //view.log( '\t meta (first load):', view.meta );
- }
- },
-
- // ------------------------------------------------------------------------- GET DATA/CHART SETTINGS
- getDataSettings : function(){
- // parse the column values for both indeces (for the data fetch) and names (for the chart)
- var columnSelections = this.getColumnSelections(),
- columns = [];
- this.log( '\t columnSelections:', columnSelections );
-
- //TODO: validate columns - minimally: we can assume either set by selectors or via a good query string
-
- // get column indices for params, include the desired ID column (if any)
- //NOTE: these are presented in human-readable 1 base index (to match the data.peek) - adjust
- columns = [
- columnSelections.X.colIndex - 1,
- columnSelections.Y.colIndex - 1
- ];
- if( this.$dataControl.find( '#include-id-checkbox' ).attr( 'checked' ) ){
- columns.push( columnSelections.ID.colIndex - 1 );
- }
- //TODO: other vals: max, start, page
-
- var params = {
- data_type : 'raw_data',
- provider : 'column_with_stats',
- columns : '[' + columns + ']'
- };
- this.log( '\t data settings (url params):', params );
- return params;
- },
-
- getColumnSelections : function(){
- // gets the current user-selected values for which columns to fetch from the data settings panel
- // returns a map: { column-select name (eg. X) : { colIndex : column-selector val,
- // colName : selected option text }, ... }
- var selections = {};
- this.$dataControl.find( 'div.column-select select' ).each( function(){
- var $this = $( this ),
- val = $this.val();
- selections[ $this.attr( 'name' ) ] = {
- colIndex : val,
- colName : $this.children( '[value="' + val + '"]' ).text()
- };
- });
- return selections;
- },
-
- getChartSettings : function(){
- // gets the user-selected chartConfig from the chart settings panel
- var settings = {},
- colSelections = this.getColumnSelections();
- //this.log( 'colSelections:', colSelections );
-
- //TODO: simplify with keys and loop
- settings.datapointSize = this.$chartControl.find( '#datapointSize.numeric-slider-input' )
- .find( '.slider' ).slider( 'value' );
- settings.width = this.$chartControl.find( '#width.numeric-slider-input' )
- .find( '.slider' ).slider( 'value' );
- settings.height = this.$chartControl.find( '#height.numeric-slider-input' )
- .find( '.slider' ).slider( 'value' );
-
- // update axes labels using chartSettings inputs (if not at defaults), otherwise the selects' colName
- //TODO: a little confusing
- var chartSettingsXLabel = this.$chartControl.find( 'input#X-axis-label' ).val(),
- chartSettingsYLabel = this.$chartControl.find( 'input#Y-axis-label' ).val();
- settings.xLabel = ( chartSettingsXLabel === 'X' )?
- ( colSelections.X.colName ):( chartSettingsXLabel );
- settings.yLabel = ( chartSettingsYLabel === 'Y' )?
- ( colSelections.Y.colName ):( chartSettingsYLabel );
-
- settings.animDuration = ( this.$chartControl.find( '#animate-chart' ).is( ':checked' ) )?
- ( this.chart.defaults.animDuration ):( 0 );
-
- this.log( '\t chartSettings:', settings );
- return settings;
- },
-
- toString : function(){
- return 'ScatterplotControlForm(' + (( this.dataset )?( this.dataset.id ):( '' )) + ')';
- }
-});
-
-ScatterplotControlForm.templates = {
- mainLayout : Handlebars.templates[ 'template-visualization-scatterplotControlForm' ],
- dataControl : Handlebars.templates[ 'template-visualization-dataControl' ],
- chartControl : Handlebars.templates[ 'template-visualization-chartControl' ],
- statsDisplay : Handlebars.templates[ 'template-visualization-statsDisplay' ],
- chartDisplay : Handlebars.templates[ 'template-visualization-chartDisplay' ]
-};
-
-//==============================================================================
diff -r 1ed94d89769ed2b51232c6c714c3bb21d93cc3ce -r 707ba3eee2abb828ac262e76c5b9fbbc6c85cd03 static/scripts/packed/mvc/history/annotated-history-panel.js
--- a/static/scripts/packed/mvc/history/annotated-history-panel.js
+++ b/static/scripts/packed/mvc/history/annotated-history-panel.js
@@ -1,1 +1,1 @@
-define(["mvc/dataset/hda-model","mvc/dataset/hda-base","mvc/history/readonly-history-panel"],function(c,a,b){var d=b.ReadOnlyHistoryPanel.extend({className:"annotated-history-panel",HDAViewClass:a.HDABaseView,renderModel:function(){this.$el.addClass(this.className);var g=b.ReadOnlyHistoryPanel.prototype.renderModel.call(this),e=g.find(this.datasetsSelector),f=$("<table/>").addClass("datasets-list datasets-table");f.append(e.children());g.find(this.datasetsSelector).replaceWith(f);g.find(".history-subtitle").after(this.renderHistoryAnnotation());g.find(".history-search-btn").hide();g.find(".history-controls").after(g.find(".history-search-controls").show());this.setUpSearchInput(g.find(".history-search-input"));return g},renderHistoryAnnotation:function(){var e=this.model.get("annotation");if(!e){return null}return $(['<div class="history-annotation">',e,"</div>"].join(""))},renderHdas:function(f){f=f||this.$el;var e=b.ReadOnlyHistoryPanel.prototype.renderHdas.call(this,f);f.find(this.datasetsSelector).prepend($("<tr/>").addClass("headers").append([$("<th/>").text(_l("Dataset")),$("<th/>").text(_l("Annotation"))]));return e},attachHdaView:function(h,f){f=f||this.$el;var i=_.find(h.el.classList,function(j){return(/^state\-/).test(j)}),e=h.model.get("annotation")||"",g=$("<tr/>").addClass("dataset-row").append([$("<td/>").addClass("dataset-container").append(h.$el).addClass(i?i.replace("-","-color-"):""),$("<td/>").addClass("additional-info").text(e)]);f.find(this.datasetsSelector).append(g)},events:_.extend(_.clone(b.ReadOnlyHistoryPanel.prototype.events),{"click tr":function(e){$(e.currentTarget).find(".dataset-title-bar").click()},"click .icon-btn":function(e){e.stopPropagation()}}),toString:function(){return"AnnotatedHistoryPanel("+((this.model)?(this.model.get("name")):(""))+")"}});return{AnnotatedHistoryPanel:d}});
\ No newline at end of file
+define(["mvc/dataset/hda-model","mvc/dataset/hda-base","mvc/history/readonly-history-panel"],function(c,a,b){var d=b.ReadOnlyHistoryPanel.extend({className:"annotated-history-panel",HDAViewClass:a.HDABaseView,renderModel:function(){this.$el.addClass(this.className);var g=b.ReadOnlyHistoryPanel.prototype.renderModel.call(this),e=this.$datasetsList(g),f=$("<table/>").addClass("datasets-list datasets-table");f.append(e.children());e.replaceWith(f);g.find(".history-subtitle").after(this.renderHistoryAnnotation());g.find(".history-search-btn").hide();g.find(".history-controls").after(g.find(".history-search-controls").show());return g},renderHistoryAnnotation:function(){var e=this.model.get("annotation");if(!e){return null}return $(['<div class="history-annotation">',e,"</div>"].join(""))},renderHdas:function(f){f=f||this.$el;var e=b.ReadOnlyHistoryPanel.prototype.renderHdas.call(this,f);this.$datasetsList(f).prepend($("<tr/>").addClass("headers").append([$("<th/>").text(_l("Dataset")),$("<th/>").text(_l("Annotation"))]));return e},attachHdaView:function(h,f){f=f||this.$el;var i=_.find(h.el.classList,function(j){return(/^state\-/).test(j)}),e=h.model.get("annotation")||"",g=$("<tr/>").addClass("dataset-row").append([$("<td/>").addClass("dataset-container").append(h.$el).addClass(i?i.replace("-","-color-"):""),$("<td/>").addClass("additional-info").text(e)]);this.$datasetsList(f).append(g)},events:_.extend(_.clone(b.ReadOnlyHistoryPanel.prototype.events),{"click tr":function(e){$(e.currentTarget).find(".dataset-title-bar").click()},"click .icon-btn":function(e){e.stopPropagation()}}),toString:function(){return"AnnotatedHistoryPanel("+((this.model)?(this.model.get("name")):(""))+")"}});return{AnnotatedHistoryPanel:d}});
\ No newline at end of file
diff -r 1ed94d89769ed2b51232c6c714c3bb21d93cc3ce -r 707ba3eee2abb828ac262e76c5b9fbbc6c85cd03 static/scripts/packed/mvc/history/current-history-panel.js
--- a/static/scripts/packed/mvc/history/current-history-panel.js
+++ b/static/scripts/packed/mvc/history/current-history-panel.js
@@ -1,1 +1,1 @@
-define(["mvc/dataset/hda-edit","mvc/history/history-panel"],function(b,e){var c=SessionStorageModel.extend({defaults:{searching:false,tagsEditorShown:false,annotationEditorShown:false},toString:function(){return"HistoryPanelPrefs("+JSON.stringify(this.toJSON())+")"}});c.storageKey=function d(){return("history-panel")};var a=e.HistoryPanel.extend({HDAViewClass:b.HDAEditView,emptyMsg:_l("This history is empty. Click 'Get Data' on the left pane to start"),noneFoundMsg:_l("No matching datasets found"),initialize:function(f){f=f||{};this.preferences=new c(_.extend({id:c.storageKey()},_.pick(f,_.keys(c.prototype.defaults))));e.HistoryPanel.prototype.initialize.call(this,f);if(this.model){console.debug(this.model);this.model.checkForUpdates()}},loadCurrentHistory:function(g){var f=this;return this.loadHistoryWithHDADetails("current",g).then(function(i,h){f.trigger("current-history",f)})},switchToHistory:function(i,h){var f=this,g=function(){return jQuery.post(galaxy_config.root+"api/histories/"+i+"/set_as_current")};return this.loadHistoryWithHDADetails(i,h,g).then(function(k,j){f.trigger("switched-history",f)})},createNewHistory:function(h){if(!Galaxy||!Galaxy.currUser||Galaxy.currUser.isAnonymous()){this.displayMessage("error",_l("You must be logged in to create histories"));return $.when()}var f=this,g=function(){return jQuery.post(galaxy_config.root+"api/histories",{current:true})};return this.loadHistory(undefined,h,g).then(function(j,i){f.trigger("new-history",f)})},setModel:function(h,f,g){g=g||{};e.HistoryPanel.prototype.setModel.call(this,h,f,g);this.model.checkForUpdates();return this},_setUpModelEventHandlers:function(){e.HistoryPanel.prototype._setUpModelEventHandlers.call(this);if(Galaxy&&Galaxy.quotaMeter){this.listenTo(this.model,"change:nice_size",function(){Galaxy.quotaMeter.update()})}this.model.hdas.on("state:ready",function(g,h,f){if((!g.get("visible"))&&(!this.storage.get("show_hidden"))){this.removeHdaView(this.hdaViews[g.id])}},this)},render:function(h,i){h=(h===undefined)?(this.fxSpeed):(h);var f=this,g;if(this.model){g=this.renderModel()}else{g=this.renderWithoutModel()}$(f).queue("fx",[function(j){if(h&&f.$el.is(":visible")){f.$el.fadeOut(h,j)}else{j()}},function(j){f.$el.empty();if(g){f.$el.append(g.children());f.renderBasedOnPrefs()}j()},function(j){if(h&&!f.$el.is(":visible")){f.$el.fadeIn(h,j)}else{j()}},function(j){if(i){i.call(this)}f.trigger("rendered",this);j()}]);return this},renderBasedOnPrefs:function(){if(this.preferences.get("searching")){this.showSearchControls(0)}},_renderTags:function(f){var g=this;e.HistoryPanel.prototype._renderTags.call(this,f);if(this.preferences.get("tagsEditorShown")){this.tagsEditor.toggle(true)}this.tagsEditor.on("hiddenUntilActivated:shown hiddenUntilActivated:hidden",function(h){g.preferences.set("tagsEditorShown",h.hidden)})},_renderAnnotation:function(f){var g=this;e.HistoryPanel.prototype._renderAnnotation.call(this,f);if(this.preferences.get("annotationEditorShown")){this.annotationEditor.toggle(true)}this.annotationEditor.on("hiddenUntilActivated:shown hiddenUntilActivated:hidden",function(h){g.preferences.set("annotationEditorShown",h.hidden)})},connectToQuotaMeter:function(f){if(!f){return this}this.listenTo(f,"quota:over",this.showQuotaMessage);this.listenTo(f,"quota:under",this.hideQuotaMessage);this.on("rendered rendered:initial",function(){if(f&&f.isOverQuota()){this.showQuotaMessage()}});return this},showQuotaMessage:function(){var f=this.$el.find(".quota-message");if(f.is(":hidden")){f.slideDown(this.fxSpeed)}},hideQuotaMessage:function(){var f=this.$el.find(".quota-message");if(!f.is(":hidden")){f.slideUp(this.fxSpeed)}},connectToOptionsMenu:function(f){if(!f){return this}this.on("new-storage",function(h,g){if(f&&h){f.findItemByHtml(_l("Include Deleted Datasets")).checked=h.get("show_deleted");f.findItemByHtml(_l("Include Hidden Datasets")).checked=h.get("show_hidden")}});return this},toString:function(){return"CurrentHistoryPanel("+((this.model)?(this.model.get("name")):(""))+")"}});return{CurrentHistoryPanel:a}});
\ No newline at end of file
+define(["mvc/dataset/hda-edit","mvc/history/history-panel"],function(b,e){var c=SessionStorageModel.extend({defaults:{searching:false,tagsEditorShown:false,annotationEditorShown:false},toString:function(){return"HistoryPanelPrefs("+JSON.stringify(this.toJSON())+")"}});c.storageKey=function d(){return("history-panel")};var a=e.HistoryPanel.extend({HDAViewClass:b.HDAEditView,emptyMsg:_l("This history is empty. Click 'Get Data' on the left pane to start"),noneFoundMsg:_l("No matching datasets found"),initialize:function(f){f=f||{};this.preferences=new c(_.extend({id:c.storageKey()},_.pick(f,_.keys(c.prototype.defaults))));e.HistoryPanel.prototype.initialize.call(this,f)},loadCurrentHistory:function(g){var f=this;return this.loadHistoryWithHDADetails("current",g).then(function(i,h){f.trigger("current-history",f)})},switchToHistory:function(i,h){var f=this,g=function(){return jQuery.post(galaxy_config.root+"api/histories/"+i+"/set_as_current")};return this.loadHistoryWithHDADetails(i,h,g).then(function(k,j){f.trigger("switched-history",f)})},createNewHistory:function(h){if(!Galaxy||!Galaxy.currUser||Galaxy.currUser.isAnonymous()){this.displayMessage("error",_l("You must be logged in to create histories"));return $.when()}var f=this,g=function(){return jQuery.post(galaxy_config.root+"api/histories",{current:true})};return this.loadHistory(undefined,h,g).then(function(j,i){f.trigger("new-history",f)})},setModel:function(g,f,h){e.HistoryPanel.prototype.setModel.call(this,g,f,h);if(this.model){this.log("checking for updates");this.model.checkForUpdates()}return this},_setUpModelEventHandlers:function(){e.HistoryPanel.prototype._setUpModelEventHandlers.call(this);if(Galaxy&&Galaxy.quotaMeter){this.listenTo(this.model,"change:nice_size",function(){Galaxy.quotaMeter.update()})}this.model.hdas.on("state:ready",function(g,h,f){if((!g.get("visible"))&&(!this.storage.get("show_hidden"))){this.removeHdaView(this.hdaViews[g.id])}},this)},render:function(h,i){h=(h===undefined)?(this.fxSpeed):(h);var f=this,g;if(this.model){g=this.renderModel()}else{g=this.renderWithoutModel()}$(f).queue("fx",[function(j){if(h&&f.$el.is(":visible")){f.$el.fadeOut(h,j)}else{j()}},function(j){f.$el.empty();if(g){f.$el.append(g.children());f.renderBasedOnPrefs()}j()},function(j){if(h&&!f.$el.is(":visible")){f.$el.fadeIn(h,j)}else{j()}},function(j){if(i){i.call(this)}f.trigger("rendered",this);j()}]);return this},renderBasedOnPrefs:function(){if(this.preferences.get("searching")){this.toggleSearchControls(0,true)}},toggleSearchControls:function(g,f){var h=e.HistoryPanel.prototype.toggleSearchControls.call(this,g,f);this.preferences.set("searching",h)},_renderTags:function(f){var g=this;e.HistoryPanel.prototype._renderTags.call(this,f);if(this.preferences.get("tagsEditorShown")){this.tagsEditor.toggle(true)}this.tagsEditor.on("hiddenUntilActivated:shown hiddenUntilActivated:hidden",function(h){g.preferences.set("tagsEditorShown",h.hidden)})},_renderAnnotation:function(f){var g=this;e.HistoryPanel.prototype._renderAnnotation.call(this,f);if(this.preferences.get("annotationEditorShown")){this.annotationEditor.toggle(true)}this.annotationEditor.on("hiddenUntilActivated:shown hiddenUntilActivated:hidden",function(h){g.preferences.set("annotationEditorShown",h.hidden)})},connectToQuotaMeter:function(f){if(!f){return this}this.listenTo(f,"quota:over",this.showQuotaMessage);this.listenTo(f,"quota:under",this.hideQuotaMessage);this.on("rendered rendered:initial",function(){if(f&&f.isOverQuota()){this.showQuotaMessage()}});return this},showQuotaMessage:function(){var f=this.$el.find(".quota-message");if(f.is(":hidden")){f.slideDown(this.fxSpeed)}},hideQuotaMessage:function(){var f=this.$el.find(".quota-message");if(!f.is(":hidden")){f.slideUp(this.fxSpeed)}},connectToOptionsMenu:function(f){if(!f){return this}this.on("new-storage",function(h,g){if(f&&h){f.findItemByHtml(_l("Include Deleted Datasets")).checked=h.get("show_deleted");f.findItemByHtml(_l("Include Hidden Datasets")).checked=h.get("show_hidden")}});return this},toString:function(){return"CurrentHistoryPanel("+((this.model)?(this.model.get("name")):(""))+")"}});return{CurrentHistoryPanel:a}});
\ No newline at end of file
diff -r 1ed94d89769ed2b51232c6c714c3bb21d93cc3ce -r 707ba3eee2abb828ac262e76c5b9fbbc6c85cd03 static/scripts/packed/mvc/history/history-panel.js
--- a/static/scripts/packed/mvc/history/history-panel.js
+++ b/static/scripts/packed/mvc/history/history-panel.js
@@ -1,1 +1,1 @@
-define(["mvc/dataset/hda-model","mvc/dataset/hda-edit","mvc/history/readonly-history-panel"],function(c,a,b){var d=b.ReadOnlyHistoryPanel.extend({HDAViewClass:a.HDAEditView,initialize:function(e){e=e||{};this.selectedHdaIds=[];this.tagsEditor=null;this.annotationEditor=null;this.selecting=e.selecting||false;this.annotationEditorShown=e.annotationEditorShown||false;this.tagsEditorShown=e.tagsEditorShown||false;b.ReadOnlyHistoryPanel.prototype.initialize.call(this,e)},_setUpModelEventHandlers:function(){b.ReadOnlyHistoryPanel.prototype._setUpModelEventHandlers.call(this);this.model.on("change:nice_size",this.updateHistoryDiskSize,this);this.model.hdas.on("change:deleted",this.handleHdaDeletionChange,this);this.model.hdas.on("change:visible",this.handleHdaVisibleChange,this);this.model.hdas.on("change:purged",function(e){this.model.fetch()},this)},renderModel:function(){var e=$("<div/>");e.append(d.templates.historyPanel(this.model.toJSON()));if(Galaxy.currUser.id&&Galaxy.currUser.id===this.model.get("user_id")){this._renderTags(e);this._renderAnnotation(e)}e.find(".history-secondary-actions").prepend(this._renderSelectButton());e.find(".history-dataset-actions").toggle(this.selecting);e.find(".history-secondary-actions").prepend(this._renderSearchButton());this._setUpBehaviours(e);this.renderHdas(e);return e},_renderTags:function(e){var f=this;this.tagsEditor=new TagsEditor({model:this.model,el:e.find(".history-controls .tags-display"),onshowFirstTime:function(){this.render()},onshow:function(){f.toggleHDATagEditors(true,f.fxSpeed)},onhide:function(){f.toggleHDATagEditors(false,f.fxSpeed)},$activator:faIconButton({title:_l("Edit history tags"),classes:"history-tag-btn",faIcon:"fa-tags"}).appendTo(e.find(".history-secondary-actions"))})},_renderAnnotation:function(e){var f=this;this.annotationEditor=new AnnotationEditor({model:this.model,el:e.find(".history-controls .annotation-display"),onshowFirstTime:function(){this.render()},onshow:function(){f.toggleHDAAnnotationEditors(true,f.fxSpeed)},onhide:function(){f.toggleHDAAnnotationEditors(false,f.fxSpeed)},$activator:faIconButton({title:_l("Edit history Annotation"),classes:"history-annotate-btn",faIcon:"fa-comment"}).appendTo(e.find(".history-secondary-actions"))})},_renderSelectButton:function(e){return faIconButton({title:_l("Operations on multiple datasets"),classes:"history-select-btn",faIcon:"fa-check-square-o"})},_setUpBehaviours:function(e){e=e||this.$el;e.find("[title]").tooltip({placement:"bottom"});if(!this.model){return}this._setUpDatasetActionsPopup(e);if((!Galaxy.currUser||Galaxy.currUser.isAnonymous())||(Galaxy.currUser.id!==this.model.get("user_id"))){return}var f=this;e.find(".history-name").attr("title",_l("Click to rename history")).tooltip({placement:"bottom"}).make_text_editable({on_finish:function(g){var h=f.model.get("name");if(g&&g!==h){f.$el.find(".history-name").text(g);f.model.save({name:g}).fail(function(){f.$el.find(".history-name").text(f.model.previous("name"))})}else{f.$el.find(".history-name").text(h)}}})},_setUpDatasetActionsPopup:function(e){var f=this;(new PopupMenu(e.find(".history-dataset-action-popup-btn"),[{html:_l("Hide datasets"),func:function(){var g=c.HistoryDatasetAssociation.prototype.hide;f.getSelectedHdaCollection().ajaxQueue(g)}},{html:_l("Unhide datasets"),func:function(){var g=c.HistoryDatasetAssociation.prototype.unhide;f.getSelectedHdaCollection().ajaxQueue(g)}},{html:_l("Delete datasets"),func:function(){var g=c.HistoryDatasetAssociation.prototype["delete"];f.getSelectedHdaCollection().ajaxQueue(g)}},{html:_l("Undelete datasets"),func:function(){var g=c.HistoryDatasetAssociation.prototype.undelete;f.getSelectedHdaCollection().ajaxQueue(g)}},{html:_l("Permanently delete datasets"),func:function(){if(confirm(_l("This will permanently remove the data in your datasets. Are you sure?"))){var g=c.HistoryDatasetAssociation.prototype.purge;f.getSelectedHdaCollection().ajaxQueue(g)}}}]))},handleHdaDeletionChange:function(e){if(e.get("deleted")&&!this.storage.get("show_deleted")){this.removeHdaView(this.hdaViews[e.id])}},handleHdaVisibleChange:function(e){if(e.hidden()&&!this.storage.get("show_hidden")){this.removeHdaView(this.hdaViews[e.id])}},createHdaView:function(f){var e=f.get("id"),g=new this.HDAViewClass({model:f,linkTarget:this.linkTarget,expanded:this.storage.get("expandedHdas")[e],selectable:this.selecting,hasUser:this.model.ownedByCurrUser(),logger:this.logger,tagsEditorShown:(this.tagsEditor&&!this.tagsEditor.hidden),annotationEditorShown:(this.annotationEditor&&!this.annotationEditor.hidden)});this._setUpHdaListeners(g);return g},_setUpHdaListeners:function(f){var e=this;b.ReadOnlyHistoryPanel.prototype._setUpHdaListeners.call(this,f);f.on("selected",function(g){var h=g.model.get("id");e.selectedHdaIds=_.union(e.selectedHdaIds,[h])});f.on("de-selected",function(g){var h=g.model.get("id");e.selectedHdaIds=_.without(e.selectedHdaIds,h)})},toggleHDATagEditors:function(e){var f=arguments;_.each(this.hdaViews,function(g){if(g.tagsEditor){g.tagsEditor.toggle.apply(g.tagsEditor,f)}})},toggleHDAAnnotationEditors:function(e){var f=arguments;_.each(this.hdaViews,function(g){if(g.annotationEditor){g.annotationEditor.toggle.apply(g.annotationEditor,f)}})},removeHdaView:function(f){if(!f){return}var e=this;f.$el.fadeOut(e.fxSpeed,function(){f.off();f.remove();delete e.hdaViews[f.model.id];if(_.isEmpty(e.hdaViews)){e.$el.find(e.emptyMsgSelector).fadeIn(e.fxSpeed,function(){e.trigger("empty-history",e)})}})},events:_.extend(_.clone(b.ReadOnlyHistoryPanel.prototype.events),{"click .history-select-btn":function(f){this.toggleSelectors(this.fxSpeed)},"click .history-select-all-datasets-btn":"selectAllDatasets","click .history-deselect-all-datasets-btn":"deselectAllDatasets"}),updateHistoryDiskSize:function(){this.$el.find(".history-size").text(this.model.get("nice_size"))},showSelectors:function(e){this.selecting=true;this.$el.find(".history-dataset-actions").slideDown(e);_.each(this.hdaViews,function(f){f.showSelector(e)});this.selectedHdaIds=[]},hideSelectors:function(e){this.selecting=false;this.$el.find(".history-dataset-actions").slideUp(e);_.each(this.hdaViews,function(f){f.hideSelector(e)});this.selectedHdaIds=[]},toggleSelectors:function(e){if(!this.selecting){this.showSelectors(e)}else{this.hideSelectors(e)}},selectAllDatasets:function(e){_.each(this.hdaViews,function(f){f.select(e)})},deselectAllDatasets:function(e){_.each(this.hdaViews,function(f){f.deselect(e)})},getSelectedHdaViews:function(){return _.filter(this.hdaViews,function(e){return e.selected})},getSelectedHdaCollection:function(){return new c.HDACollection(_.map(this.getSelectedHdaViews(),function(e){return e.model}),{historyId:this.model.id})},toString:function(){return"HistoryPanel("+((this.model)?(this.model.get("name")):(""))+")"}});return{HistoryPanel:d}});
\ No newline at end of file
+define(["mvc/dataset/hda-model","mvc/dataset/hda-edit","mvc/history/readonly-history-panel"],function(c,a,b){var d=b.ReadOnlyHistoryPanel.extend({HDAViewClass:a.HDAEditView,initialize:function(e){e=e||{};this.selectedHdaIds=[];this.tagsEditor=null;this.annotationEditor=null;this.selecting=e.selecting||false;this.annotationEditorShown=e.annotationEditorShown||false;this.tagsEditorShown=e.tagsEditorShown||false;b.ReadOnlyHistoryPanel.prototype.initialize.call(this,e)},_setUpModelEventHandlers:function(){b.ReadOnlyHistoryPanel.prototype._setUpModelEventHandlers.call(this);this.model.on("change:nice_size",this.updateHistoryDiskSize,this);this.model.hdas.on("change:deleted",this._handleHdaDeletionChange,this);this.model.hdas.on("change:visible",this._handleHdaVisibleChange,this);this.model.hdas.on("change:purged",function(e){this.model.fetch()},this)},renderModel:function(){var e=$("<div/>");e.append(d.templates.historyPanel(this.model.toJSON()));if(Galaxy.currUser.id&&Galaxy.currUser.id===this.model.get("user_id")){this._renderTags(e);this._renderAnnotation(e)}e.find(".history-secondary-actions").prepend(this._renderSelectButton());e.find(".history-dataset-actions").toggle(this.selecting);e.find(".history-secondary-actions").prepend(this._renderSearchButton());this._setUpBehaviours(e);this.renderHdas(e);return e},_renderTags:function(e){var f=this;this.tagsEditor=new TagsEditor({model:this.model,el:e.find(".history-controls .tags-display"),onshowFirstTime:function(){this.render()},onshow:function(){f.toggleHDATagEditors(true,f.fxSpeed)},onhide:function(){f.toggleHDATagEditors(false,f.fxSpeed)},$activator:faIconButton({title:_l("Edit history tags"),classes:"history-tag-btn",faIcon:"fa-tags"}).appendTo(e.find(".history-secondary-actions"))})},_renderAnnotation:function(e){var f=this;this.annotationEditor=new AnnotationEditor({model:this.model,el:e.find(".history-controls .annotation-display"),onshowFirstTime:function(){this.render()},onshow:function(){f.toggleHDAAnnotationEditors(true,f.fxSpeed)},onhide:function(){f.toggleHDAAnnotationEditors(false,f.fxSpeed)},$activator:faIconButton({title:_l("Edit history Annotation"),classes:"history-annotate-btn",faIcon:"fa-comment"}).appendTo(e.find(".history-secondary-actions"))})},_renderSelectButton:function(e){return faIconButton({title:_l("Operations on multiple datasets"),classes:"history-select-btn",faIcon:"fa-check-square-o"})},_setUpBehaviours:function(e){e=e||this.$el;b.ReadOnlyHistoryPanel.prototype._setUpBehaviours.call(this,e);if(!this.model){return}this._setUpDatasetActionsPopup(e);if((!Galaxy.currUser||Galaxy.currUser.isAnonymous())||(Galaxy.currUser.id!==this.model.get("user_id"))){return}var f=this;e.find(".history-name").attr("title",_l("Click to rename history")).tooltip({placement:"bottom"}).make_text_editable({on_finish:function(g){var h=f.model.get("name");if(g&&g!==h){f.$el.find(".history-name").text(g);f.model.save({name:g}).fail(function(){f.$el.find(".history-name").text(f.model.previous("name"))})}else{f.$el.find(".history-name").text(h)}}})},_setUpDatasetActionsPopup:function(e){var f=this;(new PopupMenu(e.find(".history-dataset-action-popup-btn"),[{html:_l("Hide datasets"),func:function(){var g=c.HistoryDatasetAssociation.prototype.hide;f.getSelectedHdaCollection().ajaxQueue(g)}},{html:_l("Unhide datasets"),func:function(){var g=c.HistoryDatasetAssociation.prototype.unhide;f.getSelectedHdaCollection().ajaxQueue(g)}},{html:_l("Delete datasets"),func:function(){var g=c.HistoryDatasetAssociation.prototype["delete"];f.getSelectedHdaCollection().ajaxQueue(g)}},{html:_l("Undelete datasets"),func:function(){var g=c.HistoryDatasetAssociation.prototype.undelete;f.getSelectedHdaCollection().ajaxQueue(g)}},{html:_l("Permanently delete datasets"),func:function(){if(confirm(_l("This will permanently remove the data in your datasets. Are you sure?"))){var g=c.HistoryDatasetAssociation.prototype.purge;f.getSelectedHdaCollection().ajaxQueue(g)}}}]))},_handleHdaDeletionChange:function(e){if(e.get("deleted")&&!this.storage.get("show_deleted")){this.removeHdaView(this.hdaViews[e.id])}},_handleHdaVisibleChange:function(e){if(e.hidden()&&!this.storage.get("show_hidden")){this.removeHdaView(this.hdaViews[e.id])}},_createHdaView:function(f){var e=f.get("id"),g=new this.HDAViewClass({model:f,linkTarget:this.linkTarget,expanded:this.storage.get("expandedHdas")[e],selectable:this.selecting,hasUser:this.model.ownedByCurrUser(),logger:this.logger,tagsEditorShown:(this.tagsEditor&&!this.tagsEditor.hidden),annotationEditorShown:(this.annotationEditor&&!this.annotationEditor.hidden)});this._setUpHdaListeners(g);return g},_setUpHdaListeners:function(f){var e=this;b.ReadOnlyHistoryPanel.prototype._setUpHdaListeners.call(this,f);f.on("selected",function(g){var h=g.model.get("id");e.selectedHdaIds=_.union(e.selectedHdaIds,[h])});f.on("de-selected",function(g){var h=g.model.get("id");e.selectedHdaIds=_.without(e.selectedHdaIds,h)})},toggleHDATagEditors:function(e){var f=arguments;_.each(this.hdaViews,function(g){if(g.tagsEditor){g.tagsEditor.toggle.apply(g.tagsEditor,f)}})},toggleHDAAnnotationEditors:function(e){var f=arguments;_.each(this.hdaViews,function(g){if(g.annotationEditor){g.annotationEditor.toggle.apply(g.annotationEditor,f)}})},removeHdaView:function(f){if(!f){return}var e=this;f.$el.fadeOut(e.fxSpeed,function(){f.off();f.remove();delete e.hdaViews[f.model.id];if(_.isEmpty(e.hdaViews)){e.$emptyMessage().fadeIn(e.fxSpeed,function(){e.trigger("empty-history",e)})}})},events:_.extend(_.clone(b.ReadOnlyHistoryPanel.prototype.events),{"click .history-select-btn":function(f){this.toggleSelectors(this.fxSpeed)},"click .history-select-all-datasets-btn":"selectAllDatasets","click .history-deselect-all-datasets-btn":"deselectAllDatasets"}),updateHistoryDiskSize:function(){this.$el.find(".history-size").text(this.model.get("nice_size"))},showSelectors:function(e){this.selecting=true;this.$el.find(".history-dataset-actions").slideDown(e);_.each(this.hdaViews,function(f){f.showSelector(e)});this.selectedHdaIds=[]},hideSelectors:function(e){this.selecting=false;this.$el.find(".history-dataset-actions").slideUp(e);_.each(this.hdaViews,function(f){f.hideSelector(e)});this.selectedHdaIds=[]},toggleSelectors:function(e){if(!this.selecting){this.showSelectors(e)}else{this.hideSelectors(e)}},selectAllDatasets:function(e){_.each(this.hdaViews,function(f){f.select(e)})},deselectAllDatasets:function(e){_.each(this.hdaViews,function(f){f.deselect(e)})},getSelectedHdaViews:function(){return _.filter(this.hdaViews,function(e){return e.selected})},getSelectedHdaCollection:function(){return new c.HDACollection(_.map(this.getSelectedHdaViews(),function(e){return e.model}),{historyId:this.model.id})},toString:function(){return"HistoryPanel("+((this.model)?(this.model.get("name")):(""))+")"}});return{HistoryPanel:d}});
\ No newline at end of file
diff -r 1ed94d89769ed2b51232c6c714c3bb21d93cc3ce -r 707ba3eee2abb828ac262e76c5b9fbbc6c85cd03 static/scripts/packed/mvc/history/readonly-history-panel.js
--- a/static/scripts/packed/mvc/history/readonly-history-panel.js
+++ b/static/scripts/packed/mvc/history/readonly-history-panel.js
@@ -1,1 +1,1 @@
-define(["mvc/history/history-model","mvc/dataset/hda-base"],function(e,a){var d=SessionStorageModel.extend({defaults:{expandedHdas:{},show_deleted:false,show_hidden:false},addExpandedHda:function(f){this.save("expandedHdas",_.extend(this.get("expandedHdas"),_.object([f],[true])))},removeExpandedHda:function(f){this.save("expandedHdas",_.omit(this.get("expandedHdas"),f))},toString:function(){return"HistoryPrefs("+this.id+")"}});d.historyStorageKey=function c(f){if(!f){throw new Error("HistoryPrefs.historyStorageKey needs valid id: "+f)}return("history:"+f)};var b=Backbone.View.extend(LoggableMixin).extend({HDAViewClass:a.HDABaseView,tagName:"div",className:"history-panel",fxSpeed:"fast",datasetsSelector:".datasets-list",msgsSelector:".message-container",emptyMsgSelector:".empty-history-message",emptyMsg:_l("This history is empty"),noneFoundMsg:_l("No matching datasets found"),initialize:function(f){f=f||{};if(f.logger){this.logger=f.logger}this.log(this+".initialize:",f);this.linkTarget=f.linkTarget||"_blank";this.fxSpeed=_.has(f,"fxSpeed")?(f.fxSpeed):(this.fxSpeed);this.hdaViews={};this.indicator=new LoadingIndicator(this.$el);this.filters=[];this.searchFor="";this._setUpListeners();if(this.model){if(this.logger){this.model.logger=this.logger}this._setUpWebStorage(f.initiallyExpanded,f.show_deleted,f.show_hidden);this._setUpModelEventHandlers()}if(f.onready){f.onready.call(this)}},_setUpListeners:function(){this.on("error",function(g,j,f,i,h){this.errorHandler(g,j,f,i,h)});this.on("loading-history",function(){this.showLoadingIndicator("loading history...",40)});this.on("loading-done",function(){this.hideLoadingIndicator(40);if(_.isEmpty(this.hdaViews)){this.trigger("empty-history",this)}});this.once("rendered",function(){this.trigger("rendered:initial",this);return false});if(this.logger){this.on("all",function(f){this.log(this+"",arguments)},this)}},errorHandler:function(h,k,g,j,i){var f=this._parseErrorMessage(h,k,g,j,i);if(k&&k.status===0&&k.readyState===0){}else{if(k&&k.status===502){}else{if(!this.$el.find(this.msgsSelector).is(":visible")){this.once("rendered",function(){this.displayMessage("error",f.message,f.details)})}else{this.displayMessage("error",f.message,f.details)}}}},_parseErrorMessage:function(i,m,h,l,k){var g=Galaxy.currUser,f={message:this._bePolite(l),details:{user:(g instanceof User)?(g.toJSON()):(g+""),source:(i instanceof Backbone.Model)?(i.toJSON()):(i+""),xhr:m,options:(m)?(_.omit(h,"xhr")):(h)}};_.extend(f.details,k||{});if(m&&_.isFunction(m.getAllResponseHeaders)){var j=m.getAllResponseHeaders();j=_.compact(j.split("\n"));j=_.map(j,function(n){return n.split(": ")});f.details.xhr.responseHeaders=_.object(j)}return f},_bePolite:function(f){f=f||_l("An error occurred while getting updates from the server");return f+". "+_l("Please contact a Galaxy administrator if the problem persists.")},loadHistoryWithHDADetails:function(i,h,g,k){var f=this,j=function(l){return f.getExpandedHdaIds(l.id)};return this.loadHistory(i,h,g,k,j)},loadHistory:function(i,h,g,l,j){this.trigger("loading-history",this);h=h||{};var f=this;var k=e.History.getHistoryData(i,{historyFn:g,hdaFn:l,hdaDetailIds:h.initiallyExpanded||j});return this._loadHistoryFromXHR(k,h).fail(function(o,m,n){f.trigger("error",f,o,h,_l("An error was encountered while "+m),{historyId:i,history:n||{}})}).always(function(){f.trigger("loading-done",f)})},_loadHistoryFromXHR:function(h,g){var f=this;h.then(function(i,j){f.setModel(i,j,g)});h.fail(function(j,i){f.render()});return h},setModel:function(h,f,g){g=g||{};if(this.model){this.model.clearUpdateTimeout();this.stopListening(this.model);this.stopListening(this.model.hdas)}this.hdaViews={};if(Galaxy&&Galaxy.currUser){h.user=Galaxy.currUser.toJSON()}this.model=new e.History(h,f,g);if(this.logger){this.model.logger=this.logger}this._setUpWebStorage(g.initiallyExpanded,g.show_deleted,g.show_hidden);this._setUpModelEventHandlers();this.selectedHdaIds=[];this.trigger("new-model",this);this.render();return this},_setUpWebStorage:function(g,f,h){this.storage=new d({id:d.historyStorageKey(this.model.get("id"))});if(_.isObject(g)){this.storage.set("exandedHdas",g)}if(_.isBoolean(f)){this.storage.set("show_deleted",f)}if(_.isBoolean(h)){this.storage.set("show_hidden",h)}this.trigger("new-storage",this.storage,this);this.log(this+" (init'd) storage:",this.storage.get())},clearWebStorage:function(){for(var f in sessionStorage){if(f.indexOf("history:")===0){sessionStorage.removeItem(f)}}},getStoredOptions:function(g){if(!g||g==="current"){return(this.storage)?(this.storage.get()):({})}var f=sessionStorage.getItem(d.historyStorageKey(g));return(f===null)?({}):(JSON.parse(f))},getExpandedHdaIds:function(f){var g=this.getStoredOptions(f).expandedHdas;return((_.isEmpty(g))?([]):(_.keys(g)))},_setUpModelEventHandlers:function(){this.model.hdas.on("add",this.addHdaView,this);this.model.on("error error:hdas",function(g,i,f,h){this.errorHandler(g,i,f,h)},this)},render:function(h,i){h=(h===undefined)?(this.fxSpeed):(h);var f=this,g;if(this.model){g=this.renderModel()}else{g=this.renderWithoutModel()}$(f).queue("fx",[function(j){if(h&&f.$el.is(":visible")){f.$el.fadeOut(h,j)}else{j()}},function(j){f.$el.empty();if(g){f.$el.append(g.children())}j()},function(j){if(h&&!f.$el.is(":visible")){f.$el.fadeIn(h,j)}else{j()}},function(j){if(i){i.call(this)}f.trigger("rendered",this);j()}]);return this},renderWithoutModel:function(){var f=$("<div/>"),g=$("<div/>").addClass("message-container").css({"margin-left":"4px","margin-right":"4px"});return f.append(g)},renderModel:function(){var f=$("<div/>");f.append(b.templates.historyPanel(this.model.toJSON()));f.find(".history-secondary-actions").prepend(this._renderSearchButton());this._setUpBehaviours(f);this.renderHdas(f);return f},_renderSearchButton:function(f){return faIconButton({title:_l("Search datasets"),classes:"history-search-btn",faIcon:"fa-search"})},_setUpBehaviours:function(f){f=f||this.$el;f.find("[title]").tooltip({placement:"bottom"})},createHdaView:function(g){var f=g.get("id"),h=new this.HDAViewClass({model:g,linkTarget:this.linkTarget,expanded:this.storage.get("expandedHdas")[f],hasUser:this.model.ownedByCurrUser(),logger:this.logger});this._setUpHdaListeners(h);return h},_setUpHdaListeners:function(g){var f=this;g.on("body-expanded",function(h){f.storage.addExpandedHda(h)});g.on("body-collapsed",function(h){f.storage.removeExpandedHda(h)});g.on("error",function(i,k,h,j){f.errorHandler(i,k,h,j)})},renderHdas:function(g){g=g||this.$el;var f=this,i={},h=this.model.hdas.getVisible(this.storage.get("show_deleted"),this.storage.get("show_hidden"),this.filters);g.find(this.datasetsSelector).empty();if(h.length){h.each(function(k){var j=k.get("id"),l=f.createHdaView(k);i[j]=l;if(_.contains(f.selectedHdaIds,j)){l.selected=true}f.attachHdaView(l.render(),g)});g.find(this.emptyMsgSelector).hide()}else{g.find(this.emptyMsgSelector).text((this.model.hdas.length&&this.searchFor)?(this.noneFoundMsg):(this.emptyMsg)).show()}this.hdaViews=i;return this.hdaViews},attachHdaView:function(h,g){g=g||this.$el;var f=g.find(this.datasetsSelector);f.prepend(h.$el)},addHdaView:function(i){this.log("add."+this,i);var g=this;if(!i.isVisible(this.storage.get("show_deleted"),this.storage.get("show_hidden"))){return}$({}).queue([function h(k){var j=g.$el.find(g.emptyMsgSelector);if(j.is(":visible")){j.fadeOut(g.fxSpeed,k)}else{k()}},function f(k){g.scrollToTop();var j=g.$el.find(g.datasetsSelector),l=g.createHdaView(i);g.hdaViews[i.id]=l;l.render().$el.hide().prependTo(j).slideDown(g.fxSpeed)}])},refreshHdas:function(g,f){if(this.model){return this.model.refresh(g,f)}return $.when()},events:{"click .message-container":"clearMessages","click .history-search-btn":"toggleSearchControls"},collapseAllHdaBodies:function(){_.each(this.hdaViews,function(f){f.toggleBodyVisibility(null,false)});this.storage.set("expandedHdas",{})},toggleShowDeleted:function(f){f=(f!==undefined)?(f):(!this.storage.get("show_deleted"));this.storage.set("show_deleted",f);this.renderHdas();return this.storage.get("show_deleted")},toggleShowHidden:function(f){f=(f!==undefined)?(f):(!this.storage.get("show_hidden"));this.storage.set("show_hidden",f);this.renderHdas();return this.storage.get("show_hidden")},setUpSearchInput:function(g){var h=this,i=".history-search-input";function f(j){if(h.model.hdas.haveDetails()){h.searchHdas(j);return}h.$el.find(i).searchInput("toggle-loading");h.model.hdas.fetchAllDetails({silent:true}).always(function(){h.$el.find(i).searchInput("toggle-loading")}).done(function(){h.searchHdas(j)})}g.searchInput({initialVal:h.searchFor,name:"history-search",placeholder:"search datasets",classes:"history-search",onfirstsearch:f,onsearch:_.bind(this.searchHdas,this),onclear:_.bind(this.clearHdaSearch,this)});return g},showSearchControls:function(g){g=(g===undefined)?(this.fxSpeed):(g);var f=this.$el.find(".history-search-controls"),h=f.find(".history-search-input");if(!h.children().size()){this.setUpSearchInput(h)}f.slideDown(g,function(){$(this).find("input").focus()})},hideSearchControls:function(f){f=(f===undefined)?(this.fxSpeed):(f);this.$el.find(".history-search-controls").slideUp(f)},toggleSearchControls:function(f){speed=(jQuery.type(f)==="number")?(f):(this.fxSpeed);if(this.$el.find(".history-search-controls").is(":visible")){this.hideSearchControls(speed)}else{this.showSearchControls(speed)}},searchHdas:function(f){var g=this;this.searchFor=f;this.filters=[function(h){return h.matchesAll(g.searchFor)}];this.trigger("search:searching",f,this);this.renderHdas()},clearHdaSearch:function(f){this.searchFor="";this.filters=[];this.trigger("search:clear",this);this.renderHdas()},showLoadingIndicator:function(g,f,h){f=(f!==undefined)?(f):(this.fxSpeed);if(!this.indicator){this.indicator=new LoadingIndicator(this.$el,this.$el.parent())}if(!this.$el.is(":visible")){this.indicator.show(0,h)}else{this.$el.fadeOut(f);this.indicator.show(g,f,h)}},hideLoadingIndicator:function(f,g){f=(f!==undefined)?(f):(this.fxSpeed);if(this.indicator){this.indicator.hide(f,g)}},displayMessage:function(k,l,j){var h=this;this.scrollToTop();var i=this.$el.find(this.msgsSelector),f=$("<div/>").addClass(k+"message").html(l);if(!_.isEmpty(j)){var g=$('<a href="javascript:void(0)">Details</a>').click(function(){Galaxy.modal.show(h._messageToModalOptions(k,l,j));return false});f.append(" ",g)}return i.html(f)},_messageToModalOptions:function(j,l,i){var f=this,k=$("<div/>"),h={title:"Details"};function g(m){m=_.omit(m,_.functions(m));return["<table>",_.map(m,function(o,n){o=(_.isObject(o))?(g(o)):(o);return'<tr><td style="vertical-align: top; color: grey">'+n+'</td><td style="padding-left: 8px">'+o+"</td></tr>"}).join(""),"</table>"].join("")}if(_.isObject(i)){h.body=k.append(g(i))}else{h.body=k.html(i)}h.buttons={Ok:function(){Galaxy.modal.hide();f.clearMessages()}};return h},clearMessages:function(){var f=this.$el.find(this.msgsSelector);f.empty()},scrollPosition:function(){return this.$el.scrollTop()},scrollTo:function(f){this.$el.scrollTop(f);return this},scrollToTop:function(){this.$el.scrollTop(0);return this},scrollIntoView:function(k,g){if(g===undefined){this.scrollTo(k);return this}var f=window,j=this.$el.parent(),i=$(f).innerHeight(),h=(i/2)-(g/2);this.scrollTo(k-h);return this},scrollToId:function(g){if((!g)||(!this.hdaViews[g])){return this}var f=this.hdaViews[g].$el;this.scrollIntoView(f.offset().top,f.outerHeight());return this},scrollToHid:function(f){var g=this.model.hdas.getByHid(f);if(!g){return this}return this.scrollToId(g.id)},toString:function(){return"ReadOnlyHistoryPanel("+((this.model)?(this.model.get("name")):(""))+")"}});b.templates={historyPanel:Handlebars.templates["template-history-historyPanel"]};return{ReadOnlyHistoryPanel:b}});
\ No newline at end of file
+define(["mvc/history/history-model","mvc/dataset/hda-base"],function(g,a){var f=SessionStorageModel.extend({defaults:{expandedHdas:{},show_deleted:false,show_hidden:false},addExpandedHda:function(i){var h="expandedHdas";this.save(h,_.extend(this.get(h),_.object([i],[true])))},removeExpandedHda:function(i){var h="expandedHdas";this.save(h,_.omit(this.get(h),i))},toString:function(){return"HistoryPrefs("+this.id+")"}});f.storageKeyPrefix="history:";f.historyStorageKey=function e(h){if(!h){throw new Error("HistoryPrefs.historyStorageKey needs valid id: "+h)}return(f.storageKeyPrefix+h)};f.get=function d(h){return new f({id:f.historyStorageKey(h)})};f.clearAll=function c(i){for(var h in sessionStorage){if(h.indexOf(f.storageKeyPrefix)===0){sessionStorage.removeItem(h)}}};var b=Backbone.View.extend(LoggableMixin).extend({HDAViewClass:a.HDABaseView,tagName:"div",className:"history-panel",fxSpeed:"fast",emptyMsg:_l("This history is empty"),noneFoundMsg:_l("No matching datasets found"),initialize:function(h){h=h||{};if(h.logger){this.logger=h.logger}this.log(this+".initialize:",h);this.linkTarget=h.linkTarget||"_blank";this.fxSpeed=_.has(h,"fxSpeed")?(h.fxSpeed):(this.fxSpeed);this.filters=[];this.searchFor="";this.findContainerFn=h.findContainerFn;this.hdaViews={};this.indicator=new LoadingIndicator(this.$el);this._setUpListeners();var i=_.pick(h,"initiallyExpanded","show_deleted","show_hidden");this.setModel(this.model,i,false);if(h.onready){h.onready.call(this)}},_setUpListeners:function(){this.on("error",function(i,l,h,k,j){this.errorHandler(i,l,h,k,j)});this.on("loading-history",function(){this._showLoadingIndicator("loading history...",40)});this.on("loading-done",function(){this._hideLoadingIndicator(40);if(_.isEmpty(this.hdaViews)){this.trigger("empty-history",this)}});this.once("rendered",function(){this.trigger("rendered:initial",this);return false});if(this.logger){this.on("all",function(h){this.log(this+"",arguments)},this)}return this},errorHandler:function(j,m,i,l,k){console.error(j,m,i,l,k);if(m&&m.status===0&&m.readyState===0){}else{if(m&&m.status===502){}else{var h=this._parseErrorMessage(j,m,i,l,k);if(!this.$messages().is(":visible")){this.once("rendered",function(){this.displayMessage("error",h.message,h.details)})}else{this.displayMessage("error",h.message,h.details)}}}},_parseErrorMessage:function(k,o,j,n,m){var i=Galaxy.currUser,h={message:this._bePolite(n),details:{user:(i instanceof User)?(i.toJSON()):(i+""),source:(k instanceof Backbone.Model)?(k.toJSON()):(k+""),xhr:o,options:(o)?(_.omit(j,"xhr")):(j)}};_.extend(h.details,m||{});if(o&&_.isFunction(o.getAllResponseHeaders)){var l=o.getAllResponseHeaders();l=_.compact(l.split("\n"));l=_.map(l,function(p){return p.split(": ")});h.details.xhr.responseHeaders=_.object(l)}return h},_bePolite:function(h){h=h||_l("An error occurred while getting updates from the server");return h+". "+_l("Please contact a Galaxy administrator if the problem persists.")},loadHistoryWithHDADetails:function(j,i,h,l){var k=function(m){return _.keys(f.get(m.id).get("expandedHdas"))};return this.loadHistory(j,i,h,l,k)},loadHistory:function(k,j,i,n,l){var h=this;j=j||{};h.trigger("loading-history",h);var m=g.History.getHistoryData(k,{historyFn:i,hdaFn:n,hdaDetailIds:j.initiallyExpanded||l});return h._loadHistoryFromXHR(m,j).fail(function(q,o,p){h.trigger("error",h,q,j,_l("An error was encountered while "+o),{historyId:k,history:p||{}})}).always(function(){h.trigger("loading-done",h)})},_loadHistoryFromXHR:function(j,i){var h=this;j.then(function(k,l){h.JSONToModel(k,l,i)});j.fail(function(l,k){h.render()});return j},JSONToModel:function(k,h,i){this.log("JSONToModel:",k,h,i);i=i||{};if(Galaxy&&Galaxy.currUser){k.user=Galaxy.currUser.toJSON()}var j=new g.History(k,h,i);this.setModel(j);return this},setModel:function(i,h,j){h=h||{};j=(j!==undefined)?(j):(true);this.log("setModel:",i,h,j);this.freeModel();this.selectedHdaIds=[];if(i){if(Galaxy&&Galaxy.currUser){i.user=Galaxy.currUser.toJSON()}this.model=i;if(this.logger){this.model.logger=this.logger}this._setUpWebStorage(h.initiallyExpanded,h.show_deleted,h.show_hidden);this._setUpModelEventHandlers();this.trigger("new-model",this)}if(j){this.render()}return this},freeModel:function(){if(this.model){this.model.clearUpdateTimeout();this.stopListening(this.model);this.stopListening(this.model.hdas)}this.freeHdaViews();return this},freeHdaViews:function(){this.hdaViews={};return this},_setUpWebStorage:function(i,h,j){this.storage=new f({id:f.historyStorageKey(this.model.get("id"))});if(_.isObject(i)){this.storage.set("exandedHdas",i)}if(_.isBoolean(h)){this.storage.set("show_deleted",h)}if(_.isBoolean(j)){this.storage.set("show_hidden",j)}this.trigger("new-storage",this.storage,this);this.log(this+" (init'd) storage:",this.storage.get());return this},_setUpModelEventHandlers:function(){this.model.hdas.on("add",this.addHdaView,this);this.model.on("error error:hdas",function(i,k,h,j){this.errorHandler(i,k,h,j)},this);return this},render:function(j,k){this.log("render:",j,k);j=(j===undefined)?(this.fxSpeed):(j);var h=this,i;if(this.model){i=this.renderModel()}else{i=this.renderWithoutModel()}$(h).queue("fx",[function(l){if(j&&h.$el.is(":visible")){h.$el.fadeOut(j,l)}else{l()}},function(l){h.$el.empty();if(i){h.$el.append(i.children())}l()},function(l){if(j&&!h.$el.is(":visible")){h.$el.fadeIn(j,l)}else{l()}},function(l){if(k){k.call(this)}h.trigger("rendered",this);l()}]);return this},renderWithoutModel:function(){var h=$("<div/>"),i=$("<div/>").addClass("message-container").css({"margin-left":"4px","margin-right":"4px"});return h.append(i)},renderModel:function(){var h=$("<div/>");h.append(b.templates.historyPanel(this.model.toJSON()));h.find(".history-secondary-actions").prepend(this._renderSearchButton());this._setUpBehaviours(h);this.renderHdas(h);return h},_renderSearchButton:function(h){return faIconButton({title:_l("Search datasets"),classes:"history-search-btn",faIcon:"fa-search"})},_setUpBehaviours:function(h){h=h||this.$el;h.find("[title]").tooltip({placement:"bottom"});this._setUpSearchInput(h.find(".history-search-controls .history-search-input"));return this},$container:function(){return(this.findContainerFn)?(this.findContainerFn.call(this)):(this.$el.parent())},$datasetsList:function(h){return(h||this.$el).find(".datasets-list")},$messages:function(h){return(h||this.$el).find(".message-container")},$emptyMessage:function(h){return(h||this.$el).find(".empty-history-message")},renderHdas:function(i){i=i||this.$el;var h=this,k={},j=this.model.hdas.getVisible(this.storage.get("show_deleted"),this.storage.get("show_hidden"),this.filters);this.$datasetsList(i).empty();if(j.length){j.each(function(m){var l=m.get("id"),n=h._createHdaView(m);k[l]=n;if(_.contains(h.selectedHdaIds,l)){n.selected=true}h.attachHdaView(n.render(),i)});h.$emptyMessage(i).hide()}else{h.$emptyMessage(i).text((this.model.hdas.length&&this.searchFor)?(this.noneFoundMsg):(this.emptyMsg)).show()}this.hdaViews=k;return this.hdaViews},_createHdaView:function(i){var h=i.get("id"),j=new this.HDAViewClass({model:i,linkTarget:this.linkTarget,expanded:this.storage.get("expandedHdas")[h],hasUser:this.model.ownedByCurrUser(),logger:this.logger});this._setUpHdaListeners(j);return j},_setUpHdaListeners:function(i){var h=this;i.on("error",function(k,m,j,l){h.errorHandler(k,m,j,l)});i.on("body-expanded",function(j){h.storage.addExpandedHda(j)});i.on("body-collapsed",function(j){h.storage.removeExpandedHda(j)});return this},attachHdaView:function(j,i){i=i||this.$el;var h=this.$datasetsList(i);h.prepend(j.$el);return this},addHdaView:function(k){this.log("add."+this,k);var i=this;if(!k.isVisible(this.storage.get("show_deleted"),this.storage.get("show_hidden"))){return i}$({}).queue([function j(m){var l=i.$emptyMessage();if(l.is(":visible")){l.fadeOut(i.fxSpeed,m)}else{m()}},function h(l){var m=i._createHdaView(k);i.hdaViews[k.id]=m;m.render().$el.hide();i.scrollToTop();i.attachHdaView(m);m.$el.slideDown(i.fxSpeed)}]);return i},refreshHdas:function(i,h){if(this.model){return this.model.refresh(i,h)}return $.when()},events:{"click .message-container":"clearMessages","click .history-search-btn":"toggleSearchControls"},collapseAllHdaBodies:function(){_.each(this.hdaViews,function(h){h.toggleBodyVisibility(null,false)});this.storage.set("expandedHdas",{});return this},toggleShowDeleted:function(h){h=(h!==undefined)?(h):(!this.storage.get("show_deleted"));this.storage.set("show_deleted",h);this.renderHdas();return this.storage.get("show_deleted")},toggleShowHidden:function(h){h=(h!==undefined)?(h):(!this.storage.get("show_hidden"));this.storage.set("show_hidden",h);this.renderHdas();return this.storage.get("show_hidden")},_setUpSearchInput:function(i){var j=this,k=".history-search-input";function h(l){if(j.model.hdas.haveDetails()){j.searchHdas(l);return}j.$el.find(k).searchInput("toggle-loading");j.model.hdas.fetchAllDetails({silent:true}).always(function(){j.$el.find(k).searchInput("toggle-loading")}).done(function(){j.searchHdas(l)})}i.searchInput({initialVal:j.searchFor,name:"history-search",placeholder:"search datasets",classes:"history-search",onfirstsearch:h,onsearch:_.bind(this.searchHdas,this),onclear:_.bind(this.clearHdaSearch,this)});return i},toggleSearchControls:function(j,h){var i=this.$el.find(".history-search-controls"),k=(jQuery.type(j)==="number")?(j):(this.fxSpeed);h=(h!==undefined)?(h):(!i.is(":visible"));if(h){i.slideDown(k,function(){$(this).find("input").focus()})}else{i.slideUp(k)}return h},searchHdas:function(h){var i=this;this.searchFor=h;this.filters=[function(j){return j.matchesAll(i.searchFor)}];this.trigger("search:searching",h,this);this.renderHdas();return this},clearHdaSearch:function(h){this.searchFor="";this.filters=[];this.trigger("search:clear",this);this.renderHdas();return this},_showLoadingIndicator:function(i,h,j){h=(h!==undefined)?(h):(this.fxSpeed);if(!this.indicator){this.indicator=new LoadingIndicator(this.$el,this.$el.parent())}if(!this.$el.is(":visible")){this.indicator.show(0,j)}else{this.$el.fadeOut(h);this.indicator.show(i,h,j)}},_hideLoadingIndicator:function(h,i){h=(h!==undefined)?(h):(this.fxSpeed);if(this.indicator){this.indicator.hide(h,i)}},displayMessage:function(m,n,l){var j=this;this.scrollToTop();var k=this.$messages(),h=$("<div/>").addClass(m+"message").html(n);if(!_.isEmpty(l)){var i=$('<a href="javascript:void(0)">Details</a>').click(function(){Galaxy.modal.show(j._messageToModalOptions(m,n,l));return false});h.append(" ",i)}return k.html(h)},_messageToModalOptions:function(l,n,k){var h=this,m=$("<div/>"),j={title:"Details"};function i(o){o=_.omit(o,_.functions(o));return["<table>",_.map(o,function(q,p){q=(_.isObject(q))?(i(q)):(q);return'<tr><td style="vertical-align: top; color: grey">'+p+'</td><td style="padding-left: 8px">'+q+"</td></tr>"}).join(""),"</table>"].join("")}if(_.isObject(k)){j.body=m.append(i(k))}else{j.body=m.html(k)}j.buttons={Ok:function(){Galaxy.modal.hide();h.clearMessages()}};return j},clearMessages:function(){this.$messages().empty();return this},scrollPosition:function(){return this.$container().scrollTop()},scrollTo:function(h){this.$container().scrollTop(h);return this},scrollToTop:function(){this.$container().scrollTop(0);return this},scrollToId:function(i){if((!i)||(!this.hdaViews[i])){return this}var h=this.hdaViews[i];this.scrollTo(h.el.offsetTop);return this},scrollToHid:function(h){var i=this.model.hdas.getByHid(h);if(!i){return this}return this.scrollToId(i.id)},toString:function(){return"ReadOnlyHistoryPanel("+((this.model)?(this.model.get("name")):(""))+")"}});b.templates={historyPanel:Handlebars.templates["template-history-historyPanel"]};return{ReadOnlyHistoryPanel:b}});
\ No newline at end of file
diff -r 1ed94d89769ed2b51232c6c714c3bb21d93cc3ce -r 707ba3eee2abb828ac262e76c5b9fbbc6c85cd03 static/style/blue/base.css
--- a/static/style/blue/base.css
+++ b/static/style/blue/base.css
@@ -1602,7 +1602,11 @@
.workflow-invocation-complete{border:solid 1px #6A6;border-left-width:5px;margin:10px 0;padding-left:5px}
.icon-btn{display:inline-block;height:22px;width:22px;text-align:center;line-height:19px;font-size:1.2em;border-radius:3px;border:1px solid #bfbfbf;background-color:#f2f2f2;color:#333;cursor:pointer}
.icon-btn:hover{background-color:white;color:maroon}
-.icon-btn.disabled{background-color:transparent;color:#bfbfbf}
+.icon-btn.disabled{background-color:transparent;color:#BBB;border-color:#BBB}
+.icon-btn-group{display:inline-block}.icon-btn-group .icon-btn:not(:last-child){margin:0px;border-radius:0px;border-right:none}
+.icon-btn-group .icon-btn:first-child{margin-right:0px;border-top-left-radius:3px;border-bottom-left-radius:3px}
+.icon-btn-group .icon-btn:last-child{margin-left:0px;border-radius:0px 3px 3px 0px}
+.icon-btn-group .icon-btn:only-child{margin:0px;border-radius:3px}
.search-input .search-query{width:100%;padding-right:24px}
.search-input .search-clear,.search-input .search-loading{position:relative;display:inline-block;float:right;margin-top:-23px;margin-right:4px;font-size:1.4em;line-height:23px;color:grey}
.search-input .search-clear:hover{color:#303030}
@@ -1618,6 +1622,7 @@
.history-panel .dataset .dataset-primary-actions{display:inline-block;float:right;margin:6px 10px 0}.history-panel .dataset .dataset-primary-actions .icon-btn:not(:last-child){margin:0px;border-radius:0px;border-right:none}
.history-panel .dataset .dataset-primary-actions .icon-btn:first-child{margin-right:0px;border-top-left-radius:3px;border-bottom-left-radius:3px}
.history-panel .dataset .dataset-primary-actions .icon-btn:last-child{margin-left:0px;border-radius:0px 3px 3px 0px}
+.history-panel .dataset .dataset-primary-actions .icon-btn:only-child{margin:0px;border-radius:3px}
.history-panel .dataset .dataset-primary-actions .icon-btn{margin-left:2px}
.history-panel .dataset .dataset-body{display:none;padding:0 10px 6px 8px}.history-panel .dataset .dataset-body [class$=messagesmall]{margin:0px 0px 8px 0px}
.history-panel .dataset .dataset-body label{margin:0px;padding:0px;font-weight:normal}
@@ -1632,9 +1637,14 @@
.history-panel .dataset .dataset-body .dataset-actions .left{display:inline-block;float:left}.history-panel .dataset .dataset-body .dataset-actions .left .icon-btn:not(:last-child){margin:0px;border-radius:0px;border-right:none}
.history-panel .dataset .dataset-body .dataset-actions .left .icon-btn:first-child{margin-right:0px;border-top-left-radius:3px;border-bottom-left-radius:3px}
.history-panel .dataset .dataset-body .dataset-actions .left .icon-btn:last-child{margin-left:0px;border-radius:0px 3px 3px 0px}
+.history-panel .dataset .dataset-body .dataset-actions .left .icon-btn:only-child{margin:0px;border-radius:3px}
.history-panel .dataset .dataset-body .dataset-actions .left .icon-btn{margin-right:2px}
.history-panel .dataset .dataset-body .dataset-actions .left:not(:empty){margin-bottom:8px}
-.history-panel .dataset .dataset-body .dataset-actions .right{float:right}.history-panel .dataset .dataset-body .dataset-actions .right .icon-btn{margin-left:2px}
+.history-panel .dataset .dataset-body .dataset-actions .right{display:inline-block;float:right}.history-panel .dataset .dataset-body .dataset-actions .right .icon-btn:not(:last-child){margin:0px;border-radius:0px;border-right:none}
+.history-panel .dataset .dataset-body .dataset-actions .right .icon-btn:first-child{margin-right:0px;border-top-left-radius:3px;border-bottom-left-radius:3px}
+.history-panel .dataset .dataset-body .dataset-actions .right .icon-btn:last-child{margin-left:0px;border-radius:0px 3px 3px 0px}
+.history-panel .dataset .dataset-body .dataset-actions .right .icon-btn:only-child{margin:0px;border-radius:3px}
+.history-panel .dataset .dataset-body .dataset-actions .right .icon-btn{margin-left:2px}
.history-panel .dataset .dataset-body .tags-display{display:none;margin-bottom:8px}.history-panel .dataset .dataset-body .tags-display .select2-container{min-width:0px}.history-panel .dataset .dataset-body .tags-display .select2-container .select2-choices{border-radius:3px}
.history-panel .dataset .dataset-body .annotation-display{display:none;margin-bottom:8px}.history-panel .dataset .dataset-body .annotation-display .annotation{border-radius:3px;border:1px solid rgba(153,153,153,0.30000000000000004);padding:4px;white-space:pre-line}
.history-panel .dataset .dataset-body .annotation-display .annotation:empty{height:22px}
@@ -1643,9 +1653,6 @@
.history-panel .dataset .dataset-body .dataset-display-applications .display-application:last-child{margin-bottom:8px}
.history-panel .dataset .dataset-body .dataset-peek{margin-bottom:8px}.history-panel .dataset .dataset-body .dataset-peek pre.peek{width:100%;margin:0px;border-radius:3px;background:white;color:black;font-size:10px;overflow:auto}.history-panel .dataset .dataset-body .dataset-peek pre.peek th{color:white;background:#5f6990}
.history-panel .dataset .dataset-body .dataset-peek pre.peek table,.history-panel .dataset .dataset-body .dataset-peek pre.peek th,.history-panel .dataset .dataset-body .dataset-peek pre.peek tr,.history-panel .dataset .dataset-body .dataset-peek pre.peek td{font-family:Monaco,Menlo,Consolas,"Courier New",monospace;font-size:10px}
-.history-panel .dataset .icon-btn-group{display:inline-block}.history-panel .dataset .icon-btn-group .icon-btn:not(:last-child){margin:0px;border-radius:0px;border-right:none}
-.history-panel .dataset .icon-btn-group .icon-btn:first-child{margin-right:0px;border-top-left-radius:3px;border-bottom-left-radius:3px}
-.history-panel .dataset .icon-btn-group .icon-btn:last-child{margin-left:0px;border-radius:0px 3px 3px 0px}
.history-panel .dataset .state-icon{font-family:FontAwesome;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;display:inline-block;vertical-align:middle;width:16px;height:16px;line-height:16px;text-align:center;font-size:16px}
.state-icon-error{background-color:white;border-radius:8px}.state-icon-error:before{font-size:20px;line-height:16px;color:red;content:"\f057"}
.history-panel [class$="messagesmall"]{margin:0px}
@@ -1659,7 +1666,11 @@
.history-panel .history-controls .history-title input{width:100%;margin:-2px 0 -2px -4px;font-weight:bold}
.history-panel .history-controls .history-subtitle{margin-bottom:8px}
.history-panel .history-controls .history-size{float:left}
-.history-panel .history-controls .history-secondary-actions{float:right}.history-panel .history-controls .history-secondary-actions .icon-btn{margin-left:2px}
+.history-panel .history-controls .history-secondary-actions{display:inline-block;float:right}.history-panel .history-controls .history-secondary-actions .icon-btn:not(:last-child){margin:0px;border-radius:0px;border-right:none}
+.history-panel .history-controls .history-secondary-actions .icon-btn:first-child{margin-right:0px;border-top-left-radius:3px;border-bottom-left-radius:3px}
+.history-panel .history-controls .history-secondary-actions .icon-btn:last-child{margin-left:0px;border-radius:0px 3px 3px 0px}
+.history-panel .history-controls .history-secondary-actions .icon-btn:only-child{margin:0px;border-radius:3px}
+.history-panel .history-controls .history-secondary-actions .icon-btn{margin-left:2px}
.history-panel .history-controls .quota-message{display:none;margin:8px 0px 5px 0px}
.history-panel .history-controls .tags-display,.history-panel .history-controls .annotation-display{display:none;margin-bottom:8px}.history-panel .history-controls .tags-display label,.history-panel .history-controls .annotation-display label{display:block;margin:0px;padding:0px;font-weight:normal;color:#555}
.history-panel .history-controls .tags-display label:after,.history-panel .history-controls .annotation-display label:after{content:':'}
This diff is so big that we needed to truncate the remainder.
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: martenson: data libraries: final touch for the library API controller
by commits-noreply@bitbucket.org 14 Mar '14
by commits-noreply@bitbucket.org 14 Mar '14
14 Mar '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/1ed94d89769e/
Changeset: 1ed94d89769e
User: martenson
Date: 2014-03-14 16:43:43
Summary: data libraries: final touch for the library API controller
Affected #: 1 file
diff -r 3fb927653301a0c06a0bf94f2b6bd71b3595ec0d -r 1ed94d89769ed2b51232c6c714c3bb21d93cc3ce lib/galaxy/webapps/galaxy/api/libraries.py
--- a/lib/galaxy/webapps/galaxy/api/libraries.py
+++ b/lib/galaxy/webapps/galaxy/api/libraries.py
@@ -12,7 +12,6 @@
import logging
log = logging.getLogger( __name__ )
-
class LibrariesController( BaseAPIController ):
@expose_api_anonymous
@@ -29,6 +28,7 @@
:rtype: list
.. seealso:: :attr:`galaxy.model.Library.dict_collection_visible_keys`
+
"""
query = trans.sa_session.query( trans.app.model.Library )
deleted = kwd.get( 'deleted', 'missing' )
@@ -54,7 +54,7 @@
trans.model.Library.table.c.id.in_( accessible_restricted_library_ids ) ) )
libraries = []
for library in query:
- item = self._prepend_folder_prefix( library.to_dict( view='element',
+ item = self.prepend_folder_prefix( library.to_dict( view='element',
value_mapper={ 'id' : trans.security.encode_id , 'root_folder_id' : trans.security.encode_id } ) )
libraries.append( item )
return libraries
@@ -77,6 +77,8 @@
:rtype: dictionary
.. seealso:: :attr:`galaxy.model.Library.dict_element_visible_keys`
+
+ :raises: MalformedId, ObjectNotFound
"""
library_id = id
deleted = util.string_as_bool( deleted )
@@ -91,7 +93,7 @@
library = None
if not library or not ( trans.user_is_admin() or trans.app.security_agent.can_access_library( trans.get_current_user_roles(), library ) ):
raise exceptions.ObjectNotFound( 'Library with the id provided ( %s ) was not found' % id )
- return self._prepend_folder_prefix( library.to_dict( view='element',
+ return self.prepend_folder_prefix( library.to_dict( view='element',
value_mapper={ 'id' : trans.security.encode_id , 'root_folder_id' : trans.security.encode_id } ) )
@expose_api
@@ -111,6 +113,8 @@
:returns: detailed library information
:rtype: dict
+
+ :raises: ItemAccessibilityException, RequestParameterMissingException
"""
if not trans.user_is_admin():
raise exceptions.ItemAccessibilityException( 'Only administrators can create libraries.' )
@@ -127,7 +131,7 @@
library.root_folder = root_folder
trans.sa_session.add_all( ( library, root_folder ) )
trans.sa_session.flush()
- return self._prepend_folder_prefix( library.to_dict( view='element',
+ return self.prepend_folder_prefix( library.to_dict( view='element',
value_mapper={ 'id' : trans.security.encode_id , 'root_folder_id' : trans.security.encode_id } ) )
@expose_api
@@ -146,6 +150,8 @@
:returns: detailed library information
:rtype: dict
+
+ :raises: ItemAccessibilityException, MalformedId, ObjectNotFound, RequestParameterInvalidException, RequestParameterMissingException
"""
if not trans.user_is_admin():
raise exceptions.ItemAccessibilityException( 'Only administrators can update libraries.' )
@@ -177,7 +183,7 @@
raise exceptions.RequestParameterMissingException( "You did not specify any payload." )
trans.sa_session.add( library )
trans.sa_session.flush()
- return self._prepend_folder_prefix( library.to_dict( view='element',
+ return self.prepend_folder_prefix( library.to_dict( view='element',
value_mapper={ 'id' : trans.security.encode_id , 'root_folder_id' : trans.security.encode_id } ) )
@expose_api
@@ -192,13 +198,15 @@
:param id: the encoded id of the library to un/delete
:type id: str
- :param undelete: flag specifying whether the item should be deleted or undeleted, defaults to false:
+ :param undelete: (optional) flag specifying whether the item should be deleted or undeleted, defaults to false:
:type undelete: bool
:returns: detailed library information
:rtype: dictionary
.. seealso:: :attr:`galaxy.model.Library.dict_element_visible_keys`
+
+ :raises: ItemAccessibilityException, MalformedId, ObjectNotFound
"""
undelete = util.string_as_bool( kwd.get( 'undelete', False ) )
if not trans.user_is_admin():
@@ -221,12 +229,21 @@
trans.sa_session.add( library )
trans.sa_session.flush()
- return self._prepend_folder_prefix( library.to_dict( view='element',
+ return self.prepend_folder_prefix( library.to_dict( view='element',
value_mapper={ 'id' : trans.security.encode_id , 'root_folder_id' : trans.security.encode_id } ) )
- def _prepend_folder_prefix (self, dictionary, type='library' ):
+ def prepend_folder_prefix (self, dictionary, type='library' ):
"""
- In Galaxy folders have 'F' as a prefix to the encoded id to distinguish between folders and libraries
+ prepend_folder_prefix (self, dictionary, type='library' )
+ In Galaxy folders have an 'F' as a prefix to the encoded id to distinguish between folders and libraries
+
+ :param dictionary: a supported object after to_dict containing _encoded_ ids
+ :type dictionary: dictionary
+
+ :param type: string representing the type of dictionary that is passed in, defaults to 'library'
+ :type type: string
+
+ :raises: TypeError, ValueError
"""
if not ( type in [ 'library', 'folder' ] ):
raise TypeError( 'Prepending is not implemented for given type of dictionary.' )
@@ -240,5 +257,5 @@
if return_dict[ 'id' ]:
return_dict[ 'id' ] = 'F' + return_dict[ 'id' ]
else:
- raise ValueError( 'Given folder does not contain id to prepend to.' )
+ raise ValueError( 'Given folder does not contain an id to prepend to.' )
return return_dict
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: jmchilton: Add documentation for using GALAXY_SLOTS with local runner.
by commits-noreply@bitbucket.org 14 Mar '14
by commits-noreply@bitbucket.org 14 Mar '14
14 Mar '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/3fb927653301/
Changeset: 3fb927653301
User: jmchilton
Date: 2014-03-14 14:42:10
Summary: Add documentation for using GALAXY_SLOTS with local runner.
Affected #: 1 file
diff -r e22d4f2b57a8c99cf5b48d36e0ca440934eaa569 -r 3fb927653301a0c06a0bf94f2b6bd71b3595ec0d job_conf.xml.sample_advanced
--- a/job_conf.xml.sample_advanced
+++ b/job_conf.xml.sample_advanced
@@ -57,6 +57,12 @@
should be executed on those remote resources.
--><destination id="local" runner="local"/>
+ <destination id="multicore_local" runner="local">
+ <param id="local_slots">4</param><!-- Specify GALAXY_SLOTS for local jobs. -->
+ <!-- Warning: Local slot count doesn't tie up additional worker threads, to prevent over
+ allocating machine define a second local runner with different name and fewer workers
+ to run this destination. -->
+ </destination><destination id="pbs" runner="pbs" tags="mycluster"/><destination id="pbs_longjobs" runner="pbs" tags="mycluster,longjobs"><!-- Define parameters that are native to the job runner plugin. -->
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: guerler: Charts: Disable box plots
by commits-noreply@bitbucket.org 13 Mar '14
by commits-noreply@bitbucket.org 13 Mar '14
13 Mar '14
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/e22d4f2b57a8/
Changeset: e22d4f2b57a8
User: guerler
Date: 2014-03-14 05:06:13
Summary: Charts: Disable box plots
Affected #: 3 files
diff -r 97063e8da923eac2f9abc0bf256f4a7c98b0acec -r e22d4f2b57a8c99cf5b48d36e0ca440934eaa569 config/plugins/visualizations/charts/static/build-app.js
--- a/config/plugins/visualizations/charts/static/build-app.js
+++ b/config/plugins/visualizations/charts/static/build-app.js
@@ -3,4 +3,4 @@
// (c) 2009-2013 Jeremy Ashkenas, DocumentCloud Inc.
// Underscore may be freely distributed under the MIT license.
-(function(){var e=this,t=e._,n={},r=Array.prototype,i=Object.prototype,s=Function.prototype,o=r.push,u=r.slice,a=r.concat,f=i.toString,l=i.hasOwnProperty,c=r.forEach,h=r.map,p=r.reduce,d=r.reduceRight,v=r.filter,m=r.every,g=r.some,y=r.indexOf,b=r.lastIndexOf,w=Array.isArray,E=Object.keys,S=s.bind,x=function(e){if(e instanceof x)return e;if(!(this instanceof x))return new x(e);this._wrapped=e};typeof exports!="undefined"?(typeof module!="undefined"&&module.exports&&(exports=module.exports=x),exports._=x):e._=x,x.VERSION="1.4.4";var T=x.each=x.forEach=function(e,t,r){if(e==null)return;if(c&&e.forEach===c)e.forEach(t,r);else if(e.length===+e.length){for(var i=0,s=e.length;i<s;i++)if(t.call(r,e[i],i,e)===n)return}else for(var o in e)if(x.has(e,o)&&t.call(r,e[o],o,e)===n)return};x.map=x.collect=function(e,t,n){var r=[];return e==null?r:h&&e.map===h?e.map(t,n):(T(e,function(e,i,s){r.push(t.call(n,e,i,s))}),r)};var N="Reduce of empty array with no initial value";x.reduce=x.foldl=x.inject=function(e,t,n,r){var i=arguments.length>2;e==null&&(e=[]);if(p&&e.reduce===p)return r&&(t=x.bind(t,r)),i?e.reduce(t,n):e.reduce(t);T(e,function(e,s,o){i?n=t.call(r,n,e,s,o):(n=e,i=!0)});if(!i)throw new TypeError(N);return n},x.reduceRight=x.foldr=function(e,t,n,r){var i=arguments.length>2;e==null&&(e=[]);if(d&&e.reduceRight===d)return r&&(t=x.bind(t,r)),i?e.reduceRight(t,n):e.reduceRight(t);var s=e.length;if(s!==+s){var o=x.keys(e);s=o.length}T(e,function(u,a,f){a=o?o[--s]:--s,i?n=t.call(r,n,e[a],a,f):(n=e[a],i=!0)});if(!i)throw new TypeError(N);return n},x.find=x.detect=function(e,t,n){var r;return C(e,function(e,i,s){if(t.call(n,e,i,s))return r=e,!0}),r},x.filter=x.select=function(e,t,n){var r=[];return e==null?r:v&&e.filter===v?e.filter(t,n):(T(e,function(e,i,s){t.call(n,e,i,s)&&r.push(e)}),r)},x.reject=function(e,t,n){return x.filter(e,function(e,r,i){return!t.call(n,e,r,i)},n)},x.every=x.all=function(e,t,r){t||(t=x.identity);var i=!0;return e==null?i:m&&e.every===m?e.every(t,r):(T(e,function(e,s,o){if(!(i=i&&t.call(r,e,s,o)))return n}),!!i)};var C=x.some=x.any=function(e,t,r){t||(t=x.identity);var i=!1;return e==null?i:g&&e.some===g?e.some(t,r):(T(e,function(e,s,o){if(i||(i=t.call(r,e,s,o)))return n}),!!i)};x.contains=x.include=function(e,t){return e==null?!1:y&&e.indexOf===y?e.indexOf(t)!=-1:C(e,function(e){return e===t})},x.invoke=function(e,t){var n=u.call(arguments,2),r=x.isFunction(t);return x.map(e,function(e){return(r?t:e[t]).apply(e,n)})},x.pluck=function(e,t){return x.map(e,function(e){return e[t]})},x.where=function(e,t,n){return x.isEmpty(t)?n?void 0:[]:x[n?"find":"filter"](e,function(e){for(var n in t)if(t[n]!==e[n])return!1;return!0})},x.findWhere=function(e,t){return x.where(e,t,!0)},x.max=function(e,t,n){if(!t&&x.isArray(e)&&e[0]===+e[0]&&e.length<65535)return Math.max.apply(Math,e);if(!t&&x.isEmpty(e))return-Infinity;var r={computed:-Infinity,value:-Infinity};return T(e,function(e,i,s){var o=t?t.call(n,e,i,s):e;o>=r.computed&&(r={value:e,computed:o})}),r.value},x.min=function(e,t,n){if(!t&&x.isArray(e)&&e[0]===+e[0]&&e.length<65535)return Math.min.apply(Math,e);if(!t&&x.isEmpty(e))return Infinity;var r={computed:Infinity,value:Infinity};return T(e,function(e,i,s){var o=t?t.call(n,e,i,s):e;o<r.computed&&(r={value:e,computed:o})}),r.value},x.shuffle=function(e){var t,n=0,r=[];return T(e,function(e){t=x.random(n++),r[n-1]=r[t],r[t]=e}),r};var k=function(e){return x.isFunction(e)?e:function(t){return t[e]}};x.sortBy=function(e,t,n){var r=k(t);return x.pluck(x.map(e,function(e,t,i){return{value:e,index:t,criteria:r.call(n,e,t,i)}}).sort(function(e,t){var n=e.criteria,r=t.criteria;if(n!==r){if(n>r||n===void 0)return 1;if(n<r||r===void 0)return-1}return e.index<t.index?-1:1}),"value")};var L=function(e,t,n,r){var i={},s=k(t==null?x.identity:t);return T(e,function(t,o){var u=s.call(n,t,o,e);r(i,u,t)}),i};x.groupBy=function(e,t,n){return L(e,t,n,function(e,t,n){(x.has(e,t)?e[t]:e[t]=[]).push(n)})},x.countBy=function(e,t,n){return L(e,t,n,function(e,t){x.has(e,t)||(e[t]=0),e[t]++})},x.sortedIndex=function(e,t,n,r){n=n==null?x.identity:k(n);var i=n.call(r,t),s=0,o=e.length;while(s<o){var u=s+o>>>1;n.call(r,e[u])<i?s=u+1:o=u}return s},x.toArray=function(e){return e?x.isArray(e)?u.call(e):e.length===+e.length?x.map(e,x.identity):x.values(e):[]},x.size=function(e){return e==null?0:e.length===+e.length?e.length:x.keys(e).length},x.first=x.head=x.take=function(e,t,n){return e==null?void 0:t!=null&&!n?u.call(e,0,t):e[0]},x.initial=function(e,t,n){return u.call(e,0,e.length-(t==null||n?1:t))},x.last=function(e,t,n){return e==null?void 0:t!=null&&!n?u.call(e,Math.max(e.length-t,0)):e[e.length-1]},x.rest=x.tail=x.drop=function(e,t,n){return u.call(e,t==null||n?1:t)},x.compact=function(e){return x.filter(e,x.identity)};var A=function(e,t,n){return T(e,function(e){x.isArray(e)?t?o.apply(n,e):A(e,t,n):n.push(e)}),n};x.flatten=function(e,t){return A(e,t,[])},x.without=function(e){return x.difference(e,u.call(arguments,1))},x.uniq=x.unique=function(e,t,n,r){x.isFunction(t)&&(r=n,n=t,t=!1);var i=n?x.map(e,n,r):e,s=[],o=[];return T(i,function(n,r){if(t?!r||o[o.length-1]!==n:!x.contains(o,n))o.push(n),s.push(e[r])}),s},x.union=function(){return x.uniq(a.apply(r,arguments))},x.intersection=function(e){var t=u.call(arguments,1);return x.filter(x.uniq(e),function(e){return x.every(t,function(t){return x.indexOf(t,e)>=0})})},x.difference=function(e){var t=a.apply(r,u.call(arguments,1));return x.filter(e,function(e){return!x.contains(t,e)})},x.zip=function(){var e=u.call(arguments),t=x.max(x.pluck(e,"length")),n=new Array(t);for(var r=0;r<t;r++)n[r]=x.pluck(e,""+r);return n},x.unzip=function(e){var t=[];return x.each(e,function(e,n){x.each(e,function(e,r){t.length<=r&&(t[r]=[]),t[r][n]=e})}),t},x.object=function(e,t){if(e==null)return{};var n={};for(var r=0,i=e.length;r<i;r++)t?n[e[r]]=t[r]:n[e[r][0]]=e[r][1];return n},x.indexOf=function(e,t,n){if(e==null)return-1;var r=0,i=e.length;if(n){if(typeof n!="number")return r=x.sortedIndex(e,t),e[r]===t?r:-1;r=n<0?Math.max(0,i+n):n}if(y&&e.indexOf===y)return e.indexOf(t,n);for(;r<i;r++)if(e[r]===t)return r;return-1},x.lastIndexOf=function(e,t,n){if(e==null)return-1;var r=n!=null;if(b&&e.lastIndexOf===b)return r?e.lastIndexOf(t,n):e.lastIndexOf(t);var i=r?n:e.length;while(i--)if(e[i]===t)return i;return-1},x.range=function(e,t,n){arguments.length<=1&&(t=e||0,e=0),n=arguments[2]||1;var r=Math.max(Math.ceil((t-e)/n),0),i=0,s=new Array(r);while(i<r)s[i++]=e,e+=n;return s};var O=function(){};x.bind=function(e,t){var n,r;if(e.bind===S&&S)return S.apply(e,u.call(arguments,1));if(!x.isFunction(e))throw new TypeError;return n=u.call(arguments,2),r=function(){if(this instanceof r){O.prototype=e.prototype;var i=new O;O.prototype=null;var s=e.apply(i,n.concat(u.call(arguments)));return Object(s)===s?s:i}return e.apply(t,n.concat(u.call(arguments)))}},x.partial=function(e){var t=u.call(arguments,1);return function(){return e.apply(this,t.concat(u.call(arguments)))}},x.bindAll=function(e){var t=u.call(arguments,1);if(t.length===0)throw new Error("bindAll must be passed function names");return T(t,function(t){e[t]=x.bind(e[t],e)}),e},x.memoize=function(e,t){var n={};return t||(t=x.identity),function(){var r=t.apply(this,arguments);return x.has(n,r)?n[r]:n[r]=e.apply(this,arguments)}},x.delay=function(e,t){var n=u.call(arguments,2);return setTimeout(function(){return e.apply(null,n)},t)},x.defer=function(e){return x.delay.apply(x,[e,1].concat(u.call(arguments,1)))},x.throttle=function(e,t,n){var r,i,s,o,u=0,a=function(){u=new Date,s=null,o=e.apply(r,i)};return function(){var f=new Date;!u&&n===!1&&(u=f);var l=t-(f-u);return r=this,i=arguments,l<=0?(clearTimeout(s),s=null,u=f,o=e.apply(r,i)):s||(s=setTimeout(a,l)),o}},x.debounce=function(e,t,n){var r,i;return function(){var s=this,o=arguments,u=function(){r=null,n||(i=e.apply(s,o))},a=n&&!r;return clearTimeout(r),r=setTimeout(u,t),a&&(i=e.apply(s,o)),i}},x.once=function(e){var t=!1,n;return function(){return t?n:(t=!0,n=e.apply(this,arguments),e=null,n)}},x.wrap=function(e,t){return function(){var n=[e];return o.apply(n,arguments),t.apply(this,n)}},x.compose=function(){var e=arguments;return function(){var t=arguments;for(var n=e.length-1;n>=0;n--)t=[e[n].apply(this,t)];return t[0]}},x.after=function(e,t){return e<=0?t():function(){if(--e<1)return t.apply(this,arguments)}},x.keys=E||function(e){if(e!==Object(e))throw new TypeError("Invalid object");var t=[];for(var n in e)x.has(e,n)&&t.push(n);return t},x.values=function(e){var t=[];for(var n in e)x.has(e,n)&&t.push(e[n]);return t},x.pairs=function(e){var t=[];for(var n in e)x.has(e,n)&&t.push([n,e[n]]);return t},x.invert=function(e){var t={};for(var n in e)x.has(e,n)&&(t[e[n]]=n);return t},x.functions=x.methods=function(e){var t=[];for(var n in e)x.isFunction(e[n])&&t.push(n);return t.sort()},x.extend=function(e){return T(u.call(arguments,1),function(t){if(t)for(var n in t)e[n]=t[n]}),e},x.pick=function(e){var t={},n=a.apply(r,u.call(arguments,1));return T(n,function(n){n in e&&(t[n]=e[n])}),t},x.omit=function(e){var t={},n=a.apply(r,u.call(arguments,1));for(var i in e)x.contains(n,i)||(t[i]=e[i]);return t},x.defaults=function(e){return T(u.call(arguments,1),function(t){if(t)for(var n in t)e[n]===void 0&&(e[n]=t[n])}),e},x.clone=function(e){return x.isObject(e)?x.isArray(e)?e.slice():x.extend({},e):e},x.tap=function(e,t){return t(e),e};var M=function(e,t,n,r){if(e===t)return e!==0||1/e==1/t;if(e==null||t==null)return e===t;e instanceof x&&(e=e._wrapped),t instanceof x&&(t=t._wrapped);var i=f.call(e);if(i!=f.call(t))return!1;switch(i){case"[object String]":return e==String(t);case"[object Number]":return e!=+e?t!=+t:e==0?1/e==1/t:e==+t;case"[object Date]":case"[object Boolean]":return+e==+t;case"[object RegExp]":return e.source==t.source&&e.global==t.global&&e.multiline==t.multiline&&e.ignoreCase==t.ignoreCase}if(typeof e!="object"||typeof t!="object")return!1;var s=n.length;while(s--)if(n[s]==e)return r[s]==t;n.push(e),r.push(t);var o=0,u=!0;if(i=="[object Array]"){o=e.length,u=o==t.length;if(u)while(o--)if(!(u=M(e[o],t[o],n,r)))break}else{var a=e.constructor,l=t.constructor;if(a!==l&&!(x.isFunction(a)&&a instanceof a&&x.isFunction(l)&&l instanceof l))return!1;for(var c in e)if(x.has(e,c)){o++;if(!(u=x.has(t,c)&&M(e[c],t[c],n,r)))break}if(u){for(c in t)if(x.has(t,c)&&!(o--))break;u=!o}}return n.pop(),r.pop(),u};x.isEqual=function(e,t){return M(e,t,[],[])},x.isEmpty=function(e){if(e==null)return!0;if(x.isArray(e)||x.isString(e))return e.length===0;for(var t in e)if(x.has(e,t))return!1;return!0},x.isElement=function(e){return!!e&&e.nodeType===1},x.isArray=w||function(e){return f.call(e)=="[object Array]"},x.isObject=function(e){return e===Object(e)},T(["Arguments","Function","String","Number","Date","RegExp"],function(e){x["is"+e]=function(t){return f.call(t)=="[object "+e+"]"}}),x.isArguments(arguments)||(x.isArguments=function(e){return!!e&&!!x.has(e,"callee")}),typeof /./!="function"&&(x.isFunction=function(e){return typeof e=="function"}),x.isFinite=function(e){return isFinite(e)&&!isNaN(parseFloat(e))},x.isNaN=function(e){return x.isNumber(e)&&e!=+e},x.isBoolean=function(e){return e===!0||e===!1||f.call(e)=="[object Boolean]"},x.isNull=function(e){return e===null},x.isUndefined=function(e){return e===void 0},x.has=function(e,t){return l.call(e,t)},x.noConflict=function(){return e._=t,this},x.identity=function(e){return e},x.times=function(e,t,n){var r=Array(e);for(var i=0;i<e;i++)r[i]=t.call(n,i);return r},x.random=function(e,t){return t==null&&(t=e,e=0),e+Math.floor(Math.random()*(t-e+1))};var _={escape:{"&":"&","<":"<",">":">",'"':""","'":"'","/":"/"}};_.unescape=x.invert(_.escape);var D={escape:new RegExp("["+x.keys(_.escape).join("")+"]","g"),unescape:new RegExp("("+x.keys(_.unescape).join("|")+")","g")};x.each(["escape","unescape"],function(e){x[e]=function(t){return t==null?"":(""+t).replace(D[e],function(t){return _[e][t]})}}),x.result=function(e,t){if(e==null)return void 0;var n=e[t];return x.isFunction(n)?n.call(e):n},x.mixin=function(e){T(x.functions(e),function(t){var n=x[t]=e[t];x.prototype[t]=function(){var e=[this._wrapped];return o.apply(e,arguments),F.call(this,n.apply(x,e))}})};var P=0;x.uniqueId=function(e){var t=++P+"";return e?e+t:t},x.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var H=/(.)^/,B={"'":"'","\\":"\\","\r":"r","\n":"n"," ":"t","\u2028":"u2028","\u2029":"u2029"},j=/\\|'|\r|\n|\t|\u2028|\u2029/g;x.template=function(e,t,n){var r;n=x.defaults({},n,x.templateSettings);var i=new RegExp([(n.escape||H).source,(n.interpolate||H).source,(n.evaluate||H).source].join("|")+"|$","g"),s=0,o="__p+='";e.replace(i,function(t,n,r,i,u){return o+=e.slice(s,u).replace(j,function(e){return"\\"+B[e]}),n&&(o+="'+\n((__t=("+n+"))==null?'':_.escape(__t))+\n'"),r&&(o+="'+\n((__t=("+r+"))==null?'':__t)+\n'"),i&&(o+="';\n"+i+"\n__p+='"),s=u+t.length,t}),o+="';\n",n.variable||(o="with(obj||{}){\n"+o+"}\n"),o="var __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'');};\n"+o+"return __p;\n";try{r=new Function(n.variable||"obj","_",o)}catch(u){throw u.source=o,u}if(t)return r(t,x);var a=function(e){return r.call(this,e,x)};return a.source="function("+(n.variable||"obj")+"){\n"+o+"}",a},x.chain=function(e){return x(e).chain()};var F=function(e){return this._chain?x(e).chain():e};x.mixin(x),T(["pop","push","reverse","shift","sort","splice","unshift"],function(e){var t=r[e];x.prototype[e]=function(){var n=this._wrapped;return t.apply(n,arguments),(e=="shift"||e=="splice")&&n.length===0&&delete n[0],F.call(this,n)}}),T(["concat","join","slice"],function(e){var t=r[e];x.prototype[e]=function(){return F.call(this,t.apply(this._wrapped,arguments))}}),x.extend(x.prototype,{chain:function(){return this._chain=!0,this},value:function(){return this._wrapped}})}).call(this),define("libs/underscore",function(e){return function(){var t,n;return t||e._}}(this)),define("utils/utils",["libs/underscore"],function(e){function t(e,t,r){n("GET",e,{},t,r)}function n(e,t,n,r,i){if(e=="GET"||e=="DELETE")t.indexOf("?")==-1?t+="?":t+="&",t+=$.param(n);var s=new XMLHttpRequest;s.open(e,t,!0),s.setRequestHeader("Accept","application/json"),s.setRequestHeader("Cache-Control","no-cache"),s.setRequestHeader("X-Requested-With","XMLHttpRequest"),s.setRequestHeader("Content-Type","application/json"),s.onloadend=function(){var e=s.status;if(e==200){try{response=jQuery.parseJSON(s.responseText)}catch(t){response=s.responseText}r&&r(response)}else i&&i(e)},e=="GET"||e=="DELETE"?s.send():s.send(JSON.stringify(n))}function r(e,t){var n=$('<div class="'+e+'"></div>');n.appendTo(":eq(0)");var r=n.css(t);return n.remove(),r}function i(e){$('link[href^="'+e+'"]').length||$('<link href="'+galaxy_config.root+e+'" rel="stylesheet">').appendTo("head")}function s(t,n){return t?e.defaults(t,n):n}function o(e,t){var n="";if(e>=1e11)e/=1e11,n="TB";else if(e>=1e8)e/=1e8,n="GB";else if(e>=1e5)e/=1e5,n="MB";else if(e>=100)e/=100,n="KB";else{if(!(e>0))return"<strong>-</strong>";e*=10,n="b"}var r=Math.round(e)/10;return t?r+" "+n:"<strong>"+r+"</strong> "+n}function u(){return(new Date).getTime().toString(36)}function a(e){var t=$("<p></p>");return t.append(e),t}function f(){var e=new Date,t=(e.getHours()<10?"0":"")+e.getHours(),n=(e.getMinutes()<10?"0":"")+e.getMinutes(),r=e.getDate()+"/"+(e.getMonth()+1)+"/"+e.getFullYear()+", "+t+":"+n;return r}return{cssLoadFile:i,cssGetAttribute:r,get:t,merge:s,bytesToString:o,uuid:u,time:f,wrap:a,request:n}}),define("mvc/ui/ui-modal",["utils/utils"],function(e){var t=Backbone.View.extend({elMain:"body",optionsDefault:{title:"ui-modal",body:"",backdrop:!0,height:null,width:null,closing_events:!1},buttonList:{},initialize:function(e){e&&this._create(e)},show:function(e){this.initialize(e),this.options.height?(this.$body.css("height",this.options.height),this.$body.css("overflow","hidden")):this.$body.css("max-height",$(window).height()/2),this.options.width&&this.$dialog.css("width",this.options.width),this.visible?this.$el.show():this.$el.fadeIn("fast"),this.visible=!0},hide:function(){this.visible=!1,this.$el.fadeOut("fast")},enableButton:function(e){var t=this.buttonList[e];this.$buttons.find("#"+t).prop("disabled",!1)},disableButton:function(e){var t=this.buttonList[e];this.$buttons.find("#"+t).prop("disabled",!0)},showButton:function(e){var t=this.buttonList[e];this.$buttons.find("#"+t).show()},hideButton:function(e){var t=this.buttonList[e];this.$buttons.find("#"+t).hide()},getButton:function(e){var t=this.buttonList[e];return this.$buttons.find("#"+t)},scrollTop:function(){return this.$body.scrollTop()},_create:function(e){var t=this;this.options=_.defaults(e,this.optionsDefault),this.options.body=="progress"&&(this.options.body=$('<div class="progress progress-striped active"><div class="progress-bar progress-bar-info" style="width:100%"></div></div>')),this.$el&&(this.$el.remove(),$(document).off("keyup")),this.setElement(this._template(this.options.title)),this.$dialog=this.$el.find(".modal-dialog"),this.$body=this.$el.find(".modal-body"),this.$footer=this.$el.find(".modal-footer"),this.$buttons=this.$el.find(".buttons"),this.$backdrop=this.$el.find(".modal-backdrop"),this.$body.html(this.options.body),this.options.backdrop||this.$backdrop.removeClass("in");if(this.options.buttons){this.buttonList={};var n=0;$.each(this.options.buttons,function(e,r){var i="button-"+n++;t.$buttons.append($('<button id="'+i+'"></button>').text(e).click(r)).append(" "),t.buttonList[e]=i})}else this.$footer.hide();$(this.elMain).append($(this.el)),this.options.closing_events&&(this.options.buttons.Pause||$(document).on("keyup",function(e){e.keyCode==27&&t.hide()}),this.$el.find(".modal-backdrop").on("click",function(){t.hide()}))},_template:function(e){return'<div class="modal"><div class="modal-backdrop fade in" style="z-index: -1;"></div><div class="modal-dialog"><div class="modal-content"><div class="modal-header"><button type="button" class="close" style="display: none;">×</button><h4 class="title">'+e+"</h4>"+"</div>"+'<div class="modal-body" style="position: static;"></div>'+'<div class="modal-footer">'+'<div class="buttons" style="float: right;"></div>'+"</div>"+"</div"+"</div>"+"</div>"}});return{View:t}}),define("mvc/ui/ui-portlet",["utils/utils"],function(e){var t=Backbone.View.extend({visible:!1,optionsDefault:{title:"",icon:"",buttons:null,body:null,height:null,operations:null,placement:"bottom",overflow:"auto"},$title:null,$content:null,$buttons:null,$operations:null,initialize:function(t){this.options=e.merge(t,this.optionsDefault),this.setElement(this._template(this.options)),this.$content=this.$el.find("#content"),this.$title=this.$el.find("#title-text"),this.options.height&&(this.$el.find("#body").css("height",this.options.height),this.$el.find("#content").css("overflow",this.options.overflow)),this.$buttons=$(this.el).find("#buttons");if(this.options.buttons){var n=this;$.each(this.options.buttons,function(e,t){t.$el.prop("id",e),n.$buttons.append(t.$el)})}else this.$buttons.remove();this.$operations=$(this.el).find("#operations");if(this.options.operations){var n=this;$.each(this.options.operations,function(e,t){t.$el.prop("id",e),n.$operations.append(t.$el)})}this.options.body&&this.append(this.options.body)},append:function(t){this.$content.append(e.wrap(t))},content:function(){return this.$content},show:function(){this.$el.fadeIn("fast"),this.visible=!0},hide:function(){this.$el.fadeOut("fast"),this.visible=!1},enableButton:function(e){this.$buttons.find("#"+e).prop("disabled",!1)},disableButton:function(e){this.$buttons.find("#"+e).prop("disabled",!0)},hideOperation:function(e){this.$operations.find("#"+e).hide()},showOperation:function(e){this.$operations.find("#"+e).show()},setOperation:function(e,t){var n=this.$operations.find("#"+e);n.off("click"),n.on("click",t)},title:function(e){var t=this.$title;return e&&t.html(e),t.html()},_template:function(e){var t='<div class="toolForm">';if(e.title||e.icon)t+='<div id="title" class="toolFormTitle" style="overflow: hidden;"><div id="operations" style="float: right;"></div><div style="overflow: hidden;">',e.icon&&(t+='<i style="padding-top: 3px; float: left; font-size: 1.2em" class="icon fa '+e.icon+'"> </i>'),t+='<div id="title-text" style="padding-top: 2px; float: left;">'+e.title+"</div>",t+="</div></div>";return t+='<div id="body" class="toolFormBody">',e.placement=="top"&&(t+='<div id="buttons" class="buttons" style="height: 50px; padding: 10px;"></div>'),t+='<div id="content" class="content" style="height: inherit; padding: 10px;"></div>',e.placement=="bottom"&&(t+='<div id="buttons" class="buttons" style="height: 50px; padding: 10px;"></div>'),t+="</div></div>",t}});return{View:t}}),define("plugin/library/ui-select",["utils/utils"],function(e){var t=Backbone.View.extend({optionsDefault:{id:"",cls:"",empty:"No data available",visible:!0,wait:!1},selected:null,initialize:function(t){this.options=e.merge(t,this.optionsDefault),this.setElement(this._template(this.options)),this.$select=this.$el.find("#select"),this.$icon=this.$el.find("#icon"),this.selected=this.options.value;var n=this;this.options.onchange&&this.$select.on("change",function(){n.value(n.$select.val())}),this._refresh(),this.options.visible||this.hide(),this.options.wait?this.wait():this.show()},value:function(e){var t=this.selected;e!==undefined&&(this.selected=e,this.$select.val(e));var n=this.selected;return n&&n!=t&&this.options.onchange&&this.options.onchange(n),n},text:function(){return this.$select.find("option:selected").text()},show:function(){this.$icon.removeClass(),this.$icon.addClass("fa fa-caret-down"),this.$select.show(),this.$el.show()},hide:function(){this.$el.hide()},wait:function(){this.$icon.removeClass(),this.$icon.addClass("fa fa-spinner fa-spin"),this.$select.hide()},disabled:function(){return this.$select.is(":disabled")},enable:function(){this.$select.prop("disabled",!1)},disable:function(){this.$select.prop("disabled",!0)},add:function(e){this.$select.append(this._templateOption(e)),this._refresh()},del:function(e){this.$select.find("option[value="+e+"]").remove(),this.$select.trigger("change"),this._refresh()},update:function(e){this.$select.find("option").remove();for(var t in e)this.$select.append(this._templateOption(e[t]));!this.selected&&e.length>0&&this.value(e[0].value),this._refresh()},_refresh:function(){this.$select.find("option[value=null]").remove();var e=this.$select.find("option").length;e==0?(this.$select.append(this._templateOption({value:"null",label:this.options.empty})),this.disable()):(this.enable(),this.selected&&this.$select.val(this.selected))},_exists:function(e){return 0!=this.$select.find("option[value="+e+"]").length},_templateOption:function(e){return'<option value="'+e.value+'">'+e.label+"</option>"},_template:function(e){var t='<div id="'+e.id+'" class="styled-select">'+'<div class="button">'+'<i id="icon"/>'+"</div>"+'<select id="select" class="select '+e.cls+" "+e.id+'">';for(key in e.data){var n=e.data[key],r="";if(n.value==e.value||n.value=="")r="selected";t+='<option value="'+n.value+'" '+r+">"+n.label+"</option>"}return t+="</select></div>",t}});return{View:t}}),define("plugin/library/ui",["utils/utils","plugin/library/ui-select"],function(e,t){var n=Backbone.View.extend({optionsDefault:{title:""},initialize:function(t){this.options=e.merge(t,this.optionsDefault),this.setElement(this._template(this.options))},title:function(e){this.$el.find("b").html(e)},_template:function(e){return"<label><b>"+e.title+"</b></label>"},value:function(){return options.title}}),r=Backbone.View.extend({optionsDefault:{id:null,title:"","float":"right",cls:"btn-default",icon:""},initialize:function(t){this.options=e.merge(t,this.optionsDefault),this.setElement(this._template(this.options)),$(this.el).on("click",t.onclick),$(this.el).tooltip({title:t.tooltip,placement:"bottom"})},_template:function(e){var t='<button id="'+e.id+'" type="submit" style="margin-right: 5px; float: '+e.float+';" type="button" class="btn '+e.cls+'">';return e.icon&&(t+='<i class="icon fa '+e.icon+'"></i> '),t+=e.title+"</button>",t}}),i=Backbone.View.extend({optionsDefault:{"float":"right",icon:"",tooltip:"",placement:"bottom"},initialize:function(t){this.options=e.merge(t,this.optionsDefault),this.setElement(this._template(this.options)),$(this.el).tooltip({title:t.tooltip,placement:"bottom"})},_template:function(e){return'<span class="fa '+e.icon+'" style="font-size: 1.2em;"/>'}}),s=Backbone.View.extend({optionsDefault:{title:"",id:null,"float":"right",cls:"icon-btn",icon:"",tooltip:""},initialize:function(t){this.options=e.merge(t,this.optionsDefault),this.setElement(this._template(this.options)),$(this.el).on("click",t.onclick),$(this.el).tooltip({title:t.tooltip,placement:"bottom"})},_template:function(e){var t="";e.title&&(t="width: auto;");var n='<div id="'+e.id+'" style="margin-right: 5px; float: '+e.float+"; "+t+'" class="'+e.cls+'">';return e.title?n+='<div style="margin-right: 5px; margin-left: 5px;"><i class="icon fa '+e.icon+'"/> '+'<span style="position: relative; font-size: 0.8em; font-weight: normal; top: -1px;">'+e.title+"</span>"+"</div>":n+='<i class="icon fa '+e.icon+'"/>',n+="</div>",n}}),o=Backbone.View.extend({optionsDefault:{title:""},initialize:function(t){this.options=e.merge(t,this.optionsDefault),this.setElement(this._template(this.options)),$(this.el).on("click",t.onclick)},_template:function(e){return'<div><a href="javascript:void(0)">'+e.title+"</a></div>"}}),u=Backbone.View.extend({optionsDefault:{message:"",status:"info",persistent:!1},initialize:function(t){this.options=e.merge(t,this.optionsDefault),this.setElement("<div></div>")},update:function(t){this.options=e.merge(t,this.optionsDefault);if(t.message!=""){this.$el.html(this._template(this.options)),this.$el.fadeIn();if(!t.persistent){var n=this;window.setTimeout(function(){n.$el.is(":visible")?n.$el.fadeOut():n.$el.hide()},3e3)}}else this.$el.fadeOut()},_template:function(e){return'<div class="alert alert-'+e.status+'" style="padding: 2px 2px 2px 10px;">'+e.message+"</div>"}}),a=Backbone.View.extend({optionsDefault:{onclick:null,searchword:""},initialize:function(t){this.options=e.merge(t,this.optionsDefault),this.setElement(this._template(this.options));var n=this;this.options.onclick&&this.$el.on("submit",function(e){var t=n.$el.find("#search");n.options.onclick(t.val())})},_template:function(e){return'<div class="search"><form onsubmit="return false;"><input id="search" class="form-control input-sm" type="text" name="search" placeholder="Search..." value="'+e.searchword+'">'+'<button type="submit" class="btn search-btn">'+'<i class="fa fa-search"></i>'+"</button>"+"</form>"+"</div>"}}),f=Backbone.View.extend({optionsDefault:{title:"Unlabeled",body:null},initialize:function(t){this.options=e.merge(t,this.optionsDefault),this.setElement(this._template(this.options)),this.options.body&&this.$el.find(".body").append(this.options.body)},_template:function(e){return'<div id="title" class="title">'+e.title+":"+"</div>"}}),l=Backbone.View.extend({optionsDefault:{id:"",title:"",target:"",href:"",onunload:null,onclick:null,visible:!0,icon:null,tag:""},$menu:null,initialize:function(t){this.options=e.merge(t,this.optionsDefault),this.setElement($(this._template(this.options)));var n=$(this.el).find(".root"),r=this;n.on("click",function(e){e.preventDefault(),r.options.onclick&&r.options.onclick()}),this.options.visible||this.hide()},show:function(){$(this.el).show()},hide:function(){$(this.el).hide()},addMenu:function(t){var n={title:"",target:"",href:"",onclick:null,divider:!1,icon:null};n=e.merge(t,n),this.$menu||($(this.el).append(this._templateMenu()),this.$menu=$(this.el).find(".menu"));var r=$(this._templateMenuItem(n));r.on("click",function(e){e.preventDefault(),n.onclick&&n.onclick()}),this.$menu.append(r),n.divider&&this.$menu.append($(this._templateDivider()))},_templateMenuItem:function(e){var t='<li><a href="'+e.href+'" target="'+e.target+'">';return e.icon&&(t+='<i class="fa '+e.icon+'"></i>'),t+=" "+e.title+"</a>"+"</li>",t},_templateMenu:function(){return'<ul class="menu dropdown-menu pull-right" role="menu"></ul>'},_templateDivider:function(){return'<li class="divider"></li>'},_template:function(e){var t='<div id="'+e.id+'" class="button-menu btn-group">'+'<button type="button" class="root btn btn-default dropdown-toggle" data-toggle="dropdown">';return e.icon&&(t+='<i class="fa '+e.icon+'"></i>'),"</button></div>",t}}),c=Backbone.View.extend({optionsDefault:{value:"",type:"text",placeholder:"",disabled:!1,visible:!0},initialize:function(t){this.options=e.merge(t,this.optionsDefault),this.setElement(this._template(this.options)),this.options.disabled&&this.$el.prop("disabled",!0),this.options.visible||this.$el.hide();var n=this;this.options.onchange&&this.$el.on("input",function(){n.options.onchange()})},value:function(e){return e!==undefined&&this.$el.val(e),this.$el.val()},_template:function(e){return'<input id="'+e.id+'" type="'+e.type+'" value="'+e.value+'" placeholder="'+e.placeholder+'" class="form-control">'}});return{Label:n,Button:r,Icon:i,ButtonIcon:s,Input:c,Anchor:o,Message:u,Searchbox:a,Title:f,Select:t,ButtonMenu:l}}),define("plugin/library/jobs",["utils/utils"],function(e){return Backbone.Model.extend({initialize:function(t,n){this.app=t,this.options=e.merge(n,this.optionsDefault)},submit:function(t,n,r){var i=this,s=t.id,o=t.get("type"),u=this.app.types.get(o);data={tool_id:"rkit",history_id:t.get("history_id"),inputs:{input:t.get("dataset_hid"),module:o,options:n}};var a=t.get("dataset_id_job");a&&e.request("PUT",config.root+"api/histories/"+t.get("history_id")+"/contents/"+a,{deleted:!0}),t.state("submit","Sending job request..."),e.request("POST",config.root+"api/tools",data,function(e){if(!e.outputs||e.outputs.length==0)t.state("failed","Job submission failed. No response.");else{Galaxy&&Galaxy.currHistoryPanel&&Galaxy.currHistoryPanel.refreshHdas();var n=e.outputs[0];t.state("queued","Job has been queued..."),t.set("dataset_id_job",n.id),i._loop(n.id,function(e){switch(e.state){case"ok":return t.state("success","Job completed successfully..."),r(e),!0;case"error":return t.state("failed","Job has failed. Please check the history for details."),!0;case"running":return t.state("running","Job is running..."),!1}})}},function(e){t.state("failed","Job submission failed. Please make sure that 'R-kit' is installed.")})},_loop:function(t,n){var r=this;e.request("GET",config.root+"api/jobs/"+t,{},function(e){n(e)||setTimeout(function(){r._loop(t,n)},r.app.config.get("query_timeout"))})}})}),define("plugin/library/datasets",["utils/utils"],function(e){return Backbone.Collection.extend({list:{},initialize:function(t,n){this.app=t,this.options=e.merge(n,this.optionsDefault)},request:function(t,n,r){var i=this;if(t.groups)this._fetch(t,n);else{var s=this.list[t.id];if(s){n(s);return}e.request("GET",config.root+"api/datasets/"+t.id,{},function(e){switch(e.state){case"error":r&&r(e);break;default:i.list[t.id]=e,n(e)}})}},_fetch:function(t,n){var r=t.start?t.start:0,i=Math.abs(t.end-t.start),s=this.app.config.get("query_limit");if(!i||i>s)i=s;var o="",u={},a=0;for(var f in t.groups){var l=t.groups[f];for(var c in l.columns){var h=l.columns[c];o+=h+",",u[h]=a,a++}}if(a==0){n({});return}o=o.substring(0,o.length-1);var p=t.groups.slice(0);for(var f in p)p[f].values=[];var d=this;e.request("GET",config.root+"api/datasets/"+t.id,{data_type:"raw_data",provider:"dataset-column",limit:i,offset:r,indeces:o},function(e){for(var i in e.data){var s=e.data[i];for(var o in t.groups){var a=t.groups[o],f={x:parseInt(i)+r};for(var l in a.columns){var c=a.columns[l],h=u[c],d=s[h];if(isNaN(d)||!d)d=0;f[l]=d}p[o].values.push(f)}}n(p)})}})}),define("plugin/library/ui-table",["utils/utils"],function(e){return Backbone.View.extend({row:null,row_count:0,optionsDefault:{content:"No content available.",onchange:null,ondblclick:null,onconfirm:null},events:{click:"_onclick",dblclick:"_ondblclick"},first:!0,initialize:function(t){this.options=e.merge(t,this.optionsDefault);var n=$(this._template(t));this.$thead=n.find("thead"),this.$tbody=n.find("tbody"),this.$tmessage=n.find("tmessage"),this.setElement(n),this.row=$("<tr></tr>")},addHeader:function(e){var t=$("<th></th>");t.append(e),this.row.append(t)},appendHeader:function(){this.$thead.append(this.row),this.row=$("<tr></tr>")},add:function(e,t){var n=$("<td></td>");t&&n.css("width",t),n.append(e),this.row.append(n)},append:function(e){this._commit(e)},prepend:function(e){this._commit(e,!0)},remove:function(e){var t=this.$tbody.find("#"+e);t.length>0&&(t.remove(),this.row_count--,this._refresh())},removeAll:function(){this.$tbody.html(""),this.row_count=0,this._refresh()},value:function(e){this.before=this.$tbody.find(".current").attr("id"),e!==undefined&&(this.$tbody.find("tr").removeClass("current"),e&&this.$tbody.find("#"+e).addClass("current"));var t=this.$tbody.find(".current").attr("id");return t===undefined?null:(t!=this.before&&this.options.onchange&&this.options.onchange(e),t)},size:function(){return this.$tbody.find("tr").length},_commit:function(e,t){this.remove(e),this.row.attr("id",e),t?this.$tbody.prepend(this.row):this.$tbody.append(this.row),this.row=$("<tr></tr>"),this.row_count++,this._refresh()},_onclick:function(e){var t=this.value(),n=$(e.target).closest("tr").attr("id");n&&t!=n&&(this.options.onconfirm?this.options.onconfirm(n):this.value(n))},_ondblclick:function(e){var t=this.value();t&&this.options.ondblclick&&this.options.ondblclick(t)},_refresh:function(){this.row_count==0?this.$tmessage.show():this.$tmessage.hide()},_template:function(e){return'<div><table class="grid"><thead></thead><tbody style="cursor: pointer;"></tbody></table><tmessage>'+e.content+"</tmessage>"+"<div>"}})}),define("plugin/models/group",[],function(){return Backbone.Model.extend({defaults:{key:"Data label",date:""},reset:function(){this.clear({silent:!0}).set(this.defaults),this.trigger("reset",this)}})}),define("plugin/views/viewport",["mvc/ui/ui-portlet","plugin/library/ui","utils/utils"],function(e,t,n){return Backbone.View.extend({list:{},optionsDefault:{height:300},initialize:function(r,i){this.app=r,this.options=n.merge(i,this.optionsDefault),this.portlet=new e.View({title:"title",height:this.options.height,overflow:"hidden",operations:{edit:new t.ButtonIcon({icon:"fa-gear",tooltip:"Customize Chart",title:"Customize"})}}),this.setElement(this.portlet.$el);var s=this;this.app.charts.on("remove",function(e){s._removeChart(e.id)}),this.app.charts.on("redraw",function(e){e.ready()?s._refreshChart(e):e.on("change:state",function(){e.ready()&&s._refreshChart(e)})})},showChart:function(e){this.show(),this.hideCharts();var t=this.list[e];if(t){var n=self.app.charts.get(e);this.portlet.title(n.get("title")),this.portlet.setOperation("edit",function(){self.app.chart.copy(n),self.app.charts_view.hide(),self.app.chart_view.$el.show()}),t.$el.show(),$(window).trigger("resize")}},hideCharts:function(){this.$el.find(".item").hide()},show:function(){$(".tooltip").hide(),this.$el.show()},hide:function(){$(".tooltip").hide(),this.$el.hide()},_refreshChart:function(e){var t=this;if(!e.ready()){t.app.log("viewport:_refreshChart()","Invalid attempt to refresh chart before completion.");return}var n=e.id;this._removeChart(n);var r="#"+n,i=$(this._template({id:r}));this.portlet.append(i);var s=d3.select(r+" svg");this.list[n]={svg:s,$el:i},this.showChart(n),e.off("change:state"),e.on("change:state",function(){var t=i.find("#info"),n=t.find("#icon");n.removeClass(),t.show(),t.find("#text").html(e.get("state_info"));var r=e.get("state");switch(r){case"ok":t.hide();break;case"failed":n.addClass("fa fa-warning");break;default:n.addClass("fa fa-spinner fa-spin")}}),e.state("wait","Please wait...");var o=e.get("type"),u=this.app.types.get(o),t=this;require(["plugin/charts/"+o+"/"+o],function(n){var r=new n(t.app,{svg:s}),i=u.mode;i=="execute"?t.app.jobs.submit(e,t._defaultRequestString(e),function(){r.draw(e,t._defaultRequestDictionary(e))}):r.draw(e,t._defaultRequestDictionary(e))})},_removeChart:function(e){var t=this.list[e];t&&(t.svg.remove(),t.$el.remove())},_template:function(e){return'<div id="'+e.id.substr(1)+'" class="item">'+'<span id="info">'+'<span id="icon" style="font-size: 1.2em; display: inline-block;"/>'+'<span id="text" style="position: relative; margin-left: 5px; top: -1px; font-size: 1.0em;"/>'+"</span>"+'<svg style="height: auto;"/>'+"</div>"},_defaultRequestString:function(e){var t=this.app.types.get(e.get("type")),n="",r=0;return e.groups.each(function(e){for(var i in t.columns)n+=i+"_"+ ++r+":"+(parseInt(e.get(i))+1)+", "}),n.substring(0,n.length-2)},_defaultRequestDictionary:function(e){var t=this.app.types.get(e.get("type")),n={id:e.get("dataset_id"),groups:[]},r=0;return e.groups.each(function(e){var i={};for(var s in t.columns)i[s]=e.get(s);n.groups.push({key:++r+":"+e.get("key"),columns:i})}),n}})}),define("plugin/views/charts",["mvc/ui/ui-portlet","plugin/library/ui-table","plugin/library/ui","utils/utils","plugin/models/group","plugin/views/viewport"],function(e,t,n,r,i,s){return Backbone.View.extend({initialize:function(i,o){this.app=i,this.viewport_view=new s(i),this.table=new t({content:"Add charts to this table.",ondblclick:function(e){var t=u.app.charts.get(e);u.app.chart.copy(t),u.hide(),u.app.chart_view.$el.show()},onchange:function(e){var t=u.app.charts.get(e);u.app.config.set("title",t.get("title")),u.viewport_view.showChart(e)}});var u=this;this.portlet=new e.View({icon:"fa-list",title:"List of created charts:",height:100,operations:{"new":new n.ButtonIcon({icon:"fa-magic",tooltip:"Create a new Chart",title:"New",onclick:function(){u.hide(),u.app.chart.reset(),u.app.chart_view.$el.show()}}),"delete":new n.ButtonIcon({icon:"fa-trash-o",tooltip:"Delete this Chart",title:"Delete",onclick:function(){var e=u.table.value();if(!e)return;var t=u.app.charts.get(e);u.app.modal.show({title:"Are you sure?",body:'The selected chart "'+t.get("title")+'" will be irreversibly deleted.',buttons:{Cancel:function(){u.app.modal.hide()},Delete:function(){u.app.modal.hide(),u.app.charts.remove(e)}}})}})}}),this.portlet.append(this.table.$el),this.app.options.config.widget||this.$el.append(this.portlet.$el),this.$el.append(r.wrap("")),this.$el.append(this.viewport_view.$el);var u=this;this.app.charts.on("add",function(e){u._addChart(e)}),this.app.charts.on("remove",function(e){u._removeChart(e)}),this.app.charts.on("change",function(e){u._changeChart(e)})},hide:function(){$(".tooltip").hide(),this.$el.hide()},_addChart:function(e){var t=e.get("title");t==""&&(t="Untitled"),this.table.add(t);var n=this.app.types.get(e.get("type"));this.table.add(n.title),this.table.add("Last change: "+e.get("date")),this.table.prepend(e.get("id")),this.table.value(e.get("id"))},_removeChart:function(e){this.table.remove(e.id),this.table.size()==0?(this.hide(),this.app.chart.reset(),this.app.chart_view.$el.show()):this.table.value(this.app.charts.last().id)},_changeChart:function(e){e.get("type")&&(this._addChart(e),this.table.value(e.id))}})}),define("mvc/ui/ui-tabs",["utils/utils"],function(e){var t=Backbone.View.extend({visible:!1,list:{},$nav:null,$content:null,first_tab:null,optionsDefault:{title_new:"",operations:null,onnew:null},initialize:function(t){this.options=e.merge(t,this.optionsDefault);var n=$(this._template(this.options));this.$nav=n.find(".tab-navigation"),this.$content=n.find(".tab-content"),this.setElement(n),this.list={};var r=this;this.options.operations&&$.each(this.options.operations,function(e,t){t.$el.prop("id",e),r.$nav.append(t.$el)});if(this.options.onnew){var i=$(this._template_tab_new(this.options));this.$nav.append(i),i.tooltip({title:"Add a new tab",placement:"bottom"}),i.on("click",function(e){i.tooltip("hide"),r.options.onnew()})}},add:function(e){var t=e.id,n={$title:$(this._template_tab(e)),$content:$(this._template_tab_content(e)),removable:e.ondel?!0:!1};this.list[t]=n,this.options.onnew?this.$nav.find("#new-tab").before(n.$title):this.$nav.append(n.$title),n.$content.append(e.$el),this.$content.append(n.$content),_.size(this.list)==1&&(n.$title.addClass("active"),n.$content.addClass("active"),this.first_tab=t);if(e.ondel){var r=n.$title.find("#delete");r.tooltip({title:"Delete this tab",placement:"bottom"}),r.on("click",function(){return r.tooltip("destroy"),e.ondel(),!1})}e.onclick&&n.$title.on("click",function(){e.onclick()})},del:function(e){var t=this.list[e];t.$title.remove(),t.$content.remove(),delete t,this.first_tab==e&&(this.first_tab=null),this.first_tab!=null&&this.show(this.first_tab)},delRemovable:function(){for(var e in this.list){var t=this.list[e];t.removable&&this.del(e)}},show:function(e){this.$el.fadeIn("fast"),this.visible=!0,e&&this.list[e].$title.find("a").tab("show")},hide:function(){this.$el.fadeOut("fast"),this.visible=!1},hideOperation:function(e){this.$nav.find("#"+e).hide()},showOperation:function(e){this.$nav.find("#"+e).show()},setOperation:function(e,t){var n=this.$nav.find("#"+e);n.off("click"),n.on("click",t)},title:function(e,t){var n=this.list[e].$title.find("#text");return t&&n.html(t),n.html()},_template:function(e){return'<div class="tabbable tabs-left"><ul class="tab-navigation nav nav-tabs"/><div class="tab-content"/></div>'},_template_tab_new:function(e){return'<li id="new-tab"><a href="javascript:void(0);"><i style="font-size: 0.8em; margin-right: 5px;" class="fa fa-plus-circle"/>'+e.title_new+"</a>"+"</li>"},_template_tab:function(e){var t='<li id="title-'+e.id+'">'+'<a title="" href="#tab-'+e.id+'" data-toggle="tab" data-original-title="">'+'<span id="text">'+e.title+"</span>";return e.ondel&&(t+='<i id="delete" style="font-size: 0.8em; margin-left: 5px; cursor: pointer;" class="fa fa-minus-circle"/>'),t+="</a></li>",t},_template_tab_content:function(e){return'<div id="tab-'+e.id+'" class="tab-pane"/>'}});return{View:t}}),define("plugin/models/groups",["plugin/models/group"],function(e){return Backbone.Collection.extend({model:e})}),define("plugin/models/chart",["plugin/models/groups"],function(e){return Backbone.Model.extend({defaults:{id:null,title:"",type:"",date:null,state:"ok",state_info:""},initialize:function(t){this.groups=new e,this.settings=new Backbone.Model},reset:function(){this.clear({silent:!0}).set(this.defaults),this.groups.reset(),this.settings.clear(),this.trigger("reset",this)},copy:function(e){var t=this;t.clear({silent:!0}).set(this.defaults),t.set(e.attributes),t.settings=e.settings.clone(),t.groups.reset(),e.groups.each(function(e){t.groups.add(e.clone())}),t.trigger("change",t)},state:function(e,t){this.set("state_info",t),this.set("state",e)},ready:function(){return this.get("state")=="ok"||this.get("state")=="failed"}})}),define("plugin/views/group",["plugin/library/ui-table","plugin/library/ui","utils/utils"],function(e,t,n){return Backbone.View.extend({columns:[],initialize:function(r,i){this.app=r;var s=this;this.chart=this.app.chart,this.group=i.group,this.group_key=new t.Input({placeholder:"Data label",onchange:function(){s.group.set("key",s.group_key.value())}}),this.table=new e({content:"No data column."});var o=$("<div/>");o.append(n.wrap((new t.Label({title:"Provide a label:"})).$el)),o.append(n.wrap(this.group_key.$el)),o.append(n.wrap((new t.Label({title:"Select columns:"})).$el)),o.append(n.wrap(this.table.$el)),this.setElement(o);var s=this;this.chart.on("change:dataset_id",function(){s._refreshTable()}),this.chart.on("change:type",function(){s._refreshTable()}),this.group.on("change:key",function(){s._refreshGroupKey()}),this.group.on("change",function(){s._refreshGroup()}),this._refreshTable(),this._refreshGroupKey(),this._refreshGroup()},_refreshTable:function(){var e=this.chart.get("dataset_id"),n=this.chart.get("type");if(!e||!n)return;var r=this,i=this.app.types.get(n);this.table.removeAll();var s={};for(var o in i.columns){var u=this.group.get(o);u||this.group.set(o,0);var a=i.columns[o],f=new t.Select.View({id:"select_"+o,gid:o,onchange:function(e){r.group.set(this.gid,e)},value:u,wait:!0});this.table.add(a.title,"25%"),this.table.add(f.$el),this.table.append(o),s[o]=f}this.app.datasets.request({id:e},function(e){r.columns=[];var t=e.metadata_column_types;for(var n in t)(t[n]=="int"||t[n]=="float")&&r.columns.push({label:"Column: "+(parseInt(n)+1)+" ["+t[n]+"]",value:n});for(var n in s)s[n].update(r.columns),s[n].show()})},_refreshGroup:function(){this.group.set("date",n.time())},_refreshGroupKey:function(){var e=this.group.get("key");e===undefined&&(e=""),this.group_key.value(e)}})}),define("plugin/library/ui-table-form",["plugin/library/ui-table","plugin/library/ui","utils/utils"],function(e,t,n){var r=Backbone.View.extend({initialize:function(r){this.table_title=new t.Label({title:r.title}),this.table=new e({content:r.content});var i=$("<div/>");i.append(n.wrap(this.table_title.$el)),i.append(n.wrap(this.table.$el)),this.setElement(i)},title:function(e){this.table_title.title(e)},update:function(e,t){this.table.removeAll();for(var n in e)this._add(n,e[n],t)},_add:function(e,n,r){var i=null,s=n.type;switch(s){case"text":i=new t.Input({placeholder:n.placeholder,onchange:function(){r.set(e,i.value())}});break;case"select":i=new t.Select.View({data:n.data,onchange:function(){r.set(e,i.value())}});break;case"slider":i=new t.Input({placeholder:n.placeholder,onchange:function(){r.set(e,i.value())}});break;case"separator":i=$("<div/>");break;default:console.log("ui-table-form:_add","Unknown setting type ("+n.type+")");return}if(s!="separator"){r.get(e)||r.set(e,n.init),i.value(r.get(e));var o=$("<div/>");o.append(i.$el),o.append('<div class="toolParamHelp" style="font-size: 0.9em;">'+n.info+"</div>"),this.table.add('<span style="white-space: nowrap;">'+n.title+"</span>","25%"),this.table.add(o)}else this.table.add('<h6 style="white-space: nowrap;">'+n.title+":<h6/>"),this.table.add($("<div/>"));this.table.append(e)}});return{View:r}}),define("plugin/views/settings",["plugin/library/ui","plugin/library/ui-table-form","utils/utils"],function(e,t,n){return Backbone.View.extend({initialize:function(e,n){this.app=e;var r=this;this.chart=this.app.chart,this.form=new t.View({title:"Chart options:",content:"This chart type does not provide any options."}),this.setElement(this.form.$el);var r=this;this.chart.on("change",function(){r._refreshTable()})},_refreshTable:function(){var e=this.chart.get("type");if(!e)return;var t=this.app.types.get(e);this.form.title(t.title+":"),this.form.update(t.settings,this.chart.settings)}})}),define("plugin/views/chart",["mvc/ui/ui-tabs","plugin/library/ui-table","plugin/library/ui","utils/utils","plugin/models/chart","plugin/models/group","plugin/views/group","plugin/views/settings"],function(e,t,n,r,i,s,o,u){return Backbone.View.extend({optionsDefault:{header:!0,content:"No content available."},initialize:function(i,s){this.app=i,this.chart=this.app.chart,this.options=r.merge(s,this.optionsDefault);var o=this;this.table=new t({header:!1,onconfirm:function(e){o.chart.groups.length>0?o.app.modal.show({title:"Switching to another chart type?",body:"If you continue your settings and selections will be reseted.",buttons:{Cancel:function(){o.app.modal.hide()},Continue:function(){o.app.modal.hide(),o.table.value(e)}}}):o.table.value(e)},onchange:function(e){o.chart.groups.reset(),o.chart.settings.clear(),o.chart.set({type:e})},ondblclick:function(e){o.tabs.show("settings")},content:"No chart types available"});var a=0,f=i.types.attributes;for(var l in f){var c=f[l];this.table.add(++a+"."),this.table.add(c.title),this.table.append(l)}this.tabs=new e.View({title_new:"Add Data",onnew:function(){var e=o._addGroupModel();o.tabs.show(e.id)},operations:{save:new n.ButtonIcon({icon:"fa-save",tooltip:"Draw Chart",title:"Draw",onclick:function(){o.chart.groups.length==0&&o._addGroupModel(),o._saveChart(),o.hide(),o.app.charts_view.$el.show()}}),back:new n.ButtonIcon({icon:"fa-caret-left",tooltip:"Return to Viewer",title:"Return",onclick:function(){o.hide(),o.app.charts_view.$el.show()}})}}),this.title=new n.Input({placeholder:"Chart title",onchange:function(){o.app.config.set("title",o.title.value())}});var h=$("<div/>");h.append(r.wrap((new n.Label({title:"Provide a chart title:"})).$el)),h.append(r.wrap(this.title.$el)),h.append(r.wrap((new n.Label({title:"Select a chart type:"})).$el)),h.append(r.wrap(this.table.$el)),this.tabs.add({id:"main",title:"Start",$el:h}),this.settings=new u(this.app),this.tabs.add({id:"settings",title:"Configuration",$el:this.settings.$el}),this.setElement(this.tabs.$el),this.tabs.hideOperation("back");var o=this;this.chart.on("change:title",function(e){o.title.value(e.get("title")),o.app.config.set("title",e.get("title"))}),this.chart.on("change:type",function(e){o.table.value(e.get("type"))}),this.chart.on("reset",function(e){o._resetChart()}),this.app.charts.on("add",function(e){o.tabs.showOperation("back")}),this.app.charts.on("remove",function(e){o.app.charts.length==0&&o.tabs.hideOperation("back")}),this.app.charts.on("reset",function(e){o.tabs.hideOperation("back")}),this.app.chart.groups.on("add",function(e){o._addGroup(e)}),this.app.chart.groups.on("remove",function(e){o._removeGroup(e)}),this.app.chart.groups.on("reset",function(e){o._removeAllGroups()}),this.app.chart.groups.on("change:key",function(e){o._refreshGroupKey()}),this._resetChart()},hide:function(){$(".tooltip").hide(),this.$el.hide()},_refreshGroupKey:function(){var e=this,t=0;this.chart.groups.each(function(n){var r=n.get("key","");r==""&&(r="Chart data"),e.tabs.title(n.id,++t+": "+r)})},_addGroupModel:function(){var e=new s({id:r.uuid()});return this.chart.groups.add(e),e},_addGroup:function(e){var t=this,n=new o(this.app,{group:e}),r=t.chart.groups.length;this.tabs.add({id:e.id,$el:n.$el,ondel:function(){t.chart.groups.remove(e.id)}}),this._refreshGroupKey()},_removeGroup:function(e){this.tabs.del(e.id),this._refreshGroupKey()},_removeAllGroups:function(e){this.tabs.delRemovable()},_resetChart:function(){this.chart.set("id",r.uuid()),this.chart.set("type","bardiagram"),this.chart.set("dataset_id",this.app.options.dataset.id),this.chart.set("dataset_hid",this.app.options.dataset.hid),this.chart.set("history_id",this.app.options.dataset.history_id),this.chart.set("title","New Chart")},_saveChart:function(){this.chart.set({type:this.table.value(),title:this.title.value(),date:r.time()});var e=this.app.charts.get(this.chart.id);e?e.copy(this.chart):(e=this.chart.clone(),e.copy(this.chart),this.app.charts.add(e)),e.trigger("redraw",e)}})}),define("plugin/models/config",[],function(){return Backbone.Model.extend({defaults:{query_limit:1e3,query_timeout:500,title:"Create a new chart"}})}),define("plugin/models/charts",["plugin/models/chart"],function(e){return Backbone.Collection.extend({model:e})}),define("plugin/charts/_nvd3/config",[],function(){return{title:"",columns:{y:{title:"Values for y-axis"}},settings:{separator_label:{title:"X axis",type:"separator"},x_axis_label:{title:"Axis label",info:"Provide a label for the axis.",type:"text",init:"X-axis",placeholder:"Axis label"},x_axis_type:{title:"Axis value type",info:"Select the value type of the axis.",type:"select",init:"f",data:[{label:"-- Do not show values --",value:"hide"},{label:"Float",value:"f"},{label:"Exponent",value:"e"},{label:"Integer",value:"d"},{label:"Percentage",value:"p"},{label:"Rounded",value:"r"},{label:"SI-prefix",value:"s"}]},x_axis_tick:{title:"Axis tick format",info:"Select the tick format for the axis.",type:"select",init:".1",data:[{label:"0.00001",value:".5"},{label:"0.0001",value:".4"},{label:"0.001",value:".3"},{label:"0.01",value:".2"},{label:"0.1",value:".1"},{label:"1",value:"1"}]},separator_tick:{title:"Y axis",type:"separator"},y_axis_label:{title:"Axis label",info:"Provide a label for the axis.",type:"text",init:"Y-axis",placeholder:"Axis label"},y_axis_type:{title:"Axis value type",info:"Select the value type of the axis.",type:"select",init:"f",data:[{label:"-- Do not show values --",value:"hide"},{label:"Float",value:"f"},{label:"Exponent",value:"e"},{label:"Integer",value:"d"},{label:"Percentage",value:"p"},{label:"Rounded",value:"r"},{label:"SI-prefix",value:"s"}]},y_axis_tick:{title:"Axis tick format",info:"Select the tick format for the axis.",type:"select",init:".1",data:[{label:"0.00001",value:".5"},{label:"0.0001",value:".4"},{label:"0.001",value:".3"},{label:"0.01",value:".2"},{label:"0.1",value:".1"},{label:"1",value:"1"}]},separator_legend:{title:"Others",type:"separator"},show_legend:{title:"Show legend",info:"Would you like to add a legend?",type:"select",init:"true",data:[{label:"Yes",value:"true"},{label:"No",value:"false"}]}}}}),define("plugin/charts/bardiagram/config",["plugin/charts/_nvd3/config"],function(e){return $.extend(!0,{},e,{title:"Bar diagram"})}),define("plugin/charts/boxplot/config",[],function(){return $.extend(!0,{},{title:"Boxplot",columns:{e:{title:"Values for y-axis"},r:{title:"Values for y-axis"},s:{title:"Values for y-axis"}}})}),define("plugin/charts/histogram/config",["plugin/charts/_nvd3/config"],function(e){return $.extend(!0,{},e,{title:"Histogram",mode:"execute",columns:{y:{title:"Observations"}},settings:{x_axis_label:{init:"Breaks"},y_axis_label:{init:"Density"},y_axis_tick:{init:".3"},separator_custom:{title:"Advanced",type:"separator"},bin_size:{title:"Number of bins",info:"Provide the number of histogram bins. The parsed data will be evenly distributed into bins according to the minimum and maximum values of the dataset.",type:"slider",init:10,min:10,max:1e3}}})}),define("plugin/charts/horizontal/config",["plugin/charts/_nvd3/config"],function(e){return $.extend(!0,{},e,{title:"Bar diagram (horizontal)",settings:{x_axis_type:{init:"hide"}}})}),define("plugin/charts/line/config",["plugin/charts/_nvd3/config"],function(e){return $.extend(!0,{},e,{title:"Line chart"})}),define("plugin/charts/linewithfocus/config",["plugin/charts/_nvd3/config"],function(e){return $.extend(!0,{},e,{title:"Line with focus"})}),define("plugin/charts/piechart/config",["plugin/charts/_nvd3/config"],function(e){return $.extend(!0,{},e,{title:"Pie chart"})}),define("plugin/charts/scatterplot/config",["plugin/charts/_nvd3/config"],function(e){return $.extend(!0,{},e,{title:"Scatter plot",columns:{x:{title:"Values for x-axis"}}})}),define("plugin/charts/stackedarea/config",["plugin/charts/_nvd3/config"],function(e){return $.extend(!0,{},e,{title:"Stacked area"})}),define("plugin/charts/types",["plugin/charts/bardiagram/config","plugin/charts/boxplot/config","plugin/charts/histogram/config","plugin/charts/horizontal/config","plugin/charts/line/config","plugin/charts/linewithfocus/config","plugin/charts/piechart/config","plugin/charts/scatterplot/config","plugin/charts/stackedarea/config"],function(e,t,n,r,i,s,o,u,a){return Backbone.Model.extend({defaults:{bardiagram:e,boxplot:t,horizontal:r,histogram:n,line:i,linewithfocus:s,piechart:o,scatterplot:u,stackedarea:a}})}),define("plugin/app",["mvc/ui/ui-modal","mvc/ui/ui-portlet","plugin/library/ui","utils/utils","plugin/library/jobs","plugin/library/datasets","plugin/views/charts","plugin/views/chart","plugin/models/config","plugin/models/chart","plugin/models/charts","plugin/charts/types"],function(e,t,n,r,i,s,o,u,a,f,l,c){return Backbone.View.extend({initialize:function(n){this.options=n,Galaxy&&Galaxy.modal?this.modal=Galaxy.modal:this.modal=new e.View,this.config=new a,this.jobs=new i(this),this.types=new c,this.chart=new f,this.charts=new l,this.datasets=new s(this),this.charts_view=new o(this),this.chart_view=new u(this),this.options.config.widget?this.portlet=$("<div></div>"):this.portlet=new t.View({icon:"fa-bar-chart-o"}),this.portlet.append(this.charts_view.$el),this.portlet.append(this.chart_view.$el),this.options.config.widget?this.setElement(this.portlet):this.setElement(this.portlet.$el),this.charts_view.$el.hide();var r=this;this.config.on("change:title",function(){r._refreshTitle()}),this.render()},render:function(){this._refreshTitle()},_refreshTitle:function(){var e=this.config.get("title");e&&(e=" - "+e),this.portlet.title("Charts"+e)},execute:function(e){},onunload:function(){},log:function(e,t){console.log(e+" "+t)}})});
\ No newline at end of file
+(function(){var e=this,t=e._,n={},r=Array.prototype,i=Object.prototype,s=Function.prototype,o=r.push,u=r.slice,a=r.concat,f=i.toString,l=i.hasOwnProperty,c=r.forEach,h=r.map,p=r.reduce,d=r.reduceRight,v=r.filter,m=r.every,g=r.some,y=r.indexOf,b=r.lastIndexOf,w=Array.isArray,E=Object.keys,S=s.bind,x=function(e){if(e instanceof x)return e;if(!(this instanceof x))return new x(e);this._wrapped=e};typeof exports!="undefined"?(typeof module!="undefined"&&module.exports&&(exports=module.exports=x),exports._=x):e._=x,x.VERSION="1.4.4";var T=x.each=x.forEach=function(e,t,r){if(e==null)return;if(c&&e.forEach===c)e.forEach(t,r);else if(e.length===+e.length){for(var i=0,s=e.length;i<s;i++)if(t.call(r,e[i],i,e)===n)return}else for(var o in e)if(x.has(e,o)&&t.call(r,e[o],o,e)===n)return};x.map=x.collect=function(e,t,n){var r=[];return e==null?r:h&&e.map===h?e.map(t,n):(T(e,function(e,i,s){r.push(t.call(n,e,i,s))}),r)};var N="Reduce of empty array with no initial value";x.reduce=x.foldl=x.inject=function(e,t,n,r){var i=arguments.length>2;e==null&&(e=[]);if(p&&e.reduce===p)return r&&(t=x.bind(t,r)),i?e.reduce(t,n):e.reduce(t);T(e,function(e,s,o){i?n=t.call(r,n,e,s,o):(n=e,i=!0)});if(!i)throw new TypeError(N);return n},x.reduceRight=x.foldr=function(e,t,n,r){var i=arguments.length>2;e==null&&(e=[]);if(d&&e.reduceRight===d)return r&&(t=x.bind(t,r)),i?e.reduceRight(t,n):e.reduceRight(t);var s=e.length;if(s!==+s){var o=x.keys(e);s=o.length}T(e,function(u,a,f){a=o?o[--s]:--s,i?n=t.call(r,n,e[a],a,f):(n=e[a],i=!0)});if(!i)throw new TypeError(N);return n},x.find=x.detect=function(e,t,n){var r;return C(e,function(e,i,s){if(t.call(n,e,i,s))return r=e,!0}),r},x.filter=x.select=function(e,t,n){var r=[];return e==null?r:v&&e.filter===v?e.filter(t,n):(T(e,function(e,i,s){t.call(n,e,i,s)&&r.push(e)}),r)},x.reject=function(e,t,n){return x.filter(e,function(e,r,i){return!t.call(n,e,r,i)},n)},x.every=x.all=function(e,t,r){t||(t=x.identity);var i=!0;return e==null?i:m&&e.every===m?e.every(t,r):(T(e,function(e,s,o){if(!(i=i&&t.call(r,e,s,o)))return n}),!!i)};var C=x.some=x.any=function(e,t,r){t||(t=x.identity);var i=!1;return e==null?i:g&&e.some===g?e.some(t,r):(T(e,function(e,s,o){if(i||(i=t.call(r,e,s,o)))return n}),!!i)};x.contains=x.include=function(e,t){return e==null?!1:y&&e.indexOf===y?e.indexOf(t)!=-1:C(e,function(e){return e===t})},x.invoke=function(e,t){var n=u.call(arguments,2),r=x.isFunction(t);return x.map(e,function(e){return(r?t:e[t]).apply(e,n)})},x.pluck=function(e,t){return x.map(e,function(e){return e[t]})},x.where=function(e,t,n){return x.isEmpty(t)?n?void 0:[]:x[n?"find":"filter"](e,function(e){for(var n in t)if(t[n]!==e[n])return!1;return!0})},x.findWhere=function(e,t){return x.where(e,t,!0)},x.max=function(e,t,n){if(!t&&x.isArray(e)&&e[0]===+e[0]&&e.length<65535)return Math.max.apply(Math,e);if(!t&&x.isEmpty(e))return-Infinity;var r={computed:-Infinity,value:-Infinity};return T(e,function(e,i,s){var o=t?t.call(n,e,i,s):e;o>=r.computed&&(r={value:e,computed:o})}),r.value},x.min=function(e,t,n){if(!t&&x.isArray(e)&&e[0]===+e[0]&&e.length<65535)return Math.min.apply(Math,e);if(!t&&x.isEmpty(e))return Infinity;var r={computed:Infinity,value:Infinity};return T(e,function(e,i,s){var o=t?t.call(n,e,i,s):e;o<r.computed&&(r={value:e,computed:o})}),r.value},x.shuffle=function(e){var t,n=0,r=[];return T(e,function(e){t=x.random(n++),r[n-1]=r[t],r[t]=e}),r};var k=function(e){return x.isFunction(e)?e:function(t){return t[e]}};x.sortBy=function(e,t,n){var r=k(t);return x.pluck(x.map(e,function(e,t,i){return{value:e,index:t,criteria:r.call(n,e,t,i)}}).sort(function(e,t){var n=e.criteria,r=t.criteria;if(n!==r){if(n>r||n===void 0)return 1;if(n<r||r===void 0)return-1}return e.index<t.index?-1:1}),"value")};var L=function(e,t,n,r){var i={},s=k(t==null?x.identity:t);return T(e,function(t,o){var u=s.call(n,t,o,e);r(i,u,t)}),i};x.groupBy=function(e,t,n){return L(e,t,n,function(e,t,n){(x.has(e,t)?e[t]:e[t]=[]).push(n)})},x.countBy=function(e,t,n){return L(e,t,n,function(e,t){x.has(e,t)||(e[t]=0),e[t]++})},x.sortedIndex=function(e,t,n,r){n=n==null?x.identity:k(n);var i=n.call(r,t),s=0,o=e.length;while(s<o){var u=s+o>>>1;n.call(r,e[u])<i?s=u+1:o=u}return s},x.toArray=function(e){return e?x.isArray(e)?u.call(e):e.length===+e.length?x.map(e,x.identity):x.values(e):[]},x.size=function(e){return e==null?0:e.length===+e.length?e.length:x.keys(e).length},x.first=x.head=x.take=function(e,t,n){return e==null?void 0:t!=null&&!n?u.call(e,0,t):e[0]},x.initial=function(e,t,n){return u.call(e,0,e.length-(t==null||n?1:t))},x.last=function(e,t,n){return e==null?void 0:t!=null&&!n?u.call(e,Math.max(e.length-t,0)):e[e.length-1]},x.rest=x.tail=x.drop=function(e,t,n){return u.call(e,t==null||n?1:t)},x.compact=function(e){return x.filter(e,x.identity)};var A=function(e,t,n){return T(e,function(e){x.isArray(e)?t?o.apply(n,e):A(e,t,n):n.push(e)}),n};x.flatten=function(e,t){return A(e,t,[])},x.without=function(e){return x.difference(e,u.call(arguments,1))},x.uniq=x.unique=function(e,t,n,r){x.isFunction(t)&&(r=n,n=t,t=!1);var i=n?x.map(e,n,r):e,s=[],o=[];return T(i,function(n,r){if(t?!r||o[o.length-1]!==n:!x.contains(o,n))o.push(n),s.push(e[r])}),s},x.union=function(){return x.uniq(a.apply(r,arguments))},x.intersection=function(e){var t=u.call(arguments,1);return x.filter(x.uniq(e),function(e){return x.every(t,function(t){return x.indexOf(t,e)>=0})})},x.difference=function(e){var t=a.apply(r,u.call(arguments,1));return x.filter(e,function(e){return!x.contains(t,e)})},x.zip=function(){var e=u.call(arguments),t=x.max(x.pluck(e,"length")),n=new Array(t);for(var r=0;r<t;r++)n[r]=x.pluck(e,""+r);return n},x.unzip=function(e){var t=[];return x.each(e,function(e,n){x.each(e,function(e,r){t.length<=r&&(t[r]=[]),t[r][n]=e})}),t},x.object=function(e,t){if(e==null)return{};var n={};for(var r=0,i=e.length;r<i;r++)t?n[e[r]]=t[r]:n[e[r][0]]=e[r][1];return n},x.indexOf=function(e,t,n){if(e==null)return-1;var r=0,i=e.length;if(n){if(typeof n!="number")return r=x.sortedIndex(e,t),e[r]===t?r:-1;r=n<0?Math.max(0,i+n):n}if(y&&e.indexOf===y)return e.indexOf(t,n);for(;r<i;r++)if(e[r]===t)return r;return-1},x.lastIndexOf=function(e,t,n){if(e==null)return-1;var r=n!=null;if(b&&e.lastIndexOf===b)return r?e.lastIndexOf(t,n):e.lastIndexOf(t);var i=r?n:e.length;while(i--)if(e[i]===t)return i;return-1},x.range=function(e,t,n){arguments.length<=1&&(t=e||0,e=0),n=arguments[2]||1;var r=Math.max(Math.ceil((t-e)/n),0),i=0,s=new Array(r);while(i<r)s[i++]=e,e+=n;return s};var O=function(){};x.bind=function(e,t){var n,r;if(e.bind===S&&S)return S.apply(e,u.call(arguments,1));if(!x.isFunction(e))throw new TypeError;return n=u.call(arguments,2),r=function(){if(this instanceof r){O.prototype=e.prototype;var i=new O;O.prototype=null;var s=e.apply(i,n.concat(u.call(arguments)));return Object(s)===s?s:i}return e.apply(t,n.concat(u.call(arguments)))}},x.partial=function(e){var t=u.call(arguments,1);return function(){return e.apply(this,t.concat(u.call(arguments)))}},x.bindAll=function(e){var t=u.call(arguments,1);if(t.length===0)throw new Error("bindAll must be passed function names");return T(t,function(t){e[t]=x.bind(e[t],e)}),e},x.memoize=function(e,t){var n={};return t||(t=x.identity),function(){var r=t.apply(this,arguments);return x.has(n,r)?n[r]:n[r]=e.apply(this,arguments)}},x.delay=function(e,t){var n=u.call(arguments,2);return setTimeout(function(){return e.apply(null,n)},t)},x.defer=function(e){return x.delay.apply(x,[e,1].concat(u.call(arguments,1)))},x.throttle=function(e,t,n){var r,i,s,o,u=0,a=function(){u=new Date,s=null,o=e.apply(r,i)};return function(){var f=new Date;!u&&n===!1&&(u=f);var l=t-(f-u);return r=this,i=arguments,l<=0?(clearTimeout(s),s=null,u=f,o=e.apply(r,i)):s||(s=setTimeout(a,l)),o}},x.debounce=function(e,t,n){var r,i;return function(){var s=this,o=arguments,u=function(){r=null,n||(i=e.apply(s,o))},a=n&&!r;return clearTimeout(r),r=setTimeout(u,t),a&&(i=e.apply(s,o)),i}},x.once=function(e){var t=!1,n;return function(){return t?n:(t=!0,n=e.apply(this,arguments),e=null,n)}},x.wrap=function(e,t){return function(){var n=[e];return o.apply(n,arguments),t.apply(this,n)}},x.compose=function(){var e=arguments;return function(){var t=arguments;for(var n=e.length-1;n>=0;n--)t=[e[n].apply(this,t)];return t[0]}},x.after=function(e,t){return e<=0?t():function(){if(--e<1)return t.apply(this,arguments)}},x.keys=E||function(e){if(e!==Object(e))throw new TypeError("Invalid object");var t=[];for(var n in e)x.has(e,n)&&t.push(n);return t},x.values=function(e){var t=[];for(var n in e)x.has(e,n)&&t.push(e[n]);return t},x.pairs=function(e){var t=[];for(var n in e)x.has(e,n)&&t.push([n,e[n]]);return t},x.invert=function(e){var t={};for(var n in e)x.has(e,n)&&(t[e[n]]=n);return t},x.functions=x.methods=function(e){var t=[];for(var n in e)x.isFunction(e[n])&&t.push(n);return t.sort()},x.extend=function(e){return T(u.call(arguments,1),function(t){if(t)for(var n in t)e[n]=t[n]}),e},x.pick=function(e){var t={},n=a.apply(r,u.call(arguments,1));return T(n,function(n){n in e&&(t[n]=e[n])}),t},x.omit=function(e){var t={},n=a.apply(r,u.call(arguments,1));for(var i in e)x.contains(n,i)||(t[i]=e[i]);return t},x.defaults=function(e){return T(u.call(arguments,1),function(t){if(t)for(var n in t)e[n]===void 0&&(e[n]=t[n])}),e},x.clone=function(e){return x.isObject(e)?x.isArray(e)?e.slice():x.extend({},e):e},x.tap=function(e,t){return t(e),e};var M=function(e,t,n,r){if(e===t)return e!==0||1/e==1/t;if(e==null||t==null)return e===t;e instanceof x&&(e=e._wrapped),t instanceof x&&(t=t._wrapped);var i=f.call(e);if(i!=f.call(t))return!1;switch(i){case"[object String]":return e==String(t);case"[object Number]":return e!=+e?t!=+t:e==0?1/e==1/t:e==+t;case"[object Date]":case"[object Boolean]":return+e==+t;case"[object RegExp]":return e.source==t.source&&e.global==t.global&&e.multiline==t.multiline&&e.ignoreCase==t.ignoreCase}if(typeof e!="object"||typeof t!="object")return!1;var s=n.length;while(s--)if(n[s]==e)return r[s]==t;n.push(e),r.push(t);var o=0,u=!0;if(i=="[object Array]"){o=e.length,u=o==t.length;if(u)while(o--)if(!(u=M(e[o],t[o],n,r)))break}else{var a=e.constructor,l=t.constructor;if(a!==l&&!(x.isFunction(a)&&a instanceof a&&x.isFunction(l)&&l instanceof l))return!1;for(var c in e)if(x.has(e,c)){o++;if(!(u=x.has(t,c)&&M(e[c],t[c],n,r)))break}if(u){for(c in t)if(x.has(t,c)&&!(o--))break;u=!o}}return n.pop(),r.pop(),u};x.isEqual=function(e,t){return M(e,t,[],[])},x.isEmpty=function(e){if(e==null)return!0;if(x.isArray(e)||x.isString(e))return e.length===0;for(var t in e)if(x.has(e,t))return!1;return!0},x.isElement=function(e){return!!e&&e.nodeType===1},x.isArray=w||function(e){return f.call(e)=="[object Array]"},x.isObject=function(e){return e===Object(e)},T(["Arguments","Function","String","Number","Date","RegExp"],function(e){x["is"+e]=function(t){return f.call(t)=="[object "+e+"]"}}),x.isArguments(arguments)||(x.isArguments=function(e){return!!e&&!!x.has(e,"callee")}),typeof /./!="function"&&(x.isFunction=function(e){return typeof e=="function"}),x.isFinite=function(e){return isFinite(e)&&!isNaN(parseFloat(e))},x.isNaN=function(e){return x.isNumber(e)&&e!=+e},x.isBoolean=function(e){return e===!0||e===!1||f.call(e)=="[object Boolean]"},x.isNull=function(e){return e===null},x.isUndefined=function(e){return e===void 0},x.has=function(e,t){return l.call(e,t)},x.noConflict=function(){return e._=t,this},x.identity=function(e){return e},x.times=function(e,t,n){var r=Array(e);for(var i=0;i<e;i++)r[i]=t.call(n,i);return r},x.random=function(e,t){return t==null&&(t=e,e=0),e+Math.floor(Math.random()*(t-e+1))};var _={escape:{"&":"&","<":"<",">":">",'"':""","'":"'","/":"/"}};_.unescape=x.invert(_.escape);var D={escape:new RegExp("["+x.keys(_.escape).join("")+"]","g"),unescape:new RegExp("("+x.keys(_.unescape).join("|")+")","g")};x.each(["escape","unescape"],function(e){x[e]=function(t){return t==null?"":(""+t).replace(D[e],function(t){return _[e][t]})}}),x.result=function(e,t){if(e==null)return void 0;var n=e[t];return x.isFunction(n)?n.call(e):n},x.mixin=function(e){T(x.functions(e),function(t){var n=x[t]=e[t];x.prototype[t]=function(){var e=[this._wrapped];return o.apply(e,arguments),F.call(this,n.apply(x,e))}})};var P=0;x.uniqueId=function(e){var t=++P+"";return e?e+t:t},x.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var H=/(.)^/,B={"'":"'","\\":"\\","\r":"r","\n":"n"," ":"t","\u2028":"u2028","\u2029":"u2029"},j=/\\|'|\r|\n|\t|\u2028|\u2029/g;x.template=function(e,t,n){var r;n=x.defaults({},n,x.templateSettings);var i=new RegExp([(n.escape||H).source,(n.interpolate||H).source,(n.evaluate||H).source].join("|")+"|$","g"),s=0,o="__p+='";e.replace(i,function(t,n,r,i,u){return o+=e.slice(s,u).replace(j,function(e){return"\\"+B[e]}),n&&(o+="'+\n((__t=("+n+"))==null?'':_.escape(__t))+\n'"),r&&(o+="'+\n((__t=("+r+"))==null?'':__t)+\n'"),i&&(o+="';\n"+i+"\n__p+='"),s=u+t.length,t}),o+="';\n",n.variable||(o="with(obj||{}){\n"+o+"}\n"),o="var __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'');};\n"+o+"return __p;\n";try{r=new Function(n.variable||"obj","_",o)}catch(u){throw u.source=o,u}if(t)return r(t,x);var a=function(e){return r.call(this,e,x)};return a.source="function("+(n.variable||"obj")+"){\n"+o+"}",a},x.chain=function(e){return x(e).chain()};var F=function(e){return this._chain?x(e).chain():e};x.mixin(x),T(["pop","push","reverse","shift","sort","splice","unshift"],function(e){var t=r[e];x.prototype[e]=function(){var n=this._wrapped;return t.apply(n,arguments),(e=="shift"||e=="splice")&&n.length===0&&delete n[0],F.call(this,n)}}),T(["concat","join","slice"],function(e){var t=r[e];x.prototype[e]=function(){return F.call(this,t.apply(this._wrapped,arguments))}}),x.extend(x.prototype,{chain:function(){return this._chain=!0,this},value:function(){return this._wrapped}})}).call(this),define("libs/underscore",function(e){return function(){var t,n;return t||e._}}(this)),define("utils/utils",["libs/underscore"],function(e){function t(e,t,r){n("GET",e,{},t,r)}function n(e,t,n,r,i){if(e=="GET"||e=="DELETE")t.indexOf("?")==-1?t+="?":t+="&",t+=$.param(n);var s=new XMLHttpRequest;s.open(e,t,!0),s.setRequestHeader("Accept","application/json"),s.setRequestHeader("Cache-Control","no-cache"),s.setRequestHeader("X-Requested-With","XMLHttpRequest"),s.setRequestHeader("Content-Type","application/json"),s.onloadend=function(){var e=s.status;if(e==200){try{response=jQuery.parseJSON(s.responseText)}catch(t){response=s.responseText}r&&r(response)}else i&&i(e)},e=="GET"||e=="DELETE"?s.send():s.send(JSON.stringify(n))}function r(e,t){var n=$('<div class="'+e+'"></div>');n.appendTo(":eq(0)");var r=n.css(t);return n.remove(),r}function i(e){$('link[href^="'+e+'"]').length||$('<link href="'+galaxy_config.root+e+'" rel="stylesheet">').appendTo("head")}function s(t,n){return t?e.defaults(t,n):n}function o(e,t){var n="";if(e>=1e11)e/=1e11,n="TB";else if(e>=1e8)e/=1e8,n="GB";else if(e>=1e5)e/=1e5,n="MB";else if(e>=100)e/=100,n="KB";else{if(!(e>0))return"<strong>-</strong>";e*=10,n="b"}var r=Math.round(e)/10;return t?r+" "+n:"<strong>"+r+"</strong> "+n}function u(){return(new Date).getTime().toString(36)}function a(e){var t=$("<p></p>");return t.append(e),t}function f(){var e=new Date,t=(e.getHours()<10?"0":"")+e.getHours(),n=(e.getMinutes()<10?"0":"")+e.getMinutes(),r=e.getDate()+"/"+(e.getMonth()+1)+"/"+e.getFullYear()+", "+t+":"+n;return r}return{cssLoadFile:i,cssGetAttribute:r,get:t,merge:s,bytesToString:o,uuid:u,time:f,wrap:a,request:n}}),define("mvc/ui/ui-modal",["utils/utils"],function(e){var t=Backbone.View.extend({elMain:"body",optionsDefault:{title:"ui-modal",body:"",backdrop:!0,height:null,width:null,closing_events:!1},buttonList:{},initialize:function(e){e&&this._create(e)},show:function(e){this.initialize(e),this.options.height?(this.$body.css("height",this.options.height),this.$body.css("overflow","hidden")):this.$body.css("max-height",$(window).height()/2),this.options.width&&this.$dialog.css("width",this.options.width),this.visible?this.$el.show():this.$el.fadeIn("fast"),this.visible=!0},hide:function(){this.visible=!1,this.$el.fadeOut("fast")},enableButton:function(e){var t=this.buttonList[e];this.$buttons.find("#"+t).prop("disabled",!1)},disableButton:function(e){var t=this.buttonList[e];this.$buttons.find("#"+t).prop("disabled",!0)},showButton:function(e){var t=this.buttonList[e];this.$buttons.find("#"+t).show()},hideButton:function(e){var t=this.buttonList[e];this.$buttons.find("#"+t).hide()},getButton:function(e){var t=this.buttonList[e];return this.$buttons.find("#"+t)},scrollTop:function(){return this.$body.scrollTop()},_create:function(e){var t=this;this.options=_.defaults(e,this.optionsDefault),this.options.body=="progress"&&(this.options.body=$('<div class="progress progress-striped active"><div class="progress-bar progress-bar-info" style="width:100%"></div></div>')),this.$el&&(this.$el.remove(),$(document).off("keyup")),this.setElement(this._template(this.options.title)),this.$dialog=this.$el.find(".modal-dialog"),this.$body=this.$el.find(".modal-body"),this.$footer=this.$el.find(".modal-footer"),this.$buttons=this.$el.find(".buttons"),this.$backdrop=this.$el.find(".modal-backdrop"),this.$body.html(this.options.body),this.options.backdrop||this.$backdrop.removeClass("in");if(this.options.buttons){this.buttonList={};var n=0;$.each(this.options.buttons,function(e,r){var i="button-"+n++;t.$buttons.append($('<button id="'+i+'"></button>').text(e).click(r)).append(" "),t.buttonList[e]=i})}else this.$footer.hide();$(this.elMain).append($(this.el)),this.options.closing_events&&(this.options.buttons.Pause||$(document).on("keyup",function(e){e.keyCode==27&&t.hide()}),this.$el.find(".modal-backdrop").on("click",function(){t.hide()}))},_template:function(e){return'<div class="modal"><div class="modal-backdrop fade in" style="z-index: -1;"></div><div class="modal-dialog"><div class="modal-content"><div class="modal-header"><button type="button" class="close" style="display: none;">×</button><h4 class="title">'+e+"</h4>"+"</div>"+'<div class="modal-body" style="position: static;"></div>'+'<div class="modal-footer">'+'<div class="buttons" style="float: right;"></div>'+"</div>"+"</div"+"</div>"+"</div>"}});return{View:t}}),define("mvc/ui/ui-portlet",["utils/utils"],function(e){var t=Backbone.View.extend({visible:!1,optionsDefault:{title:"",icon:"",buttons:null,body:null,height:null,operations:null,placement:"bottom",overflow:"auto"},$title:null,$content:null,$buttons:null,$operations:null,initialize:function(t){this.options=e.merge(t,this.optionsDefault),this.setElement(this._template(this.options)),this.$content=this.$el.find("#content"),this.$title=this.$el.find("#title-text"),this.options.height&&(this.$el.find("#body").css("height",this.options.height),this.$el.find("#content").css("overflow",this.options.overflow)),this.$buttons=$(this.el).find("#buttons");if(this.options.buttons){var n=this;$.each(this.options.buttons,function(e,t){t.$el.prop("id",e),n.$buttons.append(t.$el)})}else this.$buttons.remove();this.$operations=$(this.el).find("#operations");if(this.options.operations){var n=this;$.each(this.options.operations,function(e,t){t.$el.prop("id",e),n.$operations.append(t.$el)})}this.options.body&&this.append(this.options.body)},append:function(t){this.$content.append(e.wrap(t))},content:function(){return this.$content},show:function(){this.$el.fadeIn("fast"),this.visible=!0},hide:function(){this.$el.fadeOut("fast"),this.visible=!1},enableButton:function(e){this.$buttons.find("#"+e).prop("disabled",!1)},disableButton:function(e){this.$buttons.find("#"+e).prop("disabled",!0)},hideOperation:function(e){this.$operations.find("#"+e).hide()},showOperation:function(e){this.$operations.find("#"+e).show()},setOperation:function(e,t){var n=this.$operations.find("#"+e);n.off("click"),n.on("click",t)},title:function(e){var t=this.$title;return e&&t.html(e),t.html()},_template:function(e){var t='<div class="toolForm">';if(e.title||e.icon)t+='<div id="title" class="toolFormTitle" style="overflow: hidden;"><div id="operations" style="float: right;"></div><div style="overflow: hidden;">',e.icon&&(t+='<i style="padding-top: 3px; float: left; font-size: 1.2em" class="icon fa '+e.icon+'"> </i>'),t+='<div id="title-text" style="padding-top: 2px; float: left;">'+e.title+"</div>",t+="</div></div>";return t+='<div id="body" class="toolFormBody">',e.placement=="top"&&(t+='<div id="buttons" class="buttons" style="height: 50px; padding: 10px;"></div>'),t+='<div id="content" class="content" style="height: inherit; padding: 10px;"></div>',e.placement=="bottom"&&(t+='<div id="buttons" class="buttons" style="height: 50px; padding: 10px;"></div>'),t+="</div></div>",t}});return{View:t}}),define("plugin/library/ui-select",["utils/utils"],function(e){var t=Backbone.View.extend({optionsDefault:{id:"",cls:"",empty:"No data available",visible:!0,wait:!1},selected:null,initialize:function(t){this.options=e.merge(t,this.optionsDefault),this.setElement(this._template(this.options)),this.$select=this.$el.find("#select"),this.$icon=this.$el.find("#icon"),this.selected=this.options.value;var n=this;this.options.onchange&&this.$select.on("change",function(){n.value(n.$select.val())}),this._refresh(),this.options.visible||this.hide(),this.options.wait?this.wait():this.show()},value:function(e){var t=this.selected;e!==undefined&&(this.selected=e,this.$select.val(e));var n=this.selected;return n&&n!=t&&this.options.onchange&&this.options.onchange(n),n},text:function(){return this.$select.find("option:selected").text()},show:function(){this.$icon.removeClass(),this.$icon.addClass("fa fa-caret-down"),this.$select.show(),this.$el.show()},hide:function(){this.$el.hide()},wait:function(){this.$icon.removeClass(),this.$icon.addClass("fa fa-spinner fa-spin"),this.$select.hide()},disabled:function(){return this.$select.is(":disabled")},enable:function(){this.$select.prop("disabled",!1)},disable:function(){this.$select.prop("disabled",!0)},add:function(e){this.$select.append(this._templateOption(e)),this._refresh()},del:function(e){this.$select.find("option[value="+e+"]").remove(),this.$select.trigger("change"),this._refresh()},update:function(e){this.$select.find("option").remove();for(var t in e)this.$select.append(this._templateOption(e[t]));!this.selected&&e.length>0&&this.value(e[0].value),this._refresh()},_refresh:function(){this.$select.find("option[value=null]").remove();var e=this.$select.find("option").length;e==0?(this.$select.append(this._templateOption({value:"null",label:this.options.empty})),this.disable()):(this.enable(),this.selected&&this.$select.val(this.selected))},_exists:function(e){return 0!=this.$select.find("option[value="+e+"]").length},_templateOption:function(e){return'<option value="'+e.value+'">'+e.label+"</option>"},_template:function(e){var t='<div id="'+e.id+'" class="styled-select">'+'<div class="button">'+'<i id="icon"/>'+"</div>"+'<select id="select" class="select '+e.cls+" "+e.id+'">';for(key in e.data){var n=e.data[key],r="";if(n.value==e.value||n.value=="")r="selected";t+='<option value="'+n.value+'" '+r+">"+n.label+"</option>"}return t+="</select></div>",t}});return{View:t}}),define("plugin/library/ui",["utils/utils","plugin/library/ui-select"],function(e,t){var n=Backbone.View.extend({optionsDefault:{title:""},initialize:function(t){this.options=e.merge(t,this.optionsDefault),this.setElement(this._template(this.options))},title:function(e){this.$el.find("b").html(e)},_template:function(e){return"<label><b>"+e.title+"</b></label>"},value:function(){return options.title}}),r=Backbone.View.extend({optionsDefault:{id:null,title:"","float":"right",cls:"btn-default",icon:""},initialize:function(t){this.options=e.merge(t,this.optionsDefault),this.setElement(this._template(this.options)),$(this.el).on("click",t.onclick),$(this.el).tooltip({title:t.tooltip,placement:"bottom"})},_template:function(e){var t='<button id="'+e.id+'" type="submit" style="margin-right: 5px; float: '+e.float+';" type="button" class="btn '+e.cls+'">';return e.icon&&(t+='<i class="icon fa '+e.icon+'"></i> '),t+=e.title+"</button>",t}}),i=Backbone.View.extend({optionsDefault:{"float":"right",icon:"",tooltip:"",placement:"bottom"},initialize:function(t){this.options=e.merge(t,this.optionsDefault),this.setElement(this._template(this.options)),$(this.el).tooltip({title:t.tooltip,placement:"bottom"})},_template:function(e){return'<span class="fa '+e.icon+'" style="font-size: 1.2em;"/>'}}),s=Backbone.View.extend({optionsDefault:{title:"",id:null,"float":"right",cls:"icon-btn",icon:"",tooltip:""},initialize:function(t){this.options=e.merge(t,this.optionsDefault),this.setElement(this._template(this.options)),$(this.el).on("click",t.onclick),$(this.el).tooltip({title:t.tooltip,placement:"bottom"})},_template:function(e){var t="";e.title&&(t="width: auto;");var n='<div id="'+e.id+'" style="margin-right: 5px; float: '+e.float+"; "+t+'" class="'+e.cls+'">';return e.title?n+='<div style="margin-right: 5px; margin-left: 5px;"><i class="icon fa '+e.icon+'"/> '+'<span style="position: relative; font-size: 0.8em; font-weight: normal; top: -1px;">'+e.title+"</span>"+"</div>":n+='<i class="icon fa '+e.icon+'"/>',n+="</div>",n}}),o=Backbone.View.extend({optionsDefault:{title:""},initialize:function(t){this.options=e.merge(t,this.optionsDefault),this.setElement(this._template(this.options)),$(this.el).on("click",t.onclick)},_template:function(e){return'<div><a href="javascript:void(0)">'+e.title+"</a></div>"}}),u=Backbone.View.extend({optionsDefault:{message:"",status:"info",persistent:!1},initialize:function(t){this.options=e.merge(t,this.optionsDefault),this.setElement("<div></div>")},update:function(t){this.options=e.merge(t,this.optionsDefault);if(t.message!=""){this.$el.html(this._template(this.options)),this.$el.fadeIn();if(!t.persistent){var n=this;window.setTimeout(function(){n.$el.is(":visible")?n.$el.fadeOut():n.$el.hide()},3e3)}}else this.$el.fadeOut()},_template:function(e){return'<div class="alert alert-'+e.status+'" style="padding: 2px 2px 2px 10px;">'+e.message+"</div>"}}),a=Backbone.View.extend({optionsDefault:{onclick:null,searchword:""},initialize:function(t){this.options=e.merge(t,this.optionsDefault),this.setElement(this._template(this.options));var n=this;this.options.onclick&&this.$el.on("submit",function(e){var t=n.$el.find("#search");n.options.onclick(t.val())})},_template:function(e){return'<div class="search"><form onsubmit="return false;"><input id="search" class="form-control input-sm" type="text" name="search" placeholder="Search..." value="'+e.searchword+'">'+'<button type="submit" class="btn search-btn">'+'<i class="fa fa-search"></i>'+"</button>"+"</form>"+"</div>"}}),f=Backbone.View.extend({optionsDefault:{title:"Unlabeled",body:null},initialize:function(t){this.options=e.merge(t,this.optionsDefault),this.setElement(this._template(this.options)),this.options.body&&this.$el.find(".body").append(this.options.body)},_template:function(e){return'<div id="title" class="title">'+e.title+":"+"</div>"}}),l=Backbone.View.extend({optionsDefault:{id:"",title:"",target:"",href:"",onunload:null,onclick:null,visible:!0,icon:null,tag:""},$menu:null,initialize:function(t){this.options=e.merge(t,this.optionsDefault),this.setElement($(this._template(this.options)));var n=$(this.el).find(".root"),r=this;n.on("click",function(e){e.preventDefault(),r.options.onclick&&r.options.onclick()}),this.options.visible||this.hide()},show:function(){$(this.el).show()},hide:function(){$(this.el).hide()},addMenu:function(t){var n={title:"",target:"",href:"",onclick:null,divider:!1,icon:null};n=e.merge(t,n),this.$menu||($(this.el).append(this._templateMenu()),this.$menu=$(this.el).find(".menu"));var r=$(this._templateMenuItem(n));r.on("click",function(e){e.preventDefault(),n.onclick&&n.onclick()}),this.$menu.append(r),n.divider&&this.$menu.append($(this._templateDivider()))},_templateMenuItem:function(e){var t='<li><a href="'+e.href+'" target="'+e.target+'">';return e.icon&&(t+='<i class="fa '+e.icon+'"></i>'),t+=" "+e.title+"</a>"+"</li>",t},_templateMenu:function(){return'<ul class="menu dropdown-menu pull-right" role="menu"></ul>'},_templateDivider:function(){return'<li class="divider"></li>'},_template:function(e){var t='<div id="'+e.id+'" class="button-menu btn-group">'+'<button type="button" class="root btn btn-default dropdown-toggle" data-toggle="dropdown">';return e.icon&&(t+='<i class="fa '+e.icon+'"></i>'),"</button></div>",t}}),c=Backbone.View.extend({optionsDefault:{value:"",type:"text",placeholder:"",disabled:!1,visible:!0},initialize:function(t){this.options=e.merge(t,this.optionsDefault),this.setElement(this._template(this.options)),this.options.disabled&&this.$el.prop("disabled",!0),this.options.visible||this.$el.hide();var n=this;this.options.onchange&&this.$el.on("input",function(){n.options.onchange()})},value:function(e){return e!==undefined&&this.$el.val(e),this.$el.val()},_template:function(e){return'<input id="'+e.id+'" type="'+e.type+'" value="'+e.value+'" placeholder="'+e.placeholder+'" class="form-control">'}});return{Label:n,Button:r,Icon:i,ButtonIcon:s,Input:c,Anchor:o,Message:u,Searchbox:a,Title:f,Select:t,ButtonMenu:l}}),define("plugin/library/jobs",["utils/utils"],function(e){return Backbone.Model.extend({initialize:function(t,n){this.app=t,this.options=e.merge(n,this.optionsDefault)},submit:function(t,n,r){var i=this,s=t.id,o=t.get("type"),u=this.app.types.get(o);data={tool_id:"rkit",history_id:t.get("history_id"),inputs:{input:t.get("dataset_hid"),module:o,options:n}};var a=t.get("dataset_id_job");a&&e.request("PUT",config.root+"api/histories/"+t.get("history_id")+"/contents/"+a,{deleted:!0}),t.state("submit","Sending job request..."),e.request("POST",config.root+"api/tools",data,function(e){if(!e.outputs||e.outputs.length==0)t.state("failed","Job submission failed. No response.");else{Galaxy&&Galaxy.currHistoryPanel&&Galaxy.currHistoryPanel.refreshHdas();var n=e.outputs[0];t.state("queued","Job has been queued..."),t.set("dataset_id_job",n.id),i._loop(n.id,function(e){switch(e.state){case"ok":return t.state("success","Job completed successfully..."),r(e),!0;case"error":return t.state("failed","Job has failed. Please check the history for details."),!0;case"running":return t.state("running","Job is running..."),!1}})}},function(e){t.state("failed","Job submission failed. Please make sure that 'R-kit' is installed.")})},_loop:function(t,n){var r=this;e.request("GET",config.root+"api/jobs/"+t,{},function(e){n(e)||setTimeout(function(){r._loop(t,n)},r.app.config.get("query_timeout"))})}})}),define("plugin/library/datasets",["utils/utils"],function(e){return Backbone.Collection.extend({list:{},initialize:function(t,n){this.app=t,this.options=e.merge(n,this.optionsDefault)},request:function(t,n,r){var i=this;if(t.groups)this._fetch(t,n);else{var s=this.list[t.id];if(s){n(s);return}e.request("GET",config.root+"api/datasets/"+t.id,{},function(e){switch(e.state){case"error":r&&r(e);break;default:i.list[t.id]=e,n(e)}})}},_fetch:function(t,n){var r=t.start?t.start:0,i=Math.abs(t.end-t.start),s=this.app.config.get("query_limit");if(!i||i>s)i=s;var o="",u={},a=0;for(var f in t.groups){var l=t.groups[f];for(var c in l.columns){var h=l.columns[c];o+=h+",",u[h]=a,a++}}if(a==0){n({});return}o=o.substring(0,o.length-1);var p=t.groups.slice(0);for(var f in p)p[f].values=[];var d=this;e.request("GET",config.root+"api/datasets/"+t.id,{data_type:"raw_data",provider:"dataset-column",limit:i,offset:r,indeces:o},function(e){for(var i in e.data){var s=e.data[i];for(var o in t.groups){var a=t.groups[o],f={x:parseInt(i)+r};for(var l in a.columns){var c=a.columns[l],h=u[c],d=s[h];if(isNaN(d)||!d)d=0;f[l]=d}p[o].values.push(f)}}n(p)})}})}),define("plugin/library/ui-table",["utils/utils"],function(e){return Backbone.View.extend({row:null,row_count:0,optionsDefault:{content:"No content available.",onchange:null,ondblclick:null,onconfirm:null},events:{click:"_onclick",dblclick:"_ondblclick"},first:!0,initialize:function(t){this.options=e.merge(t,this.optionsDefault);var n=$(this._template(t));this.$thead=n.find("thead"),this.$tbody=n.find("tbody"),this.$tmessage=n.find("tmessage"),this.setElement(n),this.row=$("<tr></tr>")},addHeader:function(e){var t=$("<th></th>");t.append(e),this.row.append(t)},appendHeader:function(){this.$thead.append(this.row),this.row=$("<tr></tr>")},add:function(e,t){var n=$("<td></td>");t&&n.css("width",t),n.append(e),this.row.append(n)},append:function(e){this._commit(e)},prepend:function(e){this._commit(e,!0)},remove:function(e){var t=this.$tbody.find("#"+e);t.length>0&&(t.remove(),this.row_count--,this._refresh())},removeAll:function(){this.$tbody.html(""),this.row_count=0,this._refresh()},value:function(e){this.before=this.$tbody.find(".current").attr("id"),e!==undefined&&(this.$tbody.find("tr").removeClass("current"),e&&this.$tbody.find("#"+e).addClass("current"));var t=this.$tbody.find(".current").attr("id");return t===undefined?null:(t!=this.before&&this.options.onchange&&this.options.onchange(e),t)},size:function(){return this.$tbody.find("tr").length},_commit:function(e,t){this.remove(e),this.row.attr("id",e),t?this.$tbody.prepend(this.row):this.$tbody.append(this.row),this.row=$("<tr></tr>"),this.row_count++,this._refresh()},_onclick:function(e){var t=this.value(),n=$(e.target).closest("tr").attr("id");n&&t!=n&&(this.options.onconfirm?this.options.onconfirm(n):this.value(n))},_ondblclick:function(e){var t=this.value();t&&this.options.ondblclick&&this.options.ondblclick(t)},_refresh:function(){this.row_count==0?this.$tmessage.show():this.$tmessage.hide()},_template:function(e){return'<div><table class="grid"><thead></thead><tbody style="cursor: pointer;"></tbody></table><tmessage>'+e.content+"</tmessage>"+"<div>"}})}),define("plugin/models/group",[],function(){return Backbone.Model.extend({defaults:{key:"Data label",date:""},reset:function(){this.clear({silent:!0}).set(this.defaults),this.trigger("reset",this)}})}),define("plugin/views/viewport",["mvc/ui/ui-portlet","plugin/library/ui","utils/utils"],function(e,t,n){return Backbone.View.extend({list:{},optionsDefault:{height:300},initialize:function(r,i){this.app=r,this.options=n.merge(i,this.optionsDefault),this.portlet=new e.View({title:"title",height:this.options.height,overflow:"hidden",operations:{edit:new t.ButtonIcon({icon:"fa-gear",tooltip:"Customize Chart",title:"Customize"})}}),this.setElement(this.portlet.$el);var s=this;this.app.charts.on("remove",function(e){s._removeChart(e.id)}),this.app.charts.on("redraw",function(e){e.ready()?s._refreshChart(e):e.on("change:state",function(){e.ready()&&s._refreshChart(e)})})},showChart:function(e){this.show(),this.hideCharts();var t=this.list[e];if(t){var n=self.app.charts.get(e);this.portlet.title(n.get("title")),this.portlet.setOperation("edit",function(){self.app.chart.copy(n),self.app.charts_view.hide(),self.app.chart_view.$el.show()}),t.$el.show(),$(window).trigger("resize")}},hideCharts:function(){this.$el.find(".item").hide()},show:function(){$(".tooltip").hide(),this.$el.show()},hide:function(){$(".tooltip").hide(),this.$el.hide()},_refreshChart:function(e){var t=this;if(!e.ready()){t.app.log("viewport:_refreshChart()","Invalid attempt to refresh chart before completion.");return}var n=e.id;this._removeChart(n);var r="#"+n,i=$(this._template({id:r}));this.portlet.append(i);var s=d3.select(r+" svg");this.list[n]={svg:s,$el:i},this.showChart(n),e.off("change:state"),e.on("change:state",function(){var t=i.find("#info"),n=t.find("#icon");n.removeClass(),t.show(),t.find("#text").html(e.get("state_info"));var r=e.get("state");switch(r){case"ok":t.hide();break;case"failed":n.addClass("fa fa-warning");break;default:n.addClass("fa fa-spinner fa-spin")}}),e.state("wait","Please wait...");var o=e.get("type"),u=this.app.types.get(o),t=this;require(["plugin/charts/"+o+"/"+o],function(n){var r=new n(t.app,{svg:s}),i=u.mode;i=="execute"?t.app.jobs.submit(e,t._defaultRequestString(e),function(){r.draw(e,t._defaultRequestDictionary(e))}):r.draw(e,t._defaultRequestDictionary(e))})},_removeChart:function(e){var t=this.list[e];t&&(t.svg.remove(),t.$el.remove())},_template:function(e){return'<div id="'+e.id.substr(1)+'" class="item">'+'<span id="info">'+'<span id="icon" style="font-size: 1.2em; display: inline-block;"/>'+'<span id="text" style="position: relative; margin-left: 5px; top: -1px; font-size: 1.0em;"/>'+"</span>"+'<svg style="height: auto;"/>'+"</div>"},_defaultRequestString:function(e){var t=this.app.types.get(e.get("type")),n="",r=0;return e.groups.each(function(e){for(var i in t.columns)n+=i+"_"+ ++r+":"+(parseInt(e.get(i))+1)+", "}),n.substring(0,n.length-2)},_defaultRequestDictionary:function(e){var t=this.app.types.get(e.get("type")),n={id:e.get("dataset_id"),groups:[]},r=0;return e.groups.each(function(e){var i={};for(var s in t.columns)i[s]=e.get(s);n.groups.push({key:++r+":"+e.get("key"),columns:i})}),n}})}),define("plugin/views/charts",["mvc/ui/ui-portlet","plugin/library/ui-table","plugin/library/ui","utils/utils","plugin/models/group","plugin/views/viewport"],function(e,t,n,r,i,s){return Backbone.View.extend({initialize:function(i,o){this.app=i,this.viewport_view=new s(i),this.table=new t({content:"Add charts to this table.",ondblclick:function(e){var t=u.app.charts.get(e);u.app.chart.copy(t),u.hide(),u.app.chart_view.$el.show()},onchange:function(e){var t=u.app.charts.get(e);u.app.config.set("title",t.get("title")),u.viewport_view.showChart(e)}});var u=this;this.portlet=new e.View({icon:"fa-list",title:"List of created charts:",height:100,operations:{"new":new n.ButtonIcon({icon:"fa-magic",tooltip:"Create a new Chart",title:"New",onclick:function(){u.hide(),u.app.chart.reset(),u.app.chart_view.$el.show()}}),"delete":new n.ButtonIcon({icon:"fa-trash-o",tooltip:"Delete this Chart",title:"Delete",onclick:function(){var e=u.table.value();if(!e)return;var t=u.app.charts.get(e);u.app.modal.show({title:"Are you sure?",body:'The selected chart "'+t.get("title")+'" will be irreversibly deleted.',buttons:{Cancel:function(){u.app.modal.hide()},Delete:function(){u.app.modal.hide(),u.app.charts.remove(e)}}})}})}}),this.portlet.append(this.table.$el),this.app.options.config.widget||this.$el.append(this.portlet.$el),this.$el.append(r.wrap("")),this.$el.append(this.viewport_view.$el);var u=this;this.app.charts.on("add",function(e){u._addChart(e)}),this.app.charts.on("remove",function(e){u._removeChart(e)}),this.app.charts.on("change",function(e){u._changeChart(e)})},hide:function(){$(".tooltip").hide(),this.$el.hide()},_addChart:function(e){var t=e.get("title");t==""&&(t="Untitled"),this.table.add(t);var n=this.app.types.get(e.get("type"));this.table.add(n.title),this.table.add("Last change: "+e.get("date")),this.table.prepend(e.get("id")),this.table.value(e.get("id"))},_removeChart:function(e){this.table.remove(e.id),this.table.size()==0?(this.hide(),this.app.chart.reset(),this.app.chart_view.$el.show()):this.table.value(this.app.charts.last().id)},_changeChart:function(e){e.get("type")&&(this._addChart(e),this.table.value(e.id))}})}),define("mvc/ui/ui-tabs",["utils/utils"],function(e){var t=Backbone.View.extend({visible:!1,list:{},$nav:null,$content:null,first_tab:null,optionsDefault:{title_new:"",operations:null,onnew:null},initialize:function(t){this.options=e.merge(t,this.optionsDefault);var n=$(this._template(this.options));this.$nav=n.find(".tab-navigation"),this.$content=n.find(".tab-content"),this.setElement(n),this.list={};var r=this;this.options.operations&&$.each(this.options.operations,function(e,t){t.$el.prop("id",e),r.$nav.append(t.$el)});if(this.options.onnew){var i=$(this._template_tab_new(this.options));this.$nav.append(i),i.tooltip({title:"Add a new tab",placement:"bottom"}),i.on("click",function(e){i.tooltip("hide"),r.options.onnew()})}},add:function(e){var t=e.id,n={$title:$(this._template_tab(e)),$content:$(this._template_tab_content(e)),removable:e.ondel?!0:!1};this.list[t]=n,this.options.onnew?this.$nav.find("#new-tab").before(n.$title):this.$nav.append(n.$title),n.$content.append(e.$el),this.$content.append(n.$content),_.size(this.list)==1&&(n.$title.addClass("active"),n.$content.addClass("active"),this.first_tab=t);if(e.ondel){var r=n.$title.find("#delete");r.tooltip({title:"Delete this tab",placement:"bottom"}),r.on("click",function(){return r.tooltip("destroy"),e.ondel(),!1})}e.onclick&&n.$title.on("click",function(){e.onclick()})},del:function(e){var t=this.list[e];t.$title.remove(),t.$content.remove(),delete t,this.first_tab==e&&(this.first_tab=null),this.first_tab!=null&&this.show(this.first_tab)},delRemovable:function(){for(var e in this.list){var t=this.list[e];t.removable&&this.del(e)}},show:function(e){this.$el.fadeIn("fast"),this.visible=!0,e&&this.list[e].$title.find("a").tab("show")},hide:function(){this.$el.fadeOut("fast"),this.visible=!1},hideOperation:function(e){this.$nav.find("#"+e).hide()},showOperation:function(e){this.$nav.find("#"+e).show()},setOperation:function(e,t){var n=this.$nav.find("#"+e);n.off("click"),n.on("click",t)},title:function(e,t){var n=this.list[e].$title.find("#text");return t&&n.html(t),n.html()},_template:function(e){return'<div class="tabbable tabs-left"><ul class="tab-navigation nav nav-tabs"/><div class="tab-content"/></div>'},_template_tab_new:function(e){return'<li id="new-tab"><a href="javascript:void(0);"><i style="font-size: 0.8em; margin-right: 5px;" class="fa fa-plus-circle"/>'+e.title_new+"</a>"+"</li>"},_template_tab:function(e){var t='<li id="title-'+e.id+'">'+'<a title="" href="#tab-'+e.id+'" data-toggle="tab" data-original-title="">'+'<span id="text">'+e.title+"</span>";return e.ondel&&(t+='<i id="delete" style="font-size: 0.8em; margin-left: 5px; cursor: pointer;" class="fa fa-minus-circle"/>'),t+="</a></li>",t},_template_tab_content:function(e){return'<div id="tab-'+e.id+'" class="tab-pane"/>'}});return{View:t}}),define("plugin/models/groups",["plugin/models/group"],function(e){return Backbone.Collection.extend({model:e})}),define("plugin/models/chart",["plugin/models/groups"],function(e){return Backbone.Model.extend({defaults:{id:null,title:"",type:"",date:null,state:"ok",state_info:""},initialize:function(t){this.groups=new e,this.settings=new Backbone.Model},reset:function(){this.clear({silent:!0}).set(this.defaults),this.groups.reset(),this.settings.clear(),this.trigger("reset",this)},copy:function(e){var t=this;t.clear({silent:!0}).set(this.defaults),t.set(e.attributes),t.settings=e.settings.clone(),t.groups.reset(),e.groups.each(function(e){t.groups.add(e.clone())}),t.trigger("change",t)},state:function(e,t){this.set("state_info",t),this.set("state",e)},ready:function(){return this.get("state")=="ok"||this.get("state")=="failed"}})}),define("plugin/views/group",["plugin/library/ui-table","plugin/library/ui","utils/utils"],function(e,t,n){return Backbone.View.extend({columns:[],initialize:function(r,i){this.app=r;var s=this;this.chart=this.app.chart,this.group=i.group,this.group_key=new t.Input({placeholder:"Data label",onchange:function(){s.group.set("key",s.group_key.value())}}),this.table=new e({content:"No data column."});var o=$("<div/>");o.append(n.wrap((new t.Label({title:"Provide a label:"})).$el)),o.append(n.wrap(this.group_key.$el)),o.append(n.wrap((new t.Label({title:"Select columns:"})).$el)),o.append(n.wrap(this.table.$el)),this.setElement(o);var s=this;this.chart.on("change:dataset_id",function(){s._refreshTable()}),this.chart.on("change:type",function(){s._refreshTable()}),this.group.on("change:key",function(){s._refreshGroupKey()}),this.group.on("change",function(){s._refreshGroup()}),this._refreshTable(),this._refreshGroupKey(),this._refreshGroup()},_refreshTable:function(){var e=this.chart.get("dataset_id"),n=this.chart.get("type");if(!e||!n)return;var r=this,i=this.app.types.get(n);this.table.removeAll();var s={};for(var o in i.columns){var u=this.group.get(o);u||this.group.set(o,0);var a=i.columns[o],f=new t.Select.View({id:"select_"+o,gid:o,onchange:function(e){r.group.set(this.gid,e)},value:u,wait:!0});this.table.add(a.title,"25%"),this.table.add(f.$el),this.table.append(o),s[o]=f}this.app.datasets.request({id:e},function(e){r.columns=[];var t=e.metadata_column_types;for(var n in t)(t[n]=="int"||t[n]=="float")&&r.columns.push({label:"Column: "+(parseInt(n)+1)+" ["+t[n]+"]",value:n});for(var n in s)s[n].update(r.columns),s[n].show()})},_refreshGroup:function(){this.group.set("date",n.time())},_refreshGroupKey:function(){var e=this.group.get("key");e===undefined&&(e=""),this.group_key.value(e)}})}),define("plugin/library/ui-table-form",["plugin/library/ui-table","plugin/library/ui","utils/utils"],function(e,t,n){var r=Backbone.View.extend({initialize:function(r){this.table_title=new t.Label({title:r.title}),this.table=new e({content:r.content});var i=$("<div/>");i.append(n.wrap(this.table_title.$el)),i.append(n.wrap(this.table.$el)),this.setElement(i)},title:function(e){this.table_title.title(e)},update:function(e,t){this.table.removeAll();for(var n in e)this._add(n,e[n],t)},_add:function(e,n,r){var i=null,s=n.type;switch(s){case"text":i=new t.Input({placeholder:n.placeholder,onchange:function(){r.set(e,i.value())}});break;case"select":i=new t.Select.View({data:n.data,onchange:function(){r.set(e,i.value())}});break;case"slider":i=new t.Input({placeholder:n.placeholder,onchange:function(){r.set(e,i.value())}});break;case"separator":i=$("<div/>");break;default:console.log("ui-table-form:_add","Unknown setting type ("+n.type+")");return}if(s!="separator"){r.get(e)||r.set(e,n.init),i.value(r.get(e));var o=$("<div/>");o.append(i.$el),o.append('<div class="toolParamHelp" style="font-size: 0.9em;">'+n.info+"</div>"),this.table.add('<span style="white-space: nowrap;">'+n.title+"</span>","25%"),this.table.add(o)}else this.table.add('<h6 style="white-space: nowrap;">'+n.title+":<h6/>"),this.table.add($("<div/>"));this.table.append(e)}});return{View:r}}),define("plugin/views/settings",["plugin/library/ui","plugin/library/ui-table-form","utils/utils"],function(e,t,n){return Backbone.View.extend({initialize:function(e,n){this.app=e;var r=this;this.chart=this.app.chart,this.form=new t.View({title:"Chart options:",content:"This chart type does not provide any options."}),this.setElement(this.form.$el);var r=this;this.chart.on("change",function(){r._refreshTable()})},_refreshTable:function(){var e=this.chart.get("type");if(!e)return;var t=this.app.types.get(e);this.form.title(t.title+":"),this.form.update(t.settings,this.chart.settings)}})}),define("plugin/views/chart",["mvc/ui/ui-tabs","plugin/library/ui-table","plugin/library/ui","utils/utils","plugin/models/chart","plugin/models/group","plugin/views/group","plugin/views/settings"],function(e,t,n,r,i,s,o,u){return Backbone.View.extend({optionsDefault:{header:!0,content:"No content available."},initialize:function(i,s){this.app=i,this.chart=this.app.chart,this.options=r.merge(s,this.optionsDefault);var o=this;this.table=new t({header:!1,onconfirm:function(e){o.chart.groups.length>0?o.app.modal.show({title:"Switching to another chart type?",body:"If you continue your settings and selections will be reseted.",buttons:{Cancel:function(){o.app.modal.hide()},Continue:function(){o.app.modal.hide(),o.table.value(e)}}}):o.table.value(e)},onchange:function(e){o.chart.groups.reset(),o.chart.settings.clear(),o.chart.set({type:e})},ondblclick:function(e){o.tabs.show("settings")},content:"No chart types available"});var a=0,f=i.types.attributes;for(var l in f){var c=f[l];this.table.add(++a+"."),this.table.add(c.title),this.table.append(l)}this.tabs=new e.View({title_new:"Add Data",onnew:function(){var e=o._addGroupModel();o.tabs.show(e.id)},operations:{save:new n.ButtonIcon({icon:"fa-save",tooltip:"Draw Chart",title:"Draw",onclick:function(){o.chart.groups.length==0&&o._addGroupModel(),o._saveChart(),o.hide(),o.app.charts_view.$el.show()}}),back:new n.ButtonIcon({icon:"fa-caret-left",tooltip:"Return to Viewer",title:"Return",onclick:function(){o.hide(),o.app.charts_view.$el.show()}})}}),this.title=new n.Input({placeholder:"Chart title",onchange:function(){o.app.config.set("title",o.title.value())}});var h=$("<div/>");h.append(r.wrap((new n.Label({title:"Provide a chart title:"})).$el)),h.append(r.wrap(this.title.$el)),h.append(r.wrap((new n.Label({title:"Select a chart type:"})).$el)),h.append(r.wrap(this.table.$el)),this.tabs.add({id:"main",title:"Start",$el:h}),this.settings=new u(this.app),this.tabs.add({id:"settings",title:"Configuration",$el:this.settings.$el}),this.setElement(this.tabs.$el),this.tabs.hideOperation("back");var o=this;this.chart.on("change:title",function(e){o.title.value(e.get("title")),o.app.config.set("title",e.get("title"))}),this.chart.on("change:type",function(e){o.table.value(e.get("type"))}),this.chart.on("reset",function(e){o._resetChart()}),this.app.charts.on("add",function(e){o.tabs.showOperation("back")}),this.app.charts.on("remove",function(e){o.app.charts.length==0&&o.tabs.hideOperation("back")}),this.app.charts.on("reset",function(e){o.tabs.hideOperation("back")}),this.app.chart.groups.on("add",function(e){o._addGroup(e)}),this.app.chart.groups.on("remove",function(e){o._removeGroup(e)}),this.app.chart.groups.on("reset",function(e){o._removeAllGroups()}),this.app.chart.groups.on("change:key",function(e){o._refreshGroupKey()}),this._resetChart()},hide:function(){$(".tooltip").hide(),this.$el.hide()},_refreshGroupKey:function(){var e=this,t=0;this.chart.groups.each(function(n){var r=n.get("key","");r==""&&(r="Chart data"),e.tabs.title(n.id,++t+": "+r)})},_addGroupModel:function(){var e=new s({id:r.uuid()});return this.chart.groups.add(e),e},_addGroup:function(e){var t=this,n=new o(this.app,{group:e}),r=t.chart.groups.length;this.tabs.add({id:e.id,$el:n.$el,ondel:function(){t.chart.groups.remove(e.id)}}),this._refreshGroupKey()},_removeGroup:function(e){this.tabs.del(e.id),this._refreshGroupKey()},_removeAllGroups:function(e){this.tabs.delRemovable()},_resetChart:function(){this.chart.set("id",r.uuid()),this.chart.set("type","bardiagram"),this.chart.set("dataset_id",this.app.options.dataset.id),this.chart.set("dataset_hid",this.app.options.dataset.hid),this.chart.set("history_id",this.app.options.dataset.history_id),this.chart.set("title","New Chart")},_saveChart:function(){this.chart.set({type:this.table.value(),title:this.title.value(),date:r.time()});var e=this.app.charts.get(this.chart.id);e?e.copy(this.chart):(e=this.chart.clone(),e.copy(this.chart),this.app.charts.add(e)),e.trigger("redraw",e)}})}),define("plugin/models/config",[],function(){return Backbone.Model.extend({defaults:{query_limit:1e3,query_timeout:500,title:"Create a new chart"}})}),define("plugin/models/charts",["plugin/models/chart"],function(e){return Backbone.Collection.extend({model:e})}),define("plugin/charts/_nvd3/config",[],function(){return{title:"",columns:{y:{title:"Values for y-axis"}},settings:{separator_label:{title:"X axis",type:"separator"},x_axis_label:{title:"Axis label",info:"Provide a label for the axis.",type:"text",init:"X-axis",placeholder:"Axis label"},x_axis_type:{title:"Axis value type",info:"Select the value type of the axis.",type:"select",init:"f",data:[{label:"-- Do not show values --",value:"hide"},{label:"Float",value:"f"},{label:"Exponent",value:"e"},{label:"Integer",value:"d"},{label:"Percentage",value:"p"},{label:"Rounded",value:"r"},{label:"SI-prefix",value:"s"}]},x_axis_tick:{title:"Axis tick format",info:"Select the tick format for the axis.",type:"select",init:".1",data:[{label:"0.00001",value:".5"},{label:"0.0001",value:".4"},{label:"0.001",value:".3"},{label:"0.01",value:".2"},{label:"0.1",value:".1"},{label:"1",value:"1"}]},separator_tick:{title:"Y axis",type:"separator"},y_axis_label:{title:"Axis label",info:"Provide a label for the axis.",type:"text",init:"Y-axis",placeholder:"Axis label"},y_axis_type:{title:"Axis value type",info:"Select the value type of the axis.",type:"select",init:"f",data:[{label:"-- Do not show values --",value:"hide"},{label:"Float",value:"f"},{label:"Exponent",value:"e"},{label:"Integer",value:"d"},{label:"Percentage",value:"p"},{label:"Rounded",value:"r"},{label:"SI-prefix",value:"s"}]},y_axis_tick:{title:"Axis tick format",info:"Select the tick format for the axis.",type:"select",init:".1",data:[{label:"0.00001",value:".5"},{label:"0.0001",value:".4"},{label:"0.001",value:".3"},{label:"0.01",value:".2"},{label:"0.1",value:".1"},{label:"1",value:"1"}]},separator_legend:{title:"Others",type:"separator"},show_legend:{title:"Show legend",info:"Would you like to add a legend?",type:"select",init:"true",data:[{label:"Yes",value:"true"},{label:"No",value:"false"}]}}}}),define("plugin/charts/bardiagram/config",["plugin/charts/_nvd3/config"],function(e){return $.extend(!0,{},e,{title:"Bar diagram"})}),define("plugin/charts/histogram/config",["plugin/charts/_nvd3/config"],function(e){return $.extend(!0,{},e,{title:"Histogram",mode:"execute",columns:{y:{title:"Observations"}},settings:{x_axis_label:{init:"Breaks"},y_axis_label:{init:"Density"},y_axis_tick:{init:".3"},separator_custom:{title:"Advanced",type:"separator"},bin_size:{title:"Number of bins",info:"Provide the number of histogram bins. The parsed data will be evenly distributed into bins according to the minimum and maximum values of the dataset.",type:"slider",init:10,min:10,max:1e3}}})}),define("plugin/charts/horizontal/config",["plugin/charts/_nvd3/config"],function(e){return $.extend(!0,{},e,{title:"Bar diagram (horizontal)",settings:{x_axis_type:{init:"hide"}}})}),define("plugin/charts/line/config",["plugin/charts/_nvd3/config"],function(e){return $.extend(!0,{},e,{title:"Line chart"})}),define("plugin/charts/linewithfocus/config",["plugin/charts/_nvd3/config"],function(e){return $.extend(!0,{},e,{title:"Line with focus"})}),define("plugin/charts/piechart/config",["plugin/charts/_nvd3/config"],function(e){return $.extend(!0,{},e,{title:"Pie chart"})}),define("plugin/charts/scatterplot/config",["plugin/charts/_nvd3/config"],function(e){return $.extend(!0,{},e,{title:"Scatter plot",columns:{x:{title:"Values for x-axis"}}})}),define("plugin/charts/stackedarea/config",["plugin/charts/_nvd3/config"],function(e){return $.extend(!0,{},e,{title:"Stacked area"})}),define("plugin/charts/types",["plugin/charts/bardiagram/config","plugin/charts/histogram/config","plugin/charts/horizontal/config","plugin/charts/line/config","plugin/charts/linewithfocus/config","plugin/charts/piechart/config","plugin/charts/scatterplot/config","plugin/charts/stackedarea/config"],function(e,t,n,r,i,s,o,u){return Backbone.Model.extend({defaults:{bardiagram:e,horizontal:n,histogram:t,line:r,linewithfocus:i,piechart:s,scatterplot:o,stackedarea:u}})}),define("plugin/app",["mvc/ui/ui-modal","mvc/ui/ui-portlet","plugin/library/ui","utils/utils","plugin/library/jobs","plugin/library/datasets","plugin/views/charts","plugin/views/chart","plugin/models/config","plugin/models/chart","plugin/models/charts","plugin/charts/types"],function(e,t,n,r,i,s,o,u,a,f,l,c){return Backbone.View.extend({initialize:function(n){this.options=n,Galaxy&&Galaxy.modal?this.modal=Galaxy.modal:this.modal=new e.View,this.config=new a,this.jobs=new i(this),this.types=new c,this.chart=new f,this.charts=new l,this.datasets=new s(this),this.charts_view=new o(this),this.chart_view=new u(this),this.options.config.widget?this.portlet=$("<div></div>"):this.portlet=new t.View({icon:"fa-bar-chart-o"}),this.portlet.append(this.charts_view.$el),this.portlet.append(this.chart_view.$el),this.options.config.widget?this.setElement(this.portlet):this.setElement(this.portlet.$el),this.charts_view.$el.hide();var r=this;this.config.on("change:title",function(){r._refreshTitle()}),this.render()},render:function(){this._refreshTitle()},_refreshTitle:function(){var e=this.config.get("title");e&&(e=" - "+e),this.portlet.title("Charts"+e)},execute:function(e){},onunload:function(){},log:function(e,t){console.log(e+" "+t)}})});
\ No newline at end of file
diff -r 97063e8da923eac2f9abc0bf256f4a7c98b0acec -r e22d4f2b57a8c99cf5b48d36e0ca440934eaa569 config/plugins/visualizations/charts/static/charts/types.js
--- a/config/plugins/visualizations/charts/static/charts/types.js
+++ b/config/plugins/visualizations/charts/static/charts/types.js
@@ -1,6 +1,6 @@
// dependencies
define(['plugin/charts/bardiagram/config',
- 'plugin/charts/boxplot/config',
+ //'plugin/charts/boxplot/config',
'plugin/charts/histogram/config',
'plugin/charts/horizontal/config',
'plugin/charts/line/config',
@@ -9,7 +9,7 @@
'plugin/charts/scatterplot/config',
'plugin/charts/stackedarea/config',
], function(bardiagram,
- boxplot,
+ //boxplot,
histogram,
horizontal,
line,
@@ -25,7 +25,7 @@
// types
defaults: {
'bardiagram' : bardiagram,
- 'boxplot' : boxplot,
+ //'boxplot' : boxplot,
'horizontal' : horizontal,
'histogram' : histogram,
'line' : line,
diff -r 97063e8da923eac2f9abc0bf256f4a7c98b0acec -r e22d4f2b57a8c99cf5b48d36e0ca440934eaa569 config/plugins/visualizations/charts/templates/charts.mako
--- a/config/plugins/visualizations/charts/templates/charts.mako
+++ b/config/plugins/visualizations/charts/templates/charts.mako
@@ -25,7 +25,7 @@
${h.stylesheet_link( app_root + "plugins/nv.d3.css" )}
## install boxplot module
- ${h.javascript_link( app_root + "plugins/box.js" )}
+ ##${h.javascript_link( app_root + "plugins/box.js" )}
## load merged/minified code
${h.javascript_link( app_root + "build-app.js" )}
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0