From 604dfb27ce0acba176c059f08642aae7a390ddf0 Mon Sep 17 00:00:00 2001 From: Daniel Blankenberg Date: Fri, 7 Oct 2016 11:02:35 -0400 Subject: [PATCH 01/32] Fix for reruning tool with workflow resume that is part of a collection. The special casing of "__identifier__" that occurs here and elsewhere in the code is less than ideal. --- lib/galaxy/tools/parameters/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/galaxy/tools/parameters/__init__.py b/lib/galaxy/tools/parameters/__init__.py index cbb58e107f0..32086f18f22 100644 --- a/lib/galaxy/tools/parameters/__init__.py +++ b/lib/galaxy/tools/parameters/__init__.py @@ -201,7 +201,7 @@ def update_param( prefixed_name, input_values, new_value ): """ for key in input_values: match = re.match( '^' + key + '_(\d+)\|(.+)', prefixed_name ) - if match: + if match and not key.endswith( "|__identifier__" ): index = int( match.group( 1 ) ) if isinstance( input_values[ key ], list ) and len( input_values[ key ] ) > index: update_param( match.group( 2 ), input_values[ key ][ index ], new_value ) From 415c7fa67be28885fa92246fb63efdf5bfd777bf Mon Sep 17 00:00:00 2001 From: Marius van den Beek Date: Fri, 7 Oct 2016 20:37:07 +0200 Subject: [PATCH 02/32] Verify tools coming from cache have correct id. --- lib/galaxy/tools/toolbox/base.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/galaxy/tools/toolbox/base.py b/lib/galaxy/tools/toolbox/base.py index 2ac9b8e7221..23145efca45 100644 --- a/lib/galaxy/tools/toolbox/base.py +++ b/lib/galaxy/tools/toolbox/base.py @@ -520,8 +520,13 @@ class AbstractToolBox( Dictifiable, ManagesIntegratedToolPanelMixin, object ): tool = self.load_tool_from_cache(os.path.join(tool_path, path)) from_cache = tool if from_cache: - log.debug("Loading tool %s from cache", str(tool.id)) - elif guid: # tool was not in cache and is a tool shed tool + if guid and tool.id != guid: + # In rare cases a tool shed tool is loaded into the cache without guid. + # In that case recreating the tool will correct the cached version. + from_cache = False + else: + log.debug("Loading tool %s from cache", str(tool.id)) + if guid and not from_cache: # tool was not in cache and is a tool shed tool tool_shed_repository = self.get_tool_repository_from_xml_item(item, path) if tool_shed_repository: # Only load tools if the repository is not deactivated or uninstalled. From 89278b0ec70b7a8f1cf42bda87335cf4f98f175b Mon Sep 17 00:00:00 2001 From: John Chilton Date: Fri, 7 Oct 2016 15:11:00 -0400 Subject: [PATCH 03/32] Fix for bioconda recipes depending on conda-forge. Some bioconda recipes now depend on conda forge, this fix is needed for them to work properly. --- config/galaxy.ini.sample | 2 +- doc/source/admin/dependency_resolvers.rst | 2 +- lib/galaxy/tools/deps/__init__.py | 2 +- lib/galaxy/tools/deps/resolvers/conda.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/config/galaxy.ini.sample b/config/galaxy.ini.sample index b590b8a589c..5e1b6cb50ab 100644 --- a/config/galaxy.ini.sample +++ b/config/galaxy.ini.sample @@ -198,7 +198,7 @@ paste.app_factory = galaxy.web.buildapp:app_factory # Pass debug flag to conda commands. #conda_debug = False # conda channels to enable by default (http://conda.pydata.org/docs/custom-channels.html) -#conda_ensure_channels = r,bioconda,iuc +#conda_ensure_channels = conda-forge,r,bioconda,iuc # Set to True to instruct Galaxy to look for and install missing tool # dependencies before each job runs. #conda_auto_install = False diff --git a/doc/source/admin/dependency_resolvers.rst b/doc/source/admin/dependency_resolvers.rst index 3c140d5d987..6ec8e9f6804 100644 --- a/doc/source/admin/dependency_resolvers.rst +++ b/doc/source/admin/dependency_resolvers.rst @@ -182,7 +182,7 @@ debug ensure_channels conda channels to enable by default. See http://conda.pydata.org/docs/custom-channels.html for more - information about channels. (default: r,bioconda). + information about channels. (default: conda-forge,r,bioconda,iuc). auto_install Set to True to instruct Galaxy to look for and install missing tool diff --git a/lib/galaxy/tools/deps/__init__.py b/lib/galaxy/tools/deps/__init__.py index d683afb364f..aecab6c8b29 100644 --- a/lib/galaxy/tools/deps/__init__.py +++ b/lib/galaxy/tools/deps/__init__.py @@ -20,7 +20,7 @@ EXTRA_CONFIG_KWDS = { 'conda_prefix': None, 'conda_exec': None, 'conda_debug': None, - 'conda_ensure_channels': 'r,bioconda,iuc', + 'conda_ensure_channels': 'conda-forge,r,bioconda,iuc', 'conda_auto_install': False, 'conda_auto_init': False, 'conda_copy_dependencies': False, diff --git a/lib/galaxy/tools/deps/resolvers/conda.py b/lib/galaxy/tools/deps/resolvers/conda.py index d2ff051477e..f94cc015692 100644 --- a/lib/galaxy/tools/deps/resolvers/conda.py +++ b/lib/galaxy/tools/deps/resolvers/conda.py @@ -32,7 +32,7 @@ from ..conda_util import ( DEFAULT_BASE_PATH_DIRECTORY = "_conda" DEFAULT_CONDARC_OVERRIDE = "_condarc" -DEFAULT_ENSURE_CHANNELS = "r,bioconda,iuc" +DEFAULT_ENSURE_CHANNELS = "conda-forge,r,bioconda,iuc" import logging log = logging.getLogger(__name__) From 56dfaf003f3239d2475715b478e319ff359e08d4 Mon Sep 17 00:00:00 2001 From: Marius van den Beek Date: Sat, 8 Oct 2016 08:48:47 +0200 Subject: [PATCH 04/32] Fix tool reload in 415c7fa67be28885fa92246fb63efdf5bfd777bf Previous commit changed the if/elif/else logic. The else was now applying also to cached tools, which it should not. Instead, explicitly check that we still don't have a tool object. --- lib/galaxy/tools/toolbox/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/galaxy/tools/toolbox/base.py b/lib/galaxy/tools/toolbox/base.py index 23145efca45..a26ef0b5d86 100644 --- a/lib/galaxy/tools/toolbox/base.py +++ b/lib/galaxy/tools/toolbox/base.py @@ -533,7 +533,7 @@ class AbstractToolBox( Dictifiable, ManagesIntegratedToolPanelMixin, object ): can_load_into_panel_dict = not tool_shed_repository.deleted repository_id = self.app.security.encode_id(tool_shed_repository.id) tool = self.load_tool(os.path.join( tool_path, path ), guid=guid, repository_id=repository_id, use_cached=False) - else: # tool was not in cache and is not a tool shed tool. + if not tool: # tool was not in cache and is not a tool shed tool. tool = self.load_tool(os.path.join(tool_path, path), use_cached=False) if string_as_bool(item.get( 'hidden', False )): tool.hidden = True From 5a72449246be6f0f2fe3955bebf6f42ae89966bc Mon Sep 17 00:00:00 2001 From: John Chilton Date: Thu, 29 Sep 2016 14:28:56 -0400 Subject: [PATCH 05/32] Implement beta support for dynamically discovering mulled containers for dependencies. Contains all mulled fixes and enhancements through 16.10.7 including several bug fixes from @bgruening. --- config/galaxy.ini.sample | 31 ++ lib/galaxy/config.py | 20 +- lib/galaxy/tools/deps/__init__.py | 4 +- lib/galaxy/tools/deps/conda_compat.py | 112 +++++++ lib/galaxy/tools/deps/conda_util.py | 84 +++++- .../deps/container_resolvers/__init__.py | 50 +++ .../deps/container_resolvers/explicit.py | 26 ++ .../tools/deps/container_resolvers/mulled.py | 207 +++++++++++++ lib/galaxy/tools/deps/containers.py | 106 +++++-- lib/galaxy/tools/deps/installable.py | 77 +++++ lib/galaxy/tools/deps/mulled/__init__.py | 0 lib/galaxy/tools/deps/mulled/_cli.py | 19 ++ lib/galaxy/tools/deps/mulled/invfile.lua | 67 +++++ lib/galaxy/tools/deps/mulled/mulled_build.py | 284 ++++++++++++++++++ .../tools/deps/mulled/mulled_build_channel.py | 109 +++++++ .../tools/deps/mulled/mulled_build_files.py | 80 +++++ .../tools/deps/mulled/mulled_build_tool.py | 52 ++++ lib/galaxy/tools/deps/mulled/mulled_search.py | 131 ++++++++ lib/galaxy/tools/deps/mulled/util.py | 116 +++++++ lib/galaxy/tools/deps/resolvers/__init__.py | 19 +- lib/galaxy/tools/deps/resolvers/conda.py | 43 +-- .../tools/mulled_example_multi_1.xml | 18 ++ test/functional/tools/samples_tool_conf.xml | 2 + 23 files changed, 1569 insertions(+), 88 deletions(-) create mode 100644 lib/galaxy/tools/deps/conda_compat.py create mode 100644 lib/galaxy/tools/deps/container_resolvers/__init__.py create mode 100644 lib/galaxy/tools/deps/container_resolvers/explicit.py create mode 100644 lib/galaxy/tools/deps/container_resolvers/mulled.py create mode 100644 lib/galaxy/tools/deps/installable.py create mode 100644 lib/galaxy/tools/deps/mulled/__init__.py create mode 100644 lib/galaxy/tools/deps/mulled/_cli.py create mode 100644 lib/galaxy/tools/deps/mulled/invfile.lua create mode 100644 lib/galaxy/tools/deps/mulled/mulled_build.py create mode 100644 lib/galaxy/tools/deps/mulled/mulled_build_channel.py create mode 100644 lib/galaxy/tools/deps/mulled/mulled_build_files.py create mode 100644 lib/galaxy/tools/deps/mulled/mulled_build_tool.py create mode 100644 lib/galaxy/tools/deps/mulled/mulled_search.py create mode 100644 lib/galaxy/tools/deps/mulled/util.py create mode 100644 test/functional/tools/mulled_example_multi_1.xml diff --git a/config/galaxy.ini.sample b/config/galaxy.ini.sample index 72876dfc65c..f60ce30d917 100644 --- a/config/galaxy.ini.sample +++ b/config/galaxy.ini.sample @@ -228,6 +228,37 @@ paste.app_factory = galaxy.web.buildapp:app_factory # than the watchdog default. #watch_tools = False +# Enable Galaxy to fetch Docker containers registered with quay.io generated +# from tool requirements resolved through conda. These containers (when +# available) have been generated using mulled - https://github.com/mulled. +# These containers are highly beta and availablity will vary by tool. +# This option will additionally only be used for job destinations with +# Docker enabled. +#enable_beta_mulled_containers = False + +# Container resolvers configuration (beta). Setup a file describing container +# resolvers to use when discovering containers for Galaxy. If this is set to +# None, the default containers loaded is determined by +# enable_beta_mulled_containers. +#containers_resolvers_config_file = None + +# Container resolvers configuration (beta). Setup a file describing container +# resolvers to use when discovering containers for Galaxy. If this is set to +# None, the default containers loaded is determined by +# enable_beta_mulled_containers. +#containers_resolvers_config_file = None + +# involucro is a tool used to build Docker containers for tools from Conda +# dependencies referenced in tools as `requirement`s. The following path is +# the location of involucro on the Galaxy host. This is ignored if the relevant +# container resolver isn't enabled, and will install on demand unless +# involucro_auto_init is set to False. +#involucro_path = database/dependencies/involucro + +# Install involucro as needed to build Docker containers for tools. Ignored if +# relevant container resolver is not used. +#involucro_auto_init = True + # Enable automatic polling of relative tool sheds to see if any updates # are available for installed repositories. Ideally only one Galaxy # server process should be able to check for repository updates. The diff --git a/lib/galaxy/config.py b/lib/galaxy/config.py index 9c590c4c747..86b9cc82785 100644 --- a/lib/galaxy/config.py +++ b/lib/galaxy/config.py @@ -226,6 +226,7 @@ class Configuration( object ): self.local_task_queue_workers = int(kwargs.get("local_task_queue_workers", 2)) self.tool_submission_burst_threads = int( kwargs.get( 'tool_submission_burst_threads', '1' ) ) self.tool_submission_burst_at = int( kwargs.get( 'tool_submission_burst_at', '10' ) ) + # Enable new interface for API installations from TS. # Admin menu will list both if enabled. self.enable_beta_ts_api_install = string_as_bool( kwargs.get( 'enable_beta_ts_api_install', 'False' ) ) @@ -323,6 +324,19 @@ class Configuration( object ): else: self.tool_dependency_dir = None self.use_tool_dependencies = os.path.exists(self.dependency_resolvers_config_file) + + self.enable_beta_mulled_containers = string_as_bool( kwargs.get( 'enable_beta_mulled_containers', 'False' ) ) + containers_resolvers_config_file = kwargs.get( 'containers_resolvers_config_file', None ) + if containers_resolvers_config_file: + containers_resolvers_config_file = resolve_path(containers_resolvers_config_file, self.root) + self.containers_resolvers_config_file = containers_resolvers_config_file + + involucro_path = kwargs.get('involucro_path', None) + if involucro_path is None: + involucro_path = os.path.join(tool_dependency_dir, "involucro") + self.involucro_path = resolve_path(involucro_path, self.root) + self.involucro_auto_init = string_as_bool(kwargs.get( 'involucro_auto_init', True)) + # Configuration options for taking advantage of nginx features self.upstream_gzip = string_as_bool( kwargs.get( 'upstream_gzip', False ) ) self.apache_xsendfile = string_as_bool( kwargs.get( 'apache_xsendfile', False ) ) @@ -846,7 +860,11 @@ class ConfiguresGalaxyMixin: default_file_path=file_path, outputs_to_working_directory=self.config.outputs_to_working_directory, container_image_cache_path=self.config.container_image_cache_path, - library_import_dir=self.config.library_import_dir + library_import_dir=self.config.library_import_dir, + enable_beta_mulled_containers=self.config.enable_beta_mulled_containers, + containers_resolvers_config_file=self.config.containers_resolvers_config_file, + involucro_path=self.config.involucro_path, + involucro_auto_init=self.config.involucro_auto_init, ) self.container_finder = containers.ContainerFinder(app_info) diff --git a/lib/galaxy/tools/deps/__init__.py b/lib/galaxy/tools/deps/__init__.py index 763732e4dc7..ccf5bb25753 100644 --- a/lib/galaxy/tools/deps/__init__.py +++ b/lib/galaxy/tools/deps/__init__.py @@ -8,7 +8,7 @@ import os.path from galaxy.util import plugin_config from .resolvers import NullDependency -from .resolvers.conda import CondaDependencyResolver +from .resolvers.conda import CondaDependencyResolver, DEFAULT_ENSURE_CHANNELS from .resolvers.galaxy_packages import GalaxyPackageDependencyResolver from .resolvers.tool_shed_packages import ToolShedPackageDependencyResolver @@ -21,7 +21,7 @@ EXTRA_CONFIG_KWDS = { 'conda_prefix': None, 'conda_exec': None, 'conda_debug': None, - 'conda_ensure_channels': 'r,bioconda,iuc', + 'conda_ensure_channels': DEFAULT_ENSURE_CHANNELS, 'conda_auto_install': False, 'conda_auto_init': False, 'conda_copy_dependencies': False, diff --git a/lib/galaxy/tools/deps/conda_compat.py b/lib/galaxy/tools/deps/conda_compat.py new file mode 100644 index 00000000000..1443378f9f5 --- /dev/null +++ b/lib/galaxy/tools/deps/conda_compat.py @@ -0,0 +1,112 @@ +"""Compat. layer with conda_build/verify if Galaxy/galaxy-lib not installed through conda. + +In general there are utilities available for Conda building and parsing that are high-quality +and should be utilized when available but that are only available in conda channels and not in +PyPI. This module serves as a PyPI capable interface to these utilities. +""" +import collections +import os +import yaml + + +try: + from conda_build.metadata import MetaData +except ImportError: + MetaData = None + +try: + from anaconda_verify.recipe import render_jinja2, parse +except ImportError: + render_jinja2 = None + parse = None + + +class _Memoized(object): + + def __init__(self, func): + self.func = func + self.cache = {} + + def __call__(self, *args): + if not isinstance(args, collections.Hashable): + # uncacheable. a list, for instance. + # better to not cache than blow up. + return self.func(*args) + if args in self.cache: + return self.cache[args] + else: + value = self.func(*args) + self.cache[args] = value + return value + + +def _parse(data, cfg): + """Parse metadata YAML.""" + assert cfg is None, "Conda utilities for evaluating cfg are not available." + return dict(yamlize(data)) + + +def _render_jinja2(recipe_dir): + """Evaluate Conda recipe as a jinja template.""" + try: + import jinja2 + except ImportError: + raise Exception("Failed to import jinja2 for evaluating Conda recipe templates.") + + loaders = [jinja2.FileSystemLoader(recipe_dir)] + env = jinja2.Environment(loader=jinja2.ChoiceLoader(loaders)) + template = env.get_or_select_template('meta.yaml') + return template.render(environment=env) + + +@_Memoized +def yamlize(data): + res = yaml.load(data) + # ensure the result is a dict + if res is None: + res = {} + return res + + +if render_jinja2 is None: + render_jinja2 = _render_jinja2 + +if parse is None: + parse = _parse + + +def raw_metadata(recipe_dir): + """Evaluate Conda template if needed and return raw metadata for supplied recipe directory.""" + meta_path = os.path.join(recipe_dir, 'meta.yaml') + with open(meta_path, 'rb') as fi: + data = fi.read() + if b'{{' in data: + data = render_jinja2(recipe_dir) + meta = parse(data, None) + return meta + + +class _MetaData(object): + + def __init__(self, input_dir): + self.meta = raw_metadata(input_dir) + + def get_value(self, field, default=None): + """Get nested field value or supplied default is not present.""" + section, key = field.split('/') + submeta = self.meta.get(section) + if submeta is None: + submeta = {} + res = submeta.get(key) + if res is None: + res = default + return res + + +if MetaData is None: + MetaData = _MetaData + +__all__ = [ + "MetaData", + "raw_metadata", +] diff --git a/lib/galaxy/tools/deps/conda_util.py b/lib/galaxy/tools/deps/conda_util.py index dc91f9f967a..117acca4389 100644 --- a/lib/galaxy/tools/deps/conda_util.py +++ b/lib/galaxy/tools/deps/conda_util.py @@ -7,12 +7,14 @@ import re import shutil import tempfile +from distutils.version import LooseVersion from sys import platform as _platform import six import yaml from ..deps import commands +from ..deps import installable log = logging.getLogger(__name__) @@ -47,7 +49,8 @@ def find_conda_prefix(conda_prefix=None): return conda_prefix -class CondaContext(object): +class CondaContext(installable.InstallableContext): + installable_description = "Conda" def __init__(self, conda_prefix=None, conda_exec=None, shell_exec=None, debug=False, ensure_channels='', @@ -230,6 +233,16 @@ class CondaContext(object): def activate(self): return self._bin("activate") + def is_installed(self): + return self.is_conda_installed() + + def can_install(self): + return self.can_install_conda() + + @property + def parent_path(self): + return os.path.dirname(os.path.abspath(self.conda_prefix)) + def _bin(self, name): return os.path.join(self.conda_prefix, "bin", name) @@ -273,6 +286,8 @@ class CondaTarget(object): return "CondaTarget[%s]" % attributes + __repr__ = __str__ + @property def package_specifier(self): """ Return a package specifier as consumed by conda install/create. @@ -293,6 +308,17 @@ class CondaTarget(object): else: return "__%s@_uv_" % (self.package) + def __hash__(self): + return hash((self.package, self.version, self.channel)) + + def __eq__(self, other): + if isinstance(other, self.__class__): + return (self.package, self.version, self.channel) == (other.package, other.version, other.channel) + return False + + def __ne__(self, other): + return not(self == other) + def hash_conda_packages(conda_packages, conda_target=None): """ Produce a unique hash on supplied packages. @@ -339,23 +365,55 @@ def cleanup_failed_install(conda_target, conda_context=None): conda_context.exec_remove([conda_target.install_environment]) -def is_target_available(conda_target, conda_context=None): - """ Checks if a specified target is available for installation. - If the package name exists return "True". If in addition the version matches exactly return "exact". - Otherwise return False. +def best_search_result(conda_target, conda_context=None, channels_override=None): + """Find best "conda search" result for specified target. + + Return ``None`` if no results match. """ conda_context = _ensure_conda_context(conda_context) - conda_context.ensure_channels_configured() - search_cmd = [conda_context.conda_exec, "search", "--full-name", "--json", conda_target.package] + if not channels_override: + conda_context.ensure_channels_configured() + + search_cmd = [conda_context.conda_exec, "search", "--full-name", "--json"] + if channels_override: + search_cmd.append("--override-channels") + for channel in channels_override: + search_cmd.extend(["--channel", channel]) + search_cmd.append(conda_target.package) res = commands.execute(search_cmd) hits = json.loads(res).get(conda_target.package, []) + hits = sorted(hits, key=lambda hit: LooseVersion(hit['version']), reverse=True) - if len(hits) > 0: - if conda_target.version: - for hit in hits: - if hit['version'] == conda_target.version: - return 'exact' - return True + if len(hits) == 0: + return (None, None) + + best_result = (hits[0], False) + + for hit in hits: + if is_search_hit_exact(conda_target, hit): + best_result = (hit, True) + break + + return best_result + + +def is_search_hit_exact(conda_target, search_hit): + target_version = conda_target.version + # It'd be nice to make request verson of 1.0 match available + # version of 1.0.3 or something like that. + return not target_version or search_hit['version'] == target_version + + +def is_target_available(conda_target, conda_context=None, channels_override=None): + """Check if a specified target is available for installation. + + If the package name exists return ``True`` (the ``bool``). If in addition + the version matches exactly return "exact" (a string). Otherwise return + ``False``. + """ + (best_hit, exact) = best_search_result(conda_target, conda_context, channels_override) + if best_hit: + return 'exact' if exact else True else: return False diff --git a/lib/galaxy/tools/deps/container_resolvers/__init__.py b/lib/galaxy/tools/deps/container_resolvers/__init__.py new file mode 100644 index 00000000000..c478bcce3a2 --- /dev/null +++ b/lib/galaxy/tools/deps/container_resolvers/__init__.py @@ -0,0 +1,50 @@ +"""The module defines the abstract interface for resolving container images for tool execution.""" +from abc import ( + ABCMeta, + abstractmethod, + abstractproperty, +) + +from galaxy.util.dictifiable import Dictifiable + + +class ContainerResolver(Dictifiable, object): + """Description of a technique for resolving container images for tool execution.""" + + # Keys for dictification. + dict_collection_visible_keys = ['resolver_type'] + + __metaclass__ = ABCMeta + + def __init__(self, app_info=None, **kwds): + """Default initializer for ``ContainerResolver`` subclasses.""" + self.app_info = app_info + self.resolver_kwds = kwds + + def _get_config_option(self, key, default=None, config_prefix=None, **kwds): + """Look in resolver-specific settings for option and then fallback to + global settings. + """ + global_key = "%s_%s" % (config_prefix, key) + if key in kwds: + return kwds.get(key) + elif self.app_info and hasattr(self.app_info, global_key): + return getattr(self.app_info, global_key) + else: + return default + + @abstractmethod + def resolve(self, tool_info): + """Find a container matching all supplied requirements for tool. + + The supplied argument is a :class:`galaxy.tools.deps.containers.ToolInfo` description + of the tool and its requirements. + """ + + @abstractproperty + def resolver_type(self): + """Short label for the type of container resolution.""" + + def _container_type_enabled(self, container_description, enabled_container_types): + """Return a boolean indicating if the specified container type is enabled.""" + return container_description.type in enabled_container_types diff --git a/lib/galaxy/tools/deps/container_resolvers/explicit.py b/lib/galaxy/tools/deps/container_resolvers/explicit.py new file mode 100644 index 00000000000..851f0c1f99c --- /dev/null +++ b/lib/galaxy/tools/deps/container_resolvers/explicit.py @@ -0,0 +1,26 @@ +"""This module describes the :class:`ExplicitContainerResolver` ContainerResolver plugin.""" +import logging + +from ..container_resolvers import ( + ContainerResolver, +) + +log = logging.getLogger(__name__) + + +class ExplicitContainerResolver(ContainerResolver): + """Find explicit containers referenced in the tool description (e.g. tool XML file) if present.""" + + resolver_type = "explicit" + + def resolve(self, enabled_container_types, tool_info): + """Find a container explicitly mentioned in tool description. + + This ignores the tool requirements and assumes the tool author crafted + a correct container. + """ + for container_description in tool_info.container_descriptions: + if self._container_type_enabled(container_description): + return True + + return False diff --git a/lib/galaxy/tools/deps/container_resolvers/mulled.py b/lib/galaxy/tools/deps/container_resolvers/mulled.py new file mode 100644 index 00000000000..2b2701270ef --- /dev/null +++ b/lib/galaxy/tools/deps/container_resolvers/mulled.py @@ -0,0 +1,207 @@ +"""This module describes the :class:`MulledContainerResolver` ContainerResolver plugin.""" + +import collections +import logging + +from ..container_resolvers import ( + ContainerResolver, +) +from ..docker_util import build_docker_images_command +from ..mulled.mulled_build import ( + check_output, + DEFAULT_CHANNELS, + ensure_installed, + InvolucroContext, + mull_targets, +) +from ..mulled.mulled_build_tool import requirements_to_mulled_targets +from ..mulled.util import ( + image_name, + mulled_tags_for, + split_tag, +) +from ..requirements import ContainerDescription + +log = logging.getLogger(__name__) + + +CachedMulledImageSingleTarget = collections.namedtuple("CachedMulledImageSingleTarget", ["package_name", "version", "build", "image_identifier"]) +CachedMulledImageMultiTarget = collections.namedtuple("CachedMulledImageMultiTarget", ["hash", "image_identifier"]) + +CachedMulledImageSingleTarget.multi_target = False +CachedMulledImageMultiTarget.multi_target = True + + +def list_cached_mulled_images(namespace=None): + command = build_docker_images_command(truncate=True, sudo_docker=False) + command = "%s | tail -n +2 | tr -s ' ' | cut -d' ' -f1,2" % command + images_and_versions = check_output(command) + name_filter = get_filter(namespace) + + def output_line_to_image(line): + image_name, version = line.split(" ", 1) + identifier = "%s:%s" % (image_name, version) + url, namespace, package_description = image_name.split("/") + + if package_description.startswith("mulled-v1-"): + hash = package_description + image = CachedMulledImageMultiTarget(hash, identifier) + else: + build = None + if not version or version == "latest": + version = None + + if version and "--" in version: + version, build = split_tag(version) + + image = CachedMulledImageSingleTarget(image_name, version, build, identifier) + + return image + + return map(output_line_to_image, filter(name_filter, images_and_versions.splitlines())) + + +def get_filter(namespace): + prefix = "quay.io/" if namespace is None else "quay.io/%s" % namespace + return lambda name: name.startswith(prefix) and name.count("/") == 2 + + +def cached_container_description(targets, namespace): + if len(targets) == 0: + return None + + cached_images = list_cached_mulled_images(namespace) + image = None + if len(targets) == 1: + target = targets[0] + for cached_image in cached_images: + if cached_image.multi_target: + continue + if not cached_image.package_name == target.package_name: + continue + if not target.version or target.version == cached_image.version: + image = cached_image + break + else: + name = image_name(targets) + for cached_image in cached_images: + if not cached_image.multi_target: + continue + + if name == cached_image.hash: + image = cached_image + break + + container = None + if image: + container = ContainerDescription( + image.image_identifier, + type="docker", + ) + + return container + + +class CachedMulledContainerResolver(ContainerResolver): + + resolver_type = "cached_mulled" + + def __init__(self, app_info=None, namespace=None): + super(CachedMulledContainerResolver, self).__init__(app_info) + self.namespace = namespace + + def resolve(self, enabled_container_types, tool_info): + targets = mulled_targets(tool_info) + return cached_container_description(targets, self.namespace) + + +class MulledContainerResolver(ContainerResolver): + """Look for mulled images matching tool dependencies.""" + + resolver_type = "mulled" + + def __init__(self, app_info=None, namespace="mulled"): + super(MulledContainerResolver, self).__init__(app_info) + self.namespace = namespace + + def resolve(self, enabled_container_types, tool_info): + targets = mulled_targets(tool_info) + if len(targets) == 0: + return None + + name = None + + if len(targets) == 1: + target = targets[0] + target_version = target.version + tags = mulled_tags_for(self.namespace, target.package_name) + + if not tags: + return None + + if target_version: + for tag in tags: + version, build = split_tag(tag) + if version == target_version: + name = "%s:%s--%s" % (target.package_name, version, build) + break + else: + version, build = split_tag(tags[0]) + name = "%s:%s--%s" % (target.package_name, version, build) + else: + base_image_name = image_name(targets) + tags = mulled_tags_for(self.namespace, base_image_name) + if tags: + name = "%s:%s" % (base_image_name, tags[0]) + + if name: + return ContainerDescription( + "quay.io/%s/%s" % (self.namespace, name), + type="docker", + ) + + +class BuildMulledContainerResolver(ContainerResolver): + """Look for mulled images matching tool dependencies.""" + + resolver_type = "build_mulled" + + def __init__(self, app_info=None, namespace="local", **kwds): + super(BuildMulledContainerResolver, self).__init__(app_info) + self._involucro_context_kwds = { + 'involucro_bin': self._get_config_option("involucro_path", None) + } + self.namespace = namespace + self._mulled_kwds = { + 'namespace': namespace, + 'channels': self._get_config_option("channels", DEFAULT_CHANNELS, prefix="mulled"), + } + self.auto_init = self._get_config_option("auto_init", DEFAULT_CHANNELS, prefix="involucro") + + def resolve(self, enabled_container_types, tool_info): + targets = mulled_targets(tool_info) + if len(targets) == 0: + return None + + mull_targets( + targets, + involucro_context=self._get_involucro_context(), + **self._mulled_kwds + ) + return cached_container_description(targets, self.namespace) + + def _get_involucro_context(self): + involucro_context = InvolucroContext(**self._involucro_context_kwds) + self.enabled = ensure_installed(involucro_context, self.auto_init) + return involucro_context + + +def mulled_targets(tool_info): + return requirements_to_mulled_targets(tool_info.requirements) + + +__all__ = [ + "CachedMulledContainerResolver", + "MulledContainerResolver", + "BuildMulledContainerResolver", +] diff --git a/lib/galaxy/tools/deps/containers.py b/lib/galaxy/tools/deps/containers.py index aadf73ef482..fff1989986b 100644 --- a/lib/galaxy/tools/deps/containers.py +++ b/lib/galaxy/tools/deps/containers.py @@ -10,14 +10,23 @@ from abc import ( import six from galaxy.util import asbool +from galaxy.util import plugin_config +from .container_resolvers.explicit import ExplicitContainerResolver +from .container_resolvers.mulled import ( + BuildMulledContainerResolver, + CachedMulledContainerResolver, + MulledContainerResolver, +) from .requirements import ContainerDescription from .requirements import DEFAULT_CONTAINER_RESOLVE_DEPENDENCIES, DEFAULT_CONTAINER_SHELL from ..deps import docker_util log = logging.getLogger(__name__) -DEFAULT_CONTAINER_TYPE = "docker" +DOCKER_CONTAINER_TYPE = "docker" +DEFAULT_CONTAINER_TYPE = DOCKER_CONTAINER_TYPE +ALL_CONTAINER_TYPES = [DOCKER_CONTAINER_TYPE] LOAD_CACHED_IMAGE_COMMAND_TEMPLATE = ''' python << EOF @@ -47,9 +56,18 @@ class ContainerFinder(object): def __init__(self, app_info): self.app_info = app_info - self.container_registry = ContainerRegistry() + self.container_registry = ContainerRegistry(app_info) + + def __enabled_container_types(self, destination_info): + return [t for t in ALL_CONTAINER_TYPES if self.__container_type_enabled(t, destination_info)] def find_container(self, tool_info, destination_info, job_info): + enabled_container_types = self.__enabled_container_types(destination_info) + + # Short-cut everything else and just skip checks if no container type is enabled. + if not enabled_container_types: + return NULL_CONTAINER + def __destination_container(container_description=None, container_id=None, container_type=None): if container_description: container_id = container_description.identifier @@ -82,23 +100,10 @@ class ContainerFinder(object): return container # Otherwise lets see if we can find container for the tool. - - # Exact matches first from explicitly listed containers in tools... - for container_description in tool_info.container_descriptions: - container = __destination_container(container_description) - if container: - return container - - # Implement vague concept of looping through all containers - # matching requirements. Exact details need to be worked through - # but hopefully the idea that it sits below find_container somewhere - # external components to this module don't need to worry about it - # is good enough. - container_descriptions = self.container_registry.container_descriptions_for_requirements(tool_info.requirements) - for container_description in container_descriptions: - container = __destination_container(container_description) - if container: - return container + container_description = self.container_registry.find_best_container_description(enabled_container_types, tool_info) + container = __destination_container(container_description) + if container: + return container # If we still don't have a container, check to see if any container # types define a default container id and use that. @@ -175,14 +180,55 @@ class NullContainerFinder(object): return [] -class ContainerRegistry(): +class ContainerRegistry(object): + """Loop through enabled ContainerResolver plugins and find first match.""" - def __init__(self): - pass + def __init__(self, app_info): + self.resolver_classes = self.__resolvers_dict() + self.enable_beta_mulled_containers = app_info.enable_beta_mulled_containers + self.app_info = app_info + self.container_resolvers = self.__build_container_resolvers(app_info) - def container_descriptions_for_requirements(self, requirements): - # Return lists of containers that would match requirements... - return [] + def __build_container_resolvers( self, app_info ): + conf_file = getattr(app_info, 'containers_resolvers_config_file', None) + if not conf_file: + return self.__default_containers_resolvers() + if not os.path.exists( conf_file ): + log.debug( "Unable to find config file '%s'", conf_file) + return self.__default_containers_resolvers() + plugin_source = plugin_config.plugin_source_from_path( conf_file ) + return self.__parse_resolver_conf_xml( plugin_source ) + + def __parse_resolver_conf_xml(self, plugin_source): + extra_kwds = {} + return plugin_config.load_plugins(self.resolver_classes, plugin_source, extra_kwds) + + def __default_containers_resolvers(self): + default_resolvers = [ + ExplicitContainerResolver(self.app_info), + ] + if self.enable_beta_mulled_containers: + default_resolvers.extend([ + CachedMulledContainerResolver(self.app_info), + MulledContainerResolver(self.app_info, namespace="mulled"), + BuildMulledContainerResolver(self.app_info), + ]) + return default_resolvers + + def __resolvers_dict( self ): + import galaxy.tools.deps.container_resolvers + return plugin_config.plugins_dict( galaxy.tools.deps.container_resolvers, 'resolver_type' ) + + def find_best_container_description(self, enabled_container_types, tool_info): + """Yield best container description of supplied types matching tool info.""" + for container_resolver in self.container_resolvers: + container_description = container_resolver.resolve(enabled_container_types, tool_info) + log.info("Checking with container resolver [%s] found description [%s]" % (container_resolver, container_description)) + if container_description: + assert container_description.type in enabled_container_types + return container_description + + return None class AppInfo(object): @@ -193,7 +239,11 @@ class AppInfo(object): default_file_path=None, outputs_to_working_directory=False, container_image_cache_path=None, - library_import_dir=None + library_import_dir=None, + enable_beta_mulled_containers=False, + containers_resolvers_config_file=None, + involucro_path=None, + involucro_auto_init=True, ): self.galaxy_root_dir = galaxy_root_dir self.default_file_path = default_file_path @@ -201,6 +251,10 @@ class AppInfo(object): self.outputs_to_working_directory = outputs_to_working_directory self.container_image_cache_path = container_image_cache_path self.library_import_dir = library_import_dir + self.enable_beta_mulled_containers = enable_beta_mulled_containers + self.containers_resolvers_config_file = containers_resolvers_config_file + self.involucro_path = involucro_path + self.involucro_auto_init = involucro_auto_init class ToolInfo(object): diff --git a/lib/galaxy/tools/deps/installable.py b/lib/galaxy/tools/deps/installable.py new file mode 100644 index 00000000000..cacc069fe65 --- /dev/null +++ b/lib/galaxy/tools/deps/installable.py @@ -0,0 +1,77 @@ +"""Abstractions for installing local software managed and required by Galaxy/galaxy-lib.""" + +import logging +import os + +from abc import ( + ABCMeta, + abstractmethod, + abstractproperty, +) + +from galaxy.util.filelock import ( + FileLock, + FileLockException +) + +log = logging.getLogger(__name__) + + +class InstallableContext(object): + """Represent a directory/configuration of something that can be installed.""" + + __metaclass__ = ABCMeta + + @abstractmethod + def is_installed(self): + """Return bool indicating if the configured software is installed.""" + + @abstractmethod + def can_install(self): + """Check preconditions for installation.""" + + @abstractproperty + def installable_description(self): + """Short description of thing being installed for log statements.""" + + @abstractproperty + def parent_path(self): + """Return parent path of the location the installable will be created within.""" + + +def ensure_installed(installable_context, install_func, auto_init): + """Make sure target is installed - handle multiple processes potentially attempting installation.""" + parent_path = installable_context.parent_path + desc = installable_context.installable_description + + def _check(): + if not installable_context.is_installed(): + if auto_init: + if installable_context.can_install(): + if install_func(installable_context): + installed = False + log.warning("%s installation requested and failed." % desc) + else: + installed = installable_context.is_installed() + if not installed: + log.warning("%s installation requested, seemed to succeed, but not found." % desc) + else: + installed = False + else: + installed = False + log.warning("%s not installed and auto-installation disabled.", desc) + else: + installed = True + return installed + + if not os.path.exists(parent_path): + os.mkdir(parent_path) + + try: + if auto_init and os.access(parent_path, os.W_OK): + with FileLock(os.path.join(parent_path, desc.lower())): + return _check() + else: + return _check() + except FileLockException: + return ensure_installed(installable_context, auto_init) diff --git a/lib/galaxy/tools/deps/mulled/__init__.py b/lib/galaxy/tools/deps/mulled/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/galaxy/tools/deps/mulled/_cli.py b/lib/galaxy/tools/deps/mulled/_cli.py new file mode 100644 index 00000000000..3cf32bad5de --- /dev/null +++ b/lib/galaxy/tools/deps/mulled/_cli.py @@ -0,0 +1,19 @@ +"""CLI helpers for mulled command-line tools.""" + +import argparse + + +def arg_parser(argv, globals): + """Build an argparser for this CLI tool.""" + doc = globals["__doc__"] + description, epilog = doc.split("\n", 1) + parser = argparse.ArgumentParser( + description=description, + epilog=epilog, + formatter_class=argparse.RawTextHelpFormatter, + ) + return parser + +__all__ = [ + "arg_parser" +] diff --git a/lib/galaxy/tools/deps/mulled/invfile.lua b/lib/galaxy/tools/deps/mulled/invfile.lua new file mode 100644 index 00000000000..629c15f7b67 --- /dev/null +++ b/lib/galaxy/tools/deps/mulled/invfile.lua @@ -0,0 +1,67 @@ +-- http://stackoverflow.com/questions/19262761/lua-need-to-split-at-comma/19263313#19263313 +function string:split( inSplitPattern, outResults ) + if not outResults then + outResults = { } + end + local theStart = 1 + local theSplitStart, theSplitEnd = string.find( self, inSplitPattern, theStart ) + while theSplitStart do + table.insert( outResults, string.sub( self, theStart, theSplitStart-1 ) ) + theStart = theSplitEnd + 1 + theSplitStart, theSplitEnd = string.find( self, inSplitPattern, theStart ) + end + table.insert( outResults, string.sub( self, theStart ) ) + return outResults +end + +local repo = VAR.REPO + +local channel_args = '' +local channels = VAR.CHANNELS:split(",") +for i = 1, #channels do + channel_args = channel_args .. " -c " .. channels[i] +end + +local target_args = '' +local targets = VAR.TARGETS:split(",") +for i = 1, #targets do + target_args = target_args .. " " .. targets[i] +end + +local bind_args = {} +local binds_table = VAR.BINDS:split(",") +for i = 1, #binds_table do + table.insert(bind_args, binds_table[i]) +end + +inv.task('build') + .using('continuumio/miniconda:latest') + .withHostConfig({binds = {"build:/data"}}) + .run('rm', '-rf', '/data/dist') + .using('continuumio/miniconda:latest') + .withHostConfig({binds = bind_args}) + .run('/bin/sh', '-c', 'conda install ' + .. channel_args .. ' ' + .. target_args + .. ' -p /usr/local --copy --yes') + .wrap('build/dist') + .at('/usr/local') + .inImage('bgruening/busybox-bash:0.1') + .as(repo) + +inv.task('test') + .using(repo) + .withConfig({entrypoint = {'/bin/sh', '-c'}}) + .run(VAR.TEST) + +inv.task('push') + .push(repo) + +inv.task('build-and-test') + .runTask('build') + .runTask('test') + +inv.task('all') + .runTask('build') + .runTask('test') + .runTask('push') diff --git a/lib/galaxy/tools/deps/mulled/mulled_build.py b/lib/galaxy/tools/deps/mulled/mulled_build.py new file mode 100644 index 00000000000..b359c540cbf --- /dev/null +++ b/lib/galaxy/tools/deps/mulled/mulled_build.py @@ -0,0 +1,284 @@ +#!/usr/bin/env python +"""Build a mulled image for specified conda targets. + +Examples + +Build a mulled image with: + + mulled-build build 'samtools=1.3.1,bedtools=2.22' + +""" +from __future__ import print_function + +import json +import os +import string +import subprocess + +from sys import platform as _platform + +from galaxy.tools.deps import commands +from galaxy.tools.deps import installable + +try: + import yaml +except ImportError: + yaml = None + +from ._cli import arg_parser +from .util import build_target, conda_build_target_str, image_name +from ..conda_compat import MetaData + +DIRNAME = os.path.dirname(__file__) +DEFAULT_CHANNEL = "bioconda" +DEFAULT_EXTRA_CHANNELS = ["conda-forge", "r"] +DEFAULT_CHANNELS = [DEFAULT_CHANNEL] + DEFAULT_EXTRA_CHANNELS +DEFAULT_REPOSITORY_TEMPLATE = "quay.io/${namespace}/${image}" +DEFAULT_BINDS = ["build/dist:/usr/local/"] +IS_OS_X = _platform == "darwin" +INVOLUCRO_VERSION = "1.1.2" + + +def involucro_link(): + if IS_OS_X: + url = "https://github.com/involucro/involucro/releases/download/v%s/involucro.darwin" % INVOLUCRO_VERSION + else: + url = "https://github.com/involucro/involucro/releases/download/v%s/involucro" % INVOLUCRO_VERSION + return url + + +def get_tests(args, pkg_path): + """Extract test cases given a recipe's meta.yaml file.""" + recipes_dir = args.recipes_dir + + tests = "" + input_dir = os.path.dirname(os.path.join(recipes_dir, pkg_path)) + recipe_meta = MetaData(input_dir) + + tests_commands = recipe_meta.get_value('test/commands') + tests_imports = recipe_meta.get_value('test/imports') + requirements = recipe_meta.get_value('requirements/run') + + if tests_imports or tests_commands: + if tests_commands: + tests = ' && '.join(tests_commands) + elif tests_imports and 'python' in requirements: + tests = ' && '.join('python -c "import %s"' % imp for imp in tests_imports) + elif tests_imports and ('perl' in requirements or 'perl-threaded' in requirements): + tests = ' && '.join('''perl -e "use %s;"''' % imp for imp in tests_imports) + tests = tests.replace('$R ', 'Rscript ') + else: + pass + return tests + + +def get_pkg_name(args, pkg_path): + """Extract the package name from a given meta.yaml file.""" + recipes_dir = args.recipes_dir + + input_dir = os.path.dirname(os.path.join(recipes_dir, pkg_path)) + recipe_meta = MetaData(input_dir) + return recipe_meta.get_value('package/name') + + +def get_affected_packages(args): + """Return a list of all meta.yaml file that where modified/created recently. + + Length of time to check for indicated by the ``hours`` parameter. + """ + recipes_dir = args.recipes_dir + hours = args.diff_hours + cmd = """cd '%s' && git log --diff-filter=ACMRTUXB --name-only --pretty="" --since="%s hours ago" | grep -E '^recipes/.*/meta.yaml' | sort | uniq""" % (recipes_dir, hours) + pkg_list = check_output(cmd, shell=True) + ret = list() + for pkg in pkg_list.strip().split('\n'): + if pkg and os.path.exists(os.path.join( recipes_dir, pkg )): + ret.append( (get_pkg_name(args, pkg), get_tests(args, pkg)) ) + return ret + + +def check_output(cmd, shell=True): + return subprocess.check_output(cmd, shell=shell) + + +def conda_versions(pkg_name, file_name): + """Return all conda version strings for a specified package name.""" + j = json.load(open(file_name)) + ret = list() + for pkg in j['packages'].values(): + if pkg['name'] == pkg_name: + ret.append('%s--%s' % (pkg['version'], pkg['build'])) + return ret + + +def mull_targets( + targets, involucro_context=None, + command="build", channels=DEFAULT_CHANNELS, namespace="mulled", + test='true', image_build=None, name_override=None, + repository_template=DEFAULT_REPOSITORY_TEMPLATE, dry_run=False, + binds=DEFAULT_BINDS +): + if involucro_context is None: + involucro_context = InvolucroContext() + + repo_template_kwds = { + "namespace": namespace, + "image": image_name(targets, image_build=image_build, name_override=name_override) + } + repo = string.Template(repository_template).safe_substitute(repo_template_kwds) + + for channel in channels: + if channel.startswith('file://'): + bind_path = channel.lstrip('file://') + binds.append('/%s:/%s' % (bind_path, bind_path)) + + channels = ",".join(channels) + target_str = ",".join(map(conda_build_target_str, targets)) + bind_str = ",".join(binds) + involucro_args = [ + '-f', '%s/invfile.lua' % DIRNAME, + '-set', "CHANNELS='%s'" % channels, + '-set', "TEST='%s'" % test, + '-set', "TARGETS='%s'" % target_str, + '-set', "REPO='%s'" % repo, + '-set', "BINDS='%s'" % bind_str, + command, + ] + print(" ".join(involucro_context.build_command(involucro_args))) + if not dry_run: + ensure_installed(involucro_context, True) + involucro_context.exec_command(involucro_args) + + +def context_from_args(args): + return InvolucroContext(involucro_bin=args.involucro_path) + + +class InvolucroContext(installable.InstallableContext): + + installable_description = "Involucro" + + def __init__(self, involucro_bin=None, shell_exec=None, verbose="3"): + if involucro_bin is None: + if os.path.exists("./involucro"): + self.involucro_bin = "./involucro" + else: + self.involucro_bin = "involucro" + else: + self.involucro_bin = involucro_bin + self.shell_exec = shell_exec or commands.shell + self.verbose = verbose + + def build_command(self, involucro_args): + return [self.involucro_bin, "-v=%s" % self.verbose] + involucro_args + + def exec_command(self, involucro_args): + cmd = self.build_command(involucro_args) + return self.shell_exec(" ".join(cmd)) + + def is_installed(self): + return os.path.exists(self.involucro_bin) + + def can_install(self): + return True + + @property + def parent_path(self): + return os.path.dirname(os.path.abspath(self.involucro_bin)) + + +def ensure_installed(involucro_context, auto_init): + return installable.ensure_installed(involucro_context, install_involucro, auto_init) + + +def install_involucro(involucro_context=None, to_path=None): + to_path = involucro_context.involucro_bin + download_cmd = " ".join(commands.download_command(involucro_link(), to=to_path, quote_url=True)) + full_cmd = "%s && chmod +x %s" % (download_cmd, to_path) + return involucro_context.shell_exec(full_cmd) + + +def add_build_arguments(parser): + """Base arguments describing how to 'mull'.""" + parser.add_argument('--involucro-path', dest="involucro_path", default=None, + help="Path to involucro (if not set will look in working directory and on PATH).") + parser.add_argument('--force-rebuild', dest="force_rebuild", action="store_true", + help="Rebuild package even if already published.") + parser.add_argument('--dry-run', dest='dry_run', action="store_true", + help='Just print commands instead of executing them.') + parser.add_argument('-n', '--namespace', dest='namespace', default="mulled", + help='quay.io namespace.') + parser.add_argument('-r', '--repository_template', dest='repository_template', default=DEFAULT_REPOSITORY_TEMPLATE, + help='Docker repository target for publication (only quay.io or compat. API is currently supported).') + parser.add_argument('-c', '--channel', dest='channel', default=DEFAULT_CHANNEL, + help='Target conda channel') + parser.add_argument('--extra-channels', dest='extra_channels', default=",".join(DEFAULT_EXTRA_CHANNELS), + help='Dependent conda channels.') + + +def add_single_image_arguments(parser): + parser.add_argument("--name-override", dest="name_override", default=None, + help="Override mulled image name - this is not recommended since metadata will not be detectable from the name of resulting images") + parser.add_argument("--image-build", dest="image_build", default=None, + help="Build a versioned variant of this image.") + + +def target_str_to_targets(targets_raw): + def parse_target(target_str): + if "=" in target_str: + package_name, version = target_str.split("=", 1) + target = build_target(package_name, version) + else: + target = build_target(target_str) + return target + + targets = map(parse_target, targets_raw.split(",")) + return targets + + +def args_to_mull_targets_kwds(args): + kwds = {} + if hasattr(args, "image_build"): + kwds["image_build"] = args.image_build + if hasattr(args, "name_override"): + kwds["name_override"] = args.name_override + if hasattr(args, "namespace"): + kwds["namespace"] = args.namespace + if hasattr(args, "dry_run"): + kwds["dry_run"] = args.dry_run + if hasattr(args, "test"): + kwds["test"] = args.test + if hasattr(args, "channel"): + channels = [args.channel] + if hasattr(args, "extra_channels"): + channels += args.extra_channels.split(",") + kwds["channels"] = channels + if hasattr(args, "command"): + kwds["command"] = args.command + if hasattr(args, "repository_template"): + kwds["repository_template"] = args.repository_template + + kwds["involucro_context"] = context_from_args(args) + + return kwds + + +def main(argv=None): + """Main entry-point for the CLI tool.""" + parser = arg_parser(argv, globals()) + add_build_arguments(parser) + add_single_image_arguments(parser) + parser.add_argument('command', metavar='COMMAND', help='Command (build-and-test, build, all)') + parser.add_argument('targets', metavar="TARGETS", default=None, help="Build a single container with specific package(s).") + parser.add_argument('--repository-name', dest="repository_name", default=None, help="Name of mulled container (leave blank to auto-generate based on packages - recommended).") + parser.add_argument('--test', help='Provide a test command for the container.') + args = parser.parse_args() + targets = target_str_to_targets(args.targets) + mull_targets(targets, **args_to_mull_targets_kwds(args)) + + +__all__ = ["main"] + + +if __name__ == '__main__': + main() diff --git a/lib/galaxy/tools/deps/mulled/mulled_build_channel.py b/lib/galaxy/tools/deps/mulled/mulled_build_channel.py new file mode 100644 index 00000000000..b5082224649 --- /dev/null +++ b/lib/galaxy/tools/deps/mulled/mulled_build_channel.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python +"""Build a mulled images for all recent conda recipe updates that don't have existing images. + +Examples: + +Build mulled images for recent bioconda changes with: + + mulled-build-channel build + +Build, test, and publish images with the follow command: + + mulled-build-channel all + +See recent changes that would be built with: + + mulled-build-channel list + +""" + + +import os +import time + +from ._cli import arg_parser +from .mulled_build import ( + add_build_arguments, + args_to_mull_targets_kwds, + build_target, + check_output, + conda_versions, + get_affected_packages, + mull_targets, +) +from .util import quay_versions, version_sorted + + +def _fetch_repo_data(args): + repo_data = args.repo_data + channel = args.channel + if repo_data is None: + repo_data = "%s-repodata.json" % channel + if not os.path.exists(repo_data): + check_output("wget --quiet https://conda.anaconda.org/%s/linux-64/repodata.json.bz2 -O '%s.bz2' && bzip2 -d '%s.bz2'" % (channel, repo_data, repo_data)) + return repo_data + + +def _new_versions(quay, conda): + """Calculate the versions that are in conda but not on quay.io.""" + sconda = set(conda) + squay = set(quay) if quay else set() + return sconda - squay # sconda.symmetric_difference(squay) + + +def run_channel(args, build_last_n_versions=1): + """Build list of involucro commands (as shell snippet) to run.""" + pkgs = get_affected_packages(args) + for pkg_name, pkg_tests in pkgs: + repo_data = _fetch_repo_data(args) + c = conda_versions(pkg_name, repo_data) + # only package the most recent N versions + c = version_sorted(c)[:build_last_n_versions] + + if not args.force_rebuild: + time.sleep(1) + q = quay_versions(args.namespace, pkg_name) + versions = _new_versions(q, c) + else: + versions = c + + for tag in versions: + target = build_target(pkg_name, tag=tag) + targets = [target] + mull_targets(targets, test=pkg_tests, **args_to_mull_targets_kwds(args)) + + +def get_pkg_names(args): + """Print package names that would be affected.""" + print('\n'.join([pkg_name for pkg_name, pkg_tests in get_affected_packages(args)])) + + +def add_channel_arguments(parser): + """Add arguments only used if running mulled over a whole conda channel.""" + parser.add_argument('--repo-data', dest='repo_data', default=None, + help='Published repository data (will be fetched from --channel if not available and written). Defaults to [channel_name]-repodata.json.') + parser.add_argument('--diff-hours', dest='diff_hours', default="25", + help='If finding all recently changed recipes, use this number of hours.') + parser.add_argument('--recipes-dir', dest="recipes_dir", default="./bioconda-recipes") + + +def main(argv=None): + """Main entry-point for the CLI tool.""" + parser = arg_parser(argv, globals()) + add_channel_arguments(parser) + add_build_arguments(parser) + parser.add_argument('command', metavar='COMMAND', help='Command (list, build-and-test, build, all)') + parser.add_argument('--targets', dest="targets", default=None, help="Build a single container with specific package(s).") + parser.add_argument('--repository-name', dest="repository_name", default=None, help="Name of a single container (leave blank to auto-generate based on packages).") + args = parser.parse_args() + if args.command == "list": + get_pkg_names(args) + else: + run_channel(args) + + +__all__ = ["main"] + + +if __name__ == '__main__': + main() diff --git a/lib/galaxy/tools/deps/mulled/mulled_build_files.py b/lib/galaxy/tools/deps/mulled/mulled_build_files.py new file mode 100644 index 00000000000..0fd065c504d --- /dev/null +++ b/lib/galaxy/tools/deps/mulled/mulled_build_files.py @@ -0,0 +1,80 @@ +"""Build all composite mulled recipes discovered in TSV files. + +Use mulled-build-channel to build images for single recipes for a whole conda +channel. This script instead builds images for combinations of recipes. This +script can be given a single TSV file or a directory of TSV files to process. + +Examples: + +Build all recipes discovered in tsv files in a single directory. + + mulled-build-files build + +""" + +import collections +import glob +import os + +from ._cli import arg_parser + +from .mulled_build import ( + add_build_arguments, + args_to_mull_targets_kwds, + mull_targets, + target_str_to_targets, +) + + +def main(argv=None): + """Main entry-point for the CLI tool.""" + parser = arg_parser(argv, globals()) + add_build_arguments(parser) + parser.add_argument('command', metavar='COMMAND', help='Command (build-and-test, build, all)') + parser.add_argument('files', metavar="FILES", default=".", + help="Path to directory (or single file) of TSV files describing composite recipes.") + args = parser.parse_args() + for targets in generate_targets(args.files): + mull_targets(targets, **args_to_mull_targets_kwds(args)) + + +def generate_targets(target_source): + """Generate all targets from TSV files in specified file or directory.""" + target_source = os.path.abspath(target_source) + if os.path.isdir(target_source): + target_source_files = glob.glob(target_source + "/*.tsv") + else: + target_source_files = [target_source] + + for target_source_file in target_source_files: + with open(target_source_file, "r") as f: + for line in f.readlines(): + if line: + line = line.strip() + + if not line or line.startswith("#"): + continue + + yield line_to_targets(line) + + +def line_to_targets(line_str): + line = _parse_line(line_str) + return target_str_to_targets(line) + + +_Line = collections.namedtuple("_Line", ["targets", "image_build", "name_override"]) + + +def _parse_line(line_str): + line_parts = line_str.split(" ") + assert len(line_parts) < 3, "Too many fields in line [%s], expect at most 3 - targets, image build number, and name override." % line_str + line_parts += [None] * (3 - len(line_parts)) + return _Line(*line_parts) + + +__all__ = ["main"] + + +if __name__ == '__main__': + main() diff --git a/lib/galaxy/tools/deps/mulled/mulled_build_tool.py b/lib/galaxy/tools/deps/mulled/mulled_build_tool.py new file mode 100644 index 00000000000..7281c7940ae --- /dev/null +++ b/lib/galaxy/tools/deps/mulled/mulled_build_tool.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python +"""Build a mulled images for a tool source (Galaxy or CWL tool). + +Examples: + +Build mulled images for requirements defined in a tool: + + mulled-build-tool build path/to/tool_file.xml + +""" + +from galaxy.tools.parser import get_tool_source + +from ._cli import arg_parser +from .mulled_build import ( + add_build_arguments, + add_single_image_arguments, + args_to_mull_targets_kwds, + build_target, + mull_targets, +) + + +def main(argv=None): + """Main entry-point for the CLI tool.""" + parser = arg_parser(argv, globals()) + add_build_arguments(parser) + add_single_image_arguments(parser) + parser.add_argument('command', metavar='COMMAND', help='Command (build-and-test, build, all)') + parser.add_argument('tool', metavar="TOOL", default=None, help="Path to tool to build mulled image for.") + args = parser.parse_args() + tool_source = get_tool_source(args.tool) + requirements, _ = tool_source.parse_requirements_and_containers() + targets = requirements_to_mulled_targets(requirements) + mull_targets(targets, **args_to_mull_targets_kwds(args)) + + +def requirements_to_mulled_targets(requirements): + """Convert Galaxy's representation of requirements into mulled Target objects. + + Only package requirements are retained. + """ + package_requirements = filter(lambda r: r.type == "package", requirements) + targets = map(lambda r: build_target(r.name, r.version), package_requirements) + return targets + + +__all__ = ["main", "requirements_to_mulled_targets"] + + +if __name__ == '__main__': + main() diff --git a/lib/galaxy/tools/deps/mulled/mulled_search.py b/lib/galaxy/tools/deps/mulled/mulled_search.py new file mode 100644 index 00000000000..724956a1335 --- /dev/null +++ b/lib/galaxy/tools/deps/mulled/mulled_search.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python + +import argparse +import json +import sys +import tempfile + +try: + import requests +except ImportError: + requests = None + +try: + from whoosh.fields import Schema + from whoosh.fields import TEXT + from whoosh.fields import STORED + from whoosh.index import create_in + from whoosh.qparser import QueryParser +except ImportError: + Schema = TEXT = STORED = create_in = QueryParser = None + +QUAY_API_URL = 'https://quay.io/api/v1/repository' + + +class QuaySearch(): + """ + Tool to search within a quay organization for a given software name. + """ + def __init__(self, organization): + self.index = None + self.organization = organization + + def build_index(self): + """ + Create an index to quickly examine the repositories of a given quay.io organization. + """ + # download all information about the repositories from the + # given organization in self.organization + + parameters = {'public': 'true', 'namespace': self.organization} + r = requests.get(QUAY_API_URL, headers={'Accept-encoding': 'gzip'}, params=parameters, + timeout=12) + + tmp_dir = tempfile.mkdtemp() + schema = Schema(title=TEXT(stored=True), content=STORED) + self.index = create_in(tmp_dir, schema) + + json_decoder = json.JSONDecoder() + decoded_request = json_decoder.decode(r.text) + writer = self.index.writer() + for repository in decoded_request['repositories']: + writer.add_document(title=repository['name'], content=repository['description']) + writer.commit() + + def search_repository(self, search_string, non_strict): + """ + Search Docker containers on quay.io. + Results are displayed with all available versions, + including the complete image name. + """ + # with statement closes searcher after usage. + with self.index.searcher() as searcher: + search_string = "*%s*" % search_string + query = QueryParser("title", self.index.schema).parse(search_string) + results = searcher.search(query) + if non_strict: + # look for spelling errors and use suggestions as a search term too + corrector = searcher.corrector("title") + suggestions = corrector.suggest(search_string, limit=2) + + # get all repositories with suggested keywords + for suggestion in suggestions: + search_string = "*%s*" % suggestion + query = QueryParser("title", self.index.schema).parse(search_string) + results_tmp = searcher.search(query) + results.extend(results_tmp) + + sys.stdout.write("The query \033[1m %s \033[0m resulted in %s result(s).\n" % (search_string, len(results))) + + if non_strict: + sys.stdout.write('The search was relaxed and the following search terms were searched: ') + sys.stdout.write('\033[1m %s \033[0m\n' % ', '.join(suggestions)) + + out = list() + for result in results: + title = result['title'] + for version in self.get_additional_repository_information(title): + row = [title] + row.append(version) + out.append(row) + if out: + col_width = max(len(word) for row in out for word in row) + 2 # padding + for row in out: + name = row[0] + version = row[1] + sys.stdout.write("".join(word.ljust(col_width) for word in row) + "docker pull quay.io/%s/%s:%s\n" % (self.organization, name, version)) + else: + sys.stdout.write("No results found for %s in quay.io/%s.\n" % (search_string, self.organization)) + + def get_additional_repository_information(self, repository_string): + """ + Function downloads additional information from quay.io to + get the tag-field which includes the version number. + """ + url = "%s/%s/%s" % (QUAY_API_URL, self.organization, repository_string) + r = requests.get(url, headers={'Accept-encoding': 'gzip'}, timeout=12) + + json_decoder = json.JSONDecoder() + decoded_request = json_decoder.decode(r.text) + return decoded_request['tags'] + + +def main(argv=None): + parser = argparse.ArgumentParser(description='Searches in a given quay organization for a repository') + parser.add_argument('-o', '--organization', dest='organization_string', default="mulled", + help='Change organization. Default is mulled.') + parser.add_argument('--non-strict', dest='non_strict', action="store_true", + help='Autocorrection of typos activated. Lists more results but can be confusing.\ + For too many queries quay.io blocks the request and the results can be incomplete.') + parser.add_argument('-s', '--search', required=True, + help='The name of the tool you want to search for.') + args = parser.parse_args() + + quay = QuaySearch(args.organization_string) + quay.build_index() + + quay.search_repository(args.search, args.non_strict) + + +if __name__ == "__main__": + main() diff --git a/lib/galaxy/tools/deps/mulled/util.py b/lib/galaxy/tools/deps/mulled/util.py new file mode 100644 index 00000000000..4fc5312afbe --- /dev/null +++ b/lib/galaxy/tools/deps/mulled/util.py @@ -0,0 +1,116 @@ +"""Utilities for working with mulled abstractions outside the mulled package.""" +from __future__ import print_function + +import collections +import hashlib + +from distutils.version import LooseVersion + +try: + import requests +except ImportError: + requests = None + + +def quay_versions(namespace, pkg_name): + """Get all version tags for a Docker image stored on quay.io for supplied package name.""" + if requests is None: + raise Exception("requets library is unavailable, functionality not available.") + + assert namespace is not None + assert pkg_name is not None + url = 'https://quay.io/api/v1/repository/%s/%s' % (namespace, pkg_name) + response = requests.get(url, timeout=None) + data = response.json() + if 'error_type' in data and data['error_type'] == "invalid_token": + return [] + + if 'tags' not in data: + raise Exception("Unexpected response from quay.io - not tags description found [%s]" % data) + + return [tag for tag in data['tags'] if tag != 'latest'] + + +def mulled_tags_for(namespace, image): + """Fetch remote tags available for supplied image name. + + The result will be sorted so newest tags are first. + """ + tags = quay_versions(namespace, image) + tags = version_sorted(tags) + return tags + + +def split_tag(tag): + """Split mulled image name into conda version and conda build.""" + version = tag.split('--', 1)[0] + build = tag.split('--', 1)[1] + return version, build + + +def version_sorted(elements): + """Sort iterable based on loose description of "version" from newest to oldest.""" + return sorted(elements, key=LooseVersion, reverse=True) + + +Target = collections.namedtuple("Target", ["package_name", "version", "build"]) + + +def build_target(package_name, version=None, build=None, tag=None): + """Use supplied arguments to build a :class:`Target` object.""" + if tag is not None: + assert version is None + assert build is None + version, build = split_tag(tag) + + return Target(package_name, version, build) + + +def conda_build_target_str(target): + rval = target.package_name + if target.version: + rval += "=%s" % target.version + + if target.build: + rval += "=%s" % target.build + + return rval + + +def image_name(targets, image_build=None, name_override=None): + if name_override is not None: + print("WARNING: Overriding mulled image name, auto-detection of 'mulled' package attributes will fail to detect result.") + return name_override + + if len(targets) == 1: + target = targets[0] + suffix = "" + if target.version is not None: + if image_build is not None: + print("WARNING: Hard-coding image build instead of using Conda build - this is not recommended.") + suffix = image_build + else: + suffix += ":%s" % target.version + build = target.build + if build is not None: + suffix += "--%s" % build + return "%s%s" % (target.package_name, suffix) + else: + targets_order = sorted(targets, key=lambda t: t.package_name) + requirements_buffer = "\n".join(map(conda_build_target_str, targets_order)) + m = hashlib.sha1() + m.update(requirements_buffer) + suffix = "" if not image_build else ":%s" % image_build + return "mulled-v1-%s%s" % (m.hexdigest(), suffix) + + +__all__ = [ + "build_target", + "conda_build_target_str", + "image_name", + "mulled_tags_for", + "quay_versions", + "split_tag", + "Target", + "version_sorted", +] diff --git a/lib/galaxy/tools/deps/resolvers/__init__.py b/lib/galaxy/tools/deps/resolvers/__init__.py index 2ea5abeaa4f..6165fdb6af8 100644 --- a/lib/galaxy/tools/deps/resolvers/__init__.py +++ b/lib/galaxy/tools/deps/resolvers/__init__.py @@ -1,3 +1,4 @@ +"""The module defines the abstract interface for dealing tool dependency resolution plugins.""" from abc import ( ABCMeta, abstractmethod, @@ -10,6 +11,8 @@ from ..requirements import ToolRequirement class DependencyResolver(Dictifiable, object): + """Abstract description of a technique for resolving container images for tool execution.""" + # Keys for dictification. dict_collection_visible_keys = ['resolver_type', 'resolves_simple_dependencies'] # A "simple" dependency is one that does not depend on the the tool @@ -23,13 +26,15 @@ class DependencyResolver(Dictifiable, object): @abstractmethod def resolve( self, name, version, type, **kwds ): - """ - Given inputs describing dependency in the abstract, yield tuple of - (script, bin, version). Here script is the env.sh file to source - before running a job, if that is not found the bin directory will be - appended to the path (if it is not None). Finally, version is the - resolved tool dependency version (which may differ from requested - version for instance if the request version is 'default'.) + """Given inputs describing dependency in the abstract yield a Dependency object. + + The Dependency object describes various attributes (script, bin, + version) used to build scripts with the dependency availble. Here + script is the env.sh file to source before running a job, if that is + not found the bin directory will be appended to the path (if it is + not ``None``). Finally, version is the resolved tool dependency + version (which may differ from requested version for instance if the + request version is 'default'.) """ def _get_config_option(self, key, dependency_resolver, default=None, config_prefix=None, **kwds): diff --git a/lib/galaxy/tools/deps/resolvers/conda.py b/lib/galaxy/tools/deps/resolvers/conda.py index 3b3d85d16f9..f16734a268e 100644 --- a/lib/galaxy/tools/deps/resolvers/conda.py +++ b/lib/galaxy/tools/deps/resolvers/conda.py @@ -6,10 +6,8 @@ incompatible changes coming. import logging import os -from galaxy.util.filelock import ( - FileLock, - FileLockException -) +import galaxy.tools.deps.installable + from ..conda_util import ( build_isolated_environment, cleanup_failed_install, @@ -32,7 +30,7 @@ from ..resolvers import ( DEFAULT_BASE_PATH_DIRECTORY = "_conda" DEFAULT_CONDARC_OVERRIDE = "_condarc" -DEFAULT_ENSURE_CHANNELS = "r,bioconda,iuc" +DEFAULT_ENSURE_CHANNELS = "conda-forge,r,bioconda,iuc" log = logging.getLogger(__name__) @@ -95,44 +93,11 @@ class CondaDependencyResolver(DependencyResolver, ListableDependencyResolver, In copy_dependencies = _string_as_bool(get_option("copy_dependencies")) self.auto_init = _string_as_bool(get_option("auto_init")) self.conda_context = conda_context - self.ensure_conda_installed() + self.disabled = not galaxy.tools.deps.installable.ensure_installed(conda_context, install_conda, self.auto_init) self.auto_install = auto_install self.copy_dependencies = copy_dependencies self.verbose_install_check = verbose_install_check - def ensure_conda_installed(self): - """ - Make sure that conda is installed, and if conda can't be installed, mark resolver as disabled. - We acquire a lock, so that multiple handlers do not attempt to install conda simultaneously. - """ - target_path = self.conda_prefix_parent - - def _check(): - if not self.conda_context.is_conda_installed(): - if self.auto_init: - if self.conda_context.can_install_conda(): - if install_conda(self.conda_context): - self.disabled = True - log.warning("Conda installation requested and failed.") - else: - self.disabled = True - else: - self.disabled = True - log.warning("Conda not installed and auto-installation disabled.") - else: - self.disabled = False - - if not os.path.exists(target_path): - os.mkdir(target_path) - try: - if self.auto_init and os.access(target_path, os.W_OK): - with FileLock(os.path.join(target_path, 'conda')): - _check() - else: - _check() - except FileLockException: - self.ensure_conda_installed() - def resolve(self, name, version, type, **kwds): # Check for conda just not being there, this way we can enable # conda by default and just do nothing in not configured. diff --git a/test/functional/tools/mulled_example_multi_1.xml b/test/functional/tools/mulled_example_multi_1.xml new file mode 100644 index 00000000000..1f025598c04 --- /dev/null +++ b/test/functional/tools/mulled_example_multi_1.xml @@ -0,0 +1,18 @@ + + $out_file1 ; + echo "Moo" >> $out_file1 ; + samtools >> $out_file1 2>&1 ; + echo "Cow" >> $out_file1 ; + ]]> + + samtools + bedtools + + + + + + + + diff --git a/test/functional/tools/samples_tool_conf.xml b/test/functional/tools/samples_tool_conf.xml index d57356e25d9..90d64f86c37 100644 --- a/test/functional/tools/samples_tool_conf.xml +++ b/test/functional/tools/samples_tool_conf.xml @@ -111,6 +111,8 @@ + + + + diff --git a/lib/galaxy/jobs/__init__.py b/lib/galaxy/jobs/__init__.py index 0ab674dc7c8..9ca65ee0bec 100644 --- a/lib/galaxy/jobs/__init__.py +++ b/lib/galaxy/jobs/__init__.py @@ -803,6 +803,10 @@ class JobWrapper( object ): """ return self.get_destination_configuration("cleanup_job", DEFAULT_CLEANUP_JOB) + @property + def requires_containerization(self): + return util.asbool(self.get_destination_configuration("require_container", "False")) + def can_split( self ): # Should the job handler split this job up? return self.app.config.use_tasked_jobs and self.tool.parallelism diff --git a/lib/galaxy/jobs/runners/__init__.py b/lib/galaxy/jobs/runners/__init__.py index afed2069f1f..17ac05ee726 100644 --- a/lib/galaxy/jobs/runners/__init__.py +++ b/lib/galaxy/jobs/runners/__init__.py @@ -198,6 +198,8 @@ class BaseJobRunner( object ): def build_command_line( self, job_wrapper, include_metadata=False, include_work_dir_outputs=True, modify_command_for_container=True ): container = self._find_container( job_wrapper ) + if not container and job_wrapper.requires_containerization: + raise Exception("Failed to find a container when required, contact Galaxy admin.") return build_command( self, job_wrapper, diff --git a/test/unit/jobs/test_runner_local.py b/test/unit/jobs/test_runner_local.py index 22012258951..840c4eb6b3d 100644 --- a/test/unit/jobs/test_runner_local.py +++ b/test/unit/jobs/test_runner_local.py @@ -105,6 +105,7 @@ class MockJobWrapper( object ): os.makedirs( tool_working_directory ) self.app = app self.tool = tool + self.requires_containerization = False self.state = model.Job.states.QUEUED self.command_line = "echo HelloWorld" self.environment_variables = [] From 654b5e9322f5c54332c8d8861bc8ed34c6e71a8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gr=C3=BCning?= Date: Sun, 9 Oct 2016 12:40:50 +0200 Subject: [PATCH 07/32] Add initial documentation about the new mulled features. --- doc/source/admin/mulled_containers.rst | 128 +++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 doc/source/admin/mulled_containers.rst diff --git a/doc/source/admin/mulled_containers.rst b/doc/source/admin/mulled_containers.rst new file mode 100644 index 00000000000..7c16ba72353 --- /dev/null +++ b/doc/source/admin/mulled_containers.rst @@ -0,0 +1,128 @@ +================================= +Containers for Tool Dependencies +================================= + +Galaxy tools (also called wrappers) are able to use Conda packages +(see more information in our `Galaxy Conda documentation`_) and Docker containers as dependency resolvers. +The IUC_ recommends to use Conda packages as primary dependency resolver, mainly because Docker is not +available on every (HPC-) system. Conda on the other hand can be installed by Galaxy and maintained +entirely in user-space. Nevertheless, Docker (Containers in general) has some uniq features and +there are a bunch of use-cases in the Galaxy community that makes containerized systems very appealing. + +Since 2014 Galaxy supports running tools in Docker containers via a special `container annotation`_ inside of the +requirement field. + +.. code-block:: xml + + + + busybox:ubuntu-14.04 + + gnu_coreutils + + + +This approach has shown two limitations that slowed down the adoption by Tool developers. +First, every tool needs to be annotated with a container name (as shown above) and this container needs +to be created beforehand, usually manually. The second reason is that a Galaxy tool aims to be deployed everywhere, +independet of the underlying system, meaning if Docker is not availble Galaxy should use Conda packages. +This puts an additionl burden to tool developers that needs to take care of two dependency resolvers and can yield to +different results of your tool depending on the resolver, because both the Conda package and the Docker container is +usually not created out of the same recipe and maybe was compiled in a different way, uses different sources etc ... + +Not an ideal solution and something we wanted to solve. + +Here we demonstrate a solution that can create Containers out of Conda packages automatically. +This can be either used to support communities like BioContainers_ to create Containers +before deploying a Galaxy tool, or this can be used by Galaxy to create Containers on-demand and on-the-fly if no one +is available already. + + +Automatic build of linux containers +----------------------------------- + +We utilize [mulled](https://github.com/mulled/mulled) and with this [involucro](https://github.com/involucro/involucro) +in an automatic way. This is for example used to convert all packages in bioconda_ into Linux Containers +(Docker and rkt at the moment) and made available at the `BioContainers Quay.io account`_. + +We have developed small utilities around this technology stack which is currently included in galaxy-lib_. +Here is a short introduction: + +Search for containers +^^^^^^^^^^^^^^^^^^^^^ + +This will search for containers in the biocontainers organisation. + +.. code-block:: bash + + $ mulled-search -s vsearch -o biocontainers + + +Build all packages from bioconda from the last 24h +^^^^^^^^^^^^^^^^^^^^^ + +The BioConda community is building a container for every package they create with a command similar to this. + +.. code-block:: bash + + $ mulled-build-channel --channel bioconda --namespace biocontainers \ + --involucro-path ./involucro --recipes-dir ./bioconda-recipes --diff-hours 25 build + + +Building Docker containers for local Conda packages +^^^^^^^^^^^^^^^^^^^^^ + +Conda packages can be tested with creating a busybox based container for this particular package in the following way. +This also demonstrates how you can build a container locally and on-the-fly. + + > we modified the samtools package to version 3.0 to make clear we are using a local version + +1) build your recipe + +.. code-block:: bash + + $ conda build recipes/samtools + +2) index your local builds + +.. code-block:: bash + + $ conda index /home/bag/miniconda2/conda-bld/linux-64/ + + +3) build a container for your local package + +.. code-block:: bash + + $ mulled-build build-and-test 'samtools=3.0--0' \ + --extra-channel file://home/bag/miniconda2/conda-bld/ --test 'samtools --help' + +The ``--0`` indicates the build version of the conda package. It is recommended to specify this number otherwise +you will override already exsisting images. For python conda packages this extension might look like this ``--py35_1``. + +Build, test and push a conda-forge package to biocontainers +^^^^^^^^^^^^^^^^^^^^^ + + > You need to have write access to the biocontainers repository + +You can build packages from other Conda channels as well, not only from BioConda. ``pandoc`` is available from the +conda-forge channel and conda-forge is also enabled by default in Galaxy. To build ``pandoc`` and push it to biocontainrs +you could do something along these lines. + + +.. code-block:: bash + + $ mulled-build build-and-test 'pandoc=1.17.2--0' --test 'pandoc --help' -n biocontainers + +.. code-block:: bash + + $ mulled-build push 'pandoc=1.17.2--0' --test 'pandoc --help' -n biocontainers + + +.. _Galaxy Conda documentation: ./conda_faq.rst +.. _IUC: https://wiki.galaxyproject.org/IUC +.. _container annotation: https://github.com/galaxyproject/galaxy/blob/dev/test/functional/tools/catDocker.xml#L4 +.. _BioContainers: https://github.com/biocontainers +.. _bioconda: https://github.com/bioconda/bioconda-recipes +.. _BioContainers Quay.io account: https://quay.io/organization/biocontainers +.. _galaxy-lib: https://github.com/galaxyproject/galaxy-lib From ad0d0bc7cec7c281052610b9453db42cfce8dcea Mon Sep 17 00:00:00 2001 From: ramezrawas Date: Mon, 10 Oct 2016 09:52:02 +0200 Subject: [PATCH 08/32] Missing double quotation line 3664: name="output_vcf> --- lib/galaxy/tools/xsd/galaxy.xsd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/galaxy/tools/xsd/galaxy.xsd b/lib/galaxy/tools/xsd/galaxy.xsd index e9c62893ee2..b9c7d00fd3c 100644 --- a/lib/galaxy/tools/xsd/galaxy.xsd +++ b/lib/galaxy/tools/xsd/galaxy.xsd @@ -3661,7 +3661,7 @@ conditionals are accessed using a hash named after the conditional. - options['selection_mode'] == 'advanced' and options['vcf_output'] From fad562876f886e3384fec1a9782685251d12e2c1 Mon Sep 17 00:00:00 2001 From: carlfeberhard Date: Mon, 10 Oct 2016 10:31:29 -0400 Subject: [PATCH 09/32] Datatypes: include base classes in search by name Fixes #2982 Adds the base class 'images.Image' to the list of classes returnable by 'datatypes_registry.get_datatype_class_by_name'. Normally that method searches the registry's list of datatypes and those are instantiable classes only. The images.Image can be considered an abstract base class and you'd never create a dataset of that datatype. The method is used for getting classes for 'isinstance' tests, however, so including base classes like 'images.Image' allow testing for all its image subclasses. This change makes that possible and allows the method (and its consumers) to behave in a much more predictable and useful manner. --- lib/galaxy/datatypes/registry.py | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/lib/galaxy/datatypes/registry.py b/lib/galaxy/datatypes/registry.py index 5bde4a1ecc2..099c2bccf35 100644 --- a/lib/galaxy/datatypes/registry.py +++ b/lib/galaxy/datatypes/registry.py @@ -434,27 +434,18 @@ class Registry( object ): Return the datatype class where the datatype's `type` attribute (as defined in the datatype_conf.xml file) contains `name`. """ - # TODO: too roundabout - would be better to generate this once as a map and store in this object + # TODO: obviously not ideal but some of these base classes that are useful for testing datatypes + # aren't loaded into the datatypes registry, so we'd need to test for them here + if name == 'images.Image': + return images.Image + + # TODO: too inefficient - would be better to generate this once as a map and store in this object for ext, datatype_obj in self.datatypes_by_extension.items(): datatype_obj_class = datatype_obj.__class__ datatype_obj_class_str = str( datatype_obj_class ) if name in datatype_obj_class_str: return datatype_obj_class return None - # these seem to be connected to the dynamic classes being generated in this file, lines 157-158 - # they appear when a one of the three are used in inheritance with subclass="True" - # TODO: a possible solution is to def a fn in datatypes __init__ for creating the dynamic classes - - # remap = { - # 'galaxy.datatypes.registry.Tabular' : galaxy.datatypes.tabular.Tabular, - # 'galaxy.datatypes.registry.Text' : galaxy.datatypes.data.Text, - # 'galaxy.datatypes.registry.Binary' : galaxy.datatypes.binary.Binary - # } - # datatype_str = str( datatype ) - # if datatype_str in remap: - # datatype = remap[ datatype_str ] - # - # return datatype def get_available_tracks( self ): return self.available_tracks From 91bfecea67b78f7e459a250a7844c6344154367f Mon Sep 17 00:00:00 2001 From: John Chilton Date: Fri, 7 Oct 2016 15:56:02 -0400 Subject: [PATCH 10/32] Add documentation on optional ``multiple="true"`` data parameters. Thanks to detailed biostar question from @lindenb (sorry these parameters are so counter-intuitive - I don't have an excuse other than a need to preserve backward compatibility). Rebased with indention requested by @nsoranzo and fixes for the test case tool. --- lib/galaxy/tools/xsd/galaxy.xsd | 27 +++++++++++++++++++ test/functional/tools/multi_data_optional.xml | 27 +++++++++++++++++++ test/functional/tools/samples_tool_conf.xml | 1 + 3 files changed, 55 insertions(+) create mode 100644 test/functional/tools/multi_data_optional.xml diff --git a/lib/galaxy/tools/xsd/galaxy.xsd b/lib/galaxy/tools/xsd/galaxy.xsd index 8d0cecc92c1..978fc769716 100644 --- a/lib/galaxy/tools/xsd/galaxy.xsd +++ b/lib/galaxy/tools/xsd/galaxy.xsd @@ -1990,6 +1990,33 @@ multiple formats. help="The set of aligned reads." /> ``` +Perhaps counter-intuitively, a ``multiple="true"`` data parameter requires at least one +data input. If ``optional="true"`` is specified, this condition is relaxed and the user +is allowed to select 0 datasets. Unfortunately, if 0 datasets are selected the resulting +value for the parameter during Cheetah templating (such as in a ``command`` block) will +effectively be a list with one ``None``-like entity in it. + +The following idiom can be used to iterate over such a list and build a hypothetical ``-B`` +parameter for each file - the ``if`` block is used to handle the case where a ``None``-like +entity appears in the list because no files were selected: + +``` +#for $input in $input1 + #if $input + -B "$input" + #end if +#end for +``` + +Some example tools using ``multiple="true"`` data parameters include: + +- [multi_data_param.xml](https://github.com/galaxyproject/galaxy/blob/dev/test/functional/tools/multi_data_param.xml) +- [multi_data_optional.xml](https://github.com/galaxyproject/galaxy/blob/dev/test/functional/tools/multi_data_optional.xml) + +Additionally, a detailed discussion of handling multiple homogenous files can be found in the +the [Planemo Documentation](http://planemo.readthedocs.io/en/latest/writing_advanced.html#consuming-collections) +on this topic. + $attribute_list:format,multiple #### ``select`` diff --git a/test/functional/tools/multi_data_optional.xml b/test/functional/tools/multi_data_optional.xml new file mode 100644 index 00000000000..5f914259f8a --- /dev/null +++ b/test/functional/tools/multi_data_optional.xml @@ -0,0 +1,27 @@ + + + touch $out1; + #for $input in $input1 + #if $input + cat $input >> $out1; + #end if + #end for + + + + + + + + + + + + + + + + + + + diff --git a/test/functional/tools/samples_tool_conf.xml b/test/functional/tools/samples_tool_conf.xml index d57356e25d9..6286cbcb9c0 100644 --- a/test/functional/tools/samples_tool_conf.xml +++ b/test/functional/tools/samples_tool_conf.xml @@ -57,6 +57,7 @@ + From f0b988da78f081bf6845ce2d280d153e221f3633 Mon Sep 17 00:00:00 2001 From: John Chilton Date: Mon, 10 Oct 2016 11:07:05 -0400 Subject: [PATCH 11/32] Bring in latest galaxy-lib fixes. --- lib/galaxy/tools/deps/mulled/mulled_build.py | 8 ++++++-- lib/galaxy/tools/deps/mulled/util.py | 3 ++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/galaxy/tools/deps/mulled/mulled_build.py b/lib/galaxy/tools/deps/mulled/mulled_build.py index b359c540cbf..e110969b203 100644 --- a/lib/galaxy/tools/deps/mulled/mulled_build.py +++ b/lib/galaxy/tools/deps/mulled/mulled_build.py @@ -5,7 +5,7 @@ Examples Build a mulled image with: - mulled-build build 'samtools=1.3.1,bedtools=2.22' + mulled-build build 'samtools=1.3.1--4,bedtools=2.22' """ from __future__ import print_function @@ -118,6 +118,7 @@ def mull_targets( repository_template=DEFAULT_REPOSITORY_TEMPLATE, dry_run=False, binds=DEFAULT_BINDS ): + targets = list(targets) if involucro_context is None: involucro_context = InvolucroContext() @@ -227,7 +228,10 @@ def target_str_to_targets(targets_raw): def parse_target(target_str): if "=" in target_str: package_name, version = target_str.split("=", 1) - target = build_target(package_name, version) + build = None + if "--" in version: + version, build = version.split('--') + target = build_target(package_name, version, build) else: target = build_target(target_str) return target diff --git a/lib/galaxy/tools/deps/mulled/util.py b/lib/galaxy/tools/deps/mulled/util.py index 4fc5312afbe..a190d0e2192 100644 --- a/lib/galaxy/tools/deps/mulled/util.py +++ b/lib/galaxy/tools/deps/mulled/util.py @@ -82,6 +82,7 @@ def image_name(targets, image_build=None, name_override=None): print("WARNING: Overriding mulled image name, auto-detection of 'mulled' package attributes will fail to detect result.") return name_override + targets = list(targets) if len(targets) == 1: target = targets[0] suffix = "" @@ -99,7 +100,7 @@ def image_name(targets, image_build=None, name_override=None): targets_order = sorted(targets, key=lambda t: t.package_name) requirements_buffer = "\n".join(map(conda_build_target_str, targets_order)) m = hashlib.sha1() - m.update(requirements_buffer) + m.update(requirements_buffer.encode()) suffix = "" if not image_build else ":%s" % image_build return "mulled-v1-%s%s" % (m.hexdigest(), suffix) From 1e32c0926ca3fcee79f355577c5525c4bf73c8e4 Mon Sep 17 00:00:00 2001 From: Nicola Soranzo Date: Mon, 10 Oct 2016 18:13:33 +0100 Subject: [PATCH 12/32] Fix import order and Python3 compatibility for most of test/ --- .ci/flake8_blacklist.txt | 4 +-- .ci/flake8_lint_include_list.txt | 23 +++++++++++++--- .ci/py3_sources.txt | 2 ++ client/galaxy/style/source_material/circle.py | 5 ++-- doc/parse_gx_xsd.py | 6 ++--- test/api/helpers.py | 9 ++++--- test/api/test_api_batch.py | 4 ++- test/api/test_authenticate.py | 2 +- test/api/test_dataset_collections.py | 7 ++--- test/api/test_history_contents.py | 11 ++++---- test/api/test_page_revisions.py | 3 ++- test/api/test_pages.py | 7 +++-- test/api/test_tool_data.py | 4 ++- test/api/test_tools.py | 21 +++++++-------- test/api/test_users.py | 2 ++ test/api/test_workflow_extraction.py | 5 ++-- test/api/test_workflows.py | 26 +++++++++---------- test/api/workflows_format_2/__init__.py | 2 +- test/api/workflows_format_2/converter.py | 12 ++++----- test/base/api.py | 24 +++++++---------- test/base/driver_util.py | 19 ++++++-------- test/base/integration_util.py | 2 +- test/base/interactor.py | 7 +++-- test/base/test_logging.py | 3 +-- test/base/twilltestcase.py | 11 +++----- test/functional/test_data_managers.py | 16 +++++++----- test/functional/test_library_templates.py | 16 ++++++------ test/functional/test_toolbox.py | 21 ++++++++------- test/functional/workflow.py | 25 +++++++++--------- test/integration/test_resolvers.py | 4 +-- test/manual/workflows_scaling.py | 9 +++---- test/shed_functional/base/test_db_util.py | 5 ++-- .../test_0300_reset_all_metadata.py | 2 +- .../test_1300_reset_all_metadata.py | 2 +- test/shed_functional/functional_tests.py | 4 +-- 35 files changed, 168 insertions(+), 157 deletions(-) diff --git a/.ci/flake8_blacklist.txt b/.ci/flake8_blacklist.txt index bac2e110b7f..bfef6f7adeb 100644 --- a/.ci/flake8_blacklist.txt +++ b/.ci/flake8_blacklist.txt @@ -1,8 +1,8 @@ .venv/ .tox/ -eggs/ -client/ +client/node_modules/ database/ doc/patch.py doc/source/conf.py +eggs/ lib/galaxy/util/jstree.py diff --git a/.ci/flake8_lint_include_list.txt b/.ci/flake8_lint_include_list.txt index 735434e27ce..eb2ab916d61 100644 --- a/.ci/flake8_lint_include_list.txt +++ b/.ci/flake8_lint_include_list.txt @@ -1,8 +1,10 @@ +client/galaxy/style/source_material/circle.py contrib/ cron/add_manual_builds.py cron/cleanup_datasets.py cron/parse_builds_3_sites.py cron/parse_builds.py +doc/parse_gx_xsd.py lib/galaxy/actions/ lib/galaxy/auth/__init__.py lib/galaxy/auth/providers/__init__.py @@ -440,6 +442,7 @@ scripts/tool_shed/migrate_tools_to_repositories.py scripts/tool_shed/show_tool_dependency_installation_dir_contents.py scripts/tools/re_escape_output.py test/api/__init__.py +test/api/test_dataset_collections.py test/api/test_datasets.py test/api/test_datatypes.py test/api/test_framework.py @@ -447,27 +450,39 @@ test/api/test_histories.py test/api/test_history_contents_provenance.py test/api/test_jobs.py test/api/test_libraries.py +test/api/test_page_revisions.py test/api/test_search.py +test/api/test_tool_data.py +test/api/test_tools.py test/api/test_tours.py +test/api/test_workflow_extraction.py test/api/test_workflows_from_yaml.py -test/api/workflows_format_2/interface.py -test/api/workflows_format_2/main.py +test/api/workflows_format_2/ test/base/api_asserts.py +test/base/api.py test/base/api_util.py test/base/__init__.py test/base/instrument.py +test/base/integration_util.py +test/base/interactor.py test/base/nose_util.py +test/base/test_logging.py test/base/tool_shed_util.py +test/base/twilltestcase.py test/casperjs/ test/functional/database_contexts.py test/functional/__init__.py +test/functional/test_data_managers.py +test/functional/test_toolbox.py +test/functional/tool-data/ test/functional/tools/ -test/integration/__init__.py -test/integration/test_pulsar_embedded.py +test/functional/workflow.py +test/integration/ test/manual/__init__.py test/shed_functional/base/common.py test/shed_functional/base/__init__.py test/shed_functional/functional/ +test/shed_functional/functional_tests.py test/shed_functional/__init__.py test/unit/dataset_collections/__init__.py test/unit/datatypes/dataproviders/__init__.py diff --git a/.ci/py3_sources.txt b/.ci/py3_sources.txt index d9f47f7b999..ecd7f79c9d4 100644 --- a/.ci/py3_sources.txt +++ b/.ci/py3_sources.txt @@ -1,5 +1,7 @@ +client/galaxy/style/source_material/circle.py contrib/ cron/ +doc/parse_gx_xsd.py lib/galaxy/actions/ lib/galaxy/auth/ lib/galaxy/config.py diff --git a/client/galaxy/style/source_material/circle.py b/client/galaxy/style/source_material/circle.py index 5331a0e0cc4..f1f014b9565 100755 --- a/client/galaxy/style/source_material/circle.py +++ b/client/galaxy/style/source_material/circle.py @@ -1,15 +1,14 @@ #!/usr/bin/env python - """ usage: %prog width height bg_color hatch_color [color alpha stop_pos] + """ - from __future__ import division import sys -import cairo from math import pi +import cairo + assert sys.version_info[:2] >= ( 2, 4 ) diff --git a/doc/parse_gx_xsd.py b/doc/parse_gx_xsd.py index 7263ae358fd..452b7f4e43b 100644 --- a/doc/parse_gx_xsd.py +++ b/doc/parse_gx_xsd.py @@ -2,14 +2,12 @@ # TODO: Add examples, tables and best practice links to command # TODO: Examples of truevalue, falsevalue # TODO: Test param extra_file -from __future__ import print_function - # Things dropped from TOC (still documented inside schema). # - request_parameter_translation +from __future__ import print_function from lxml import etree -from StringIO import StringIO - +from six import StringIO with open("doc/schema_template.md", "r") as f: MARKDOWN_TEMPLATE = f.read() diff --git a/test/api/helpers.py b/test/api/helpers.py index 2a6b0c2537c..a4276a010cf 100644 --- a/test/api/helpers.py +++ b/test/api/helpers.py @@ -1,10 +1,11 @@ -from base import api_asserts +import json +import time from operator import itemgetter -import time -import json -from six import StringIO from pkg_resources import resource_string +from six import StringIO + +from base import api_asserts # Simple workflow that takes an input and call cat wrapper on it. workflow_str = resource_string( __name__, "test_workflow_1.ga" ) diff --git a/test/api/test_api_batch.py b/test/api/test_api_batch.py index 186e0964d6a..d85f0c890c4 100644 --- a/test/api/test_api_batch.py +++ b/test/api/test_api_batch.py @@ -1,7 +1,9 @@ import json -import pprint import logging +import pprint + from requests import post + from base import api log = logging.getLogger( "functional_tests.py" ) diff --git a/test/api/test_authenticate.py b/test/api/test_authenticate.py index 6e58693e2d3..d713e27066e 100644 --- a/test/api/test_authenticate.py +++ b/test/api/test_authenticate.py @@ -1,8 +1,8 @@ import base64 from requests import get -from base import api +from base import api TEST_USER_EMAIL = "auth_user_test@bx.psu.edu" TEST_USER_PASSWORD = "testpassword1" diff --git a/test/api/test_dataset_collections.py b/test/api/test_dataset_collections.py index 2e298fe7698..7e38350956f 100644 --- a/test/api/test_dataset_collections.py +++ b/test/api/test_dataset_collections.py @@ -1,7 +1,8 @@ -from base import api import json -from .helpers import DatasetPopulator -from .helpers import DatasetCollectionPopulator + +from base import api + +from .helpers import DatasetCollectionPopulator, DatasetPopulator class DatasetCollectionApiTestCase( api.ApiTestCase ): diff --git a/test/api/test_history_contents.py b/test/api/test_history_contents.py index 3a35515a92c..8f58bb321c7 100644 --- a/test/api/test_history_contents.py +++ b/test/api/test_history_contents.py @@ -1,11 +1,10 @@ -from base import api import json -from .helpers import TestsDatasets -from .helpers import LibraryPopulator -from .helpers import DatasetCollectionPopulator -from requests import delete -from requests import put +from requests import delete, put + +from base import api + +from .helpers import DatasetCollectionPopulator, LibraryPopulator, TestsDatasets # TODO: Test anonymous access. diff --git a/test/api/test_page_revisions.py b/test/api/test_page_revisions.py index 674ee5805ae..8f6013c1312 100644 --- a/test/api/test_page_revisions.py +++ b/test/api/test_page_revisions.py @@ -1,5 +1,6 @@ from galaxy.exceptions import error_codes -from api.test_pages import BasePageApiTestCase + +from .test_pages import BasePageApiTestCase class PageRevisionsApiTestCase( BasePageApiTestCase ): diff --git a/test/api/test_pages.py b/test/api/test_pages.py index f0195cdb0a5..1e72de9f3b5 100644 --- a/test/api/test_pages.py +++ b/test/api/test_pages.py @@ -1,8 +1,7 @@ -from galaxy.exceptions import error_codes -from base import api from requests import delete -from operator import itemgetter +from base import api +from galaxy.exceptions import error_codes class BasePageApiTestCase( api.ApiTestCase ): @@ -102,4 +101,4 @@ class PageApiTestCase( BasePageApiTestCase ): index_response = self._get( "pages" ) self._assert_status_code_is( index_response, 200 ) pages = index_response.json() - return id in map( itemgetter( "id" ), pages ) + return id in ( _["id"] for _ in pages ) diff --git a/test/api/test_tool_data.py b/test/api/test_tool_data.py index 0a2408e6f8d..0a0f3b14d9c 100644 --- a/test/api/test_tool_data.py +++ b/test/api/test_tool_data.py @@ -1,9 +1,11 @@ """ Tests for the tool data API. """ from __future__ import print_function -from base import api + import operator +from base import api + class ToolDataApiTestCase( api.ApiTestCase ): diff --git a/test/api/test_tools.py b/test/api/test_tools.py index 82e310b06a9..2db9ed5e2c8 100644 --- a/test/api/test_tools.py +++ b/test/api/test_tools.py @@ -1,13 +1,12 @@ # Test tools API. -from base import api import json -from operator import itemgetter -from .helpers import DatasetPopulator -from .helpers import DatasetCollectionPopulator -from .helpers import LibraryPopulator -from .helpers import skip_without_tool + +from base import api from galaxy.tools.verify.test_data import TestDataResolver +from .helpers import (DatasetCollectionPopulator, DatasetPopulator, + LibraryPopulator, skip_without_tool) + class ToolsTestCase( api.ApiTestCase ): @@ -25,7 +24,7 @@ class ToolsTestCase( api.ApiTestCase ): tools_index = index.json() # No need to flatten out sections, with in_panel=False, only tools are # returned. - tool_ids = map( itemgetter( "id" ), tools_index ) + tool_ids = [_["id"] for _ in tools_index] assert "upload1" in tool_ids @skip_without_tool( "cat1" ) @@ -195,7 +194,7 @@ class ToolsTestCase( api.ApiTestCase ): def get_state(dce): return dce["object"]["state"] - mixed_states = map(get_state, mixed_hdca["elements"]) + mixed_states = [get_state(_) for _ in mixed_hdca["elements"]] assert mixed_states == [u"ok", u"error", u"ok", u"error"], mixed_states inputs = { "input": { "src": "hdca", "id": mixed_hdca["id"] }, @@ -206,7 +205,7 @@ class ToolsTestCase( api.ApiTestCase ): self.assertEquals( len(filter_output_collections), 1 ) filtered_hid = filter_output_collections[0]["hid"] filtered_hdca = self.dataset_populator.get_history_collection_details(history_id, hid=filtered_hid, wait=False) - filtered_states = map(get_state, filtered_hdca["elements"]) + filtered_states = [get_state(_) for _ in filtered_hdca["elements"]] assert filtered_states == [u"ok", u"ok"], filtered_states @skip_without_tool( "multi_select" ) @@ -525,7 +524,7 @@ class ToolsTestCase( api.ApiTestCase ): # Assert we have three outputs with 1, 2, and 3 lines respectively. assert len( outputs ) == 3 outputs_contents = [ self.dataset_populator.get_history_dataset_content( history_id, dataset=o ).strip() for o in outputs ] - assert sorted( map( lambda c: len( c.split( "\n" ) ), outputs_contents ) ) == [ 1, 2, 3 ] + assert sorted( len( c.split( "\n" ) ) for c in outputs_contents ) == [ 1, 2, 3 ] @skip_without_tool( "cat1" ) def test_multirun_in_repeat( self ): @@ -1230,7 +1229,7 @@ class ToolsTestCase( api.ApiTestCase ): else: tools.append( tool_or_section ) - tool_ids = map( itemgetter( "id" ), tools ) + tool_ids = [_["id"] for _ in tools] return tool_ids def __build_nested_list( self, history_id ): diff --git a/test/api/test_users.py b/test/api/test_users.py index 897c3499d89..9a7a14291c4 100644 --- a/test/api/test_users.py +++ b/test/api/test_users.py @@ -1,5 +1,7 @@ import json + from requests import put + from base import api TEST_USER_EMAIL = "user_for_users_index_test@bx.psu.edu" diff --git a/test/api/test_workflow_extraction.py b/test/api/test_workflow_extraction.py index 47f0ab009eb..41545bbe3e4 100644 --- a/test/api/test_workflow_extraction.py +++ b/test/api/test_workflow_extraction.py @@ -1,8 +1,9 @@ from __future__ import print_function -from collections import namedtuple + import functools -from json import dumps, loads import operator +from collections import namedtuple +from json import dumps, loads from .helpers import skip_without_tool from .test_workflows import BaseWorkflowsApiTestCase diff --git a/test/api/test_workflows.py b/test/api/test_workflows.py index a6e1c94d2c2..a98f87cc1f8 100644 --- a/test/api/test_workflows.py +++ b/test/api/test_workflows.py @@ -1,26 +1,24 @@ from __future__ import print_function import time -import yaml -from json import dumps from collections import namedtuple +from json import dumps from uuid import uuid4 +import yaml +from requests import delete, put + from base import api -from galaxy.tools.verify.test_data import TestDataResolver from galaxy.exceptions import error_codes -from .helpers import WorkflowPopulator -from .helpers import DatasetPopulator -from .helpers import DatasetCollectionPopulator -from .helpers import skip_without_tool +from galaxy.tools.verify.test_data import TestDataResolver + +from .helpers import (DatasetCollectionPopulator, DatasetPopulator, + skip_without_tool, WorkflowPopulator) from .workflows_format_2 import ( convert_and_import_workflow, ImporterGalaxyInterface, ) -from requests import delete -from requests import put - SIMPLE_NESTED_WORKFLOW_YAML = """ class: GalaxyWorkflow inputs: @@ -277,7 +275,7 @@ class WorkflowsApiTestCase( BaseWorkflowsApiTestCase ): workflow = show_response.json() self._assert_looks_like_instance_workflow_representation( workflow ) assert len(workflow["steps"]) == 3 - self.assertEqual(sorted([step["id"] for step in workflow["steps"].values()]), [0, 1, 2]) + self.assertEqual(sorted(step["id"] for step in workflow["steps"].values()), [0, 1, 2]) show_response = self._get( "workflows/%s" % workflow_id, {"legacy": True} ) workflow = show_response.json() @@ -285,7 +283,7 @@ class WorkflowsApiTestCase( BaseWorkflowsApiTestCase ): assert len(workflow["steps"]) == 3 # Can't reay say what the legacy IDs are but must be greater than 3 because dummy # workflow was created first in this instance. - self.assertNotEqual(sorted([step["id"] for step in workflow["steps"].values()]), [0, 1, 2]) + self.assertNotEqual(sorted(step["id"] for step in workflow["steps"].values()), [0, 1, 2]) def test_show_invalid_key_is_400( self ): show_response = self._get( "workflows/%s" % self._random_key() ) @@ -457,7 +455,7 @@ class WorkflowsApiTestCase( BaseWorkflowsApiTestCase ): def get_subworkflow_content_id(workflow_id): workflow_contents = self._download_workflow(workflow_id, style="editor") steps = workflow_contents['steps'] - subworkflow_step = filter(lambda s: s["type"] == "subworkflow", steps.values())[0] + subworkflow_step = next(s for s in steps.values() if s["type"] == "subworkflow") return subworkflow_step['content_id'] workflow_id = self._upload_yaml_workflow(SIMPLE_NESTED_WORKFLOW_YAML, publish=True) @@ -1636,7 +1634,7 @@ steps: contents_url = "histories/%s/contents" % history history_contents_response = self._get( contents_url ) self._assert_status_code_is( history_contents_response, 200 ) - hda_summary = filter( lambda hc: hc[ "hid" ] == hid, history_contents_response.json() )[ 0 ] + hda_summary = next(hc for hc in history_contents_response.json() if hc[ "hid" ] == hid) hda_info_response = self._get( "%s/%s" % ( contents_url, hda_summary[ "id" ] ) ) self._assert_status_code_is( hda_info_response, 200 ) self.assertEqual( hda_info_response.json()[ "metadata_data_lines" ], lines ) diff --git a/test/api/workflows_format_2/__init__.py b/test/api/workflows_format_2/__init__.py index 96461e5e62f..e680a3db612 100644 --- a/test/api/workflows_format_2/__init__.py +++ b/test/api/workflows_format_2/__init__.py @@ -1,8 +1,8 @@ """ This module defines the public interface or entry point for the Format 2 workflow code. """ -from .main import convert_and_import_workflow from .interface import ImporterGalaxyInterface +from .main import convert_and_import_workflow __all__ = [ diff --git a/test/api/workflows_format_2/converter.py b/test/api/workflows_format_2/converter.py index 2795982914d..61ef54e6685 100644 --- a/test/api/workflows_format_2/converter.py +++ b/test/api/workflows_format_2/converter.py @@ -1,11 +1,11 @@ """Functionality for converting a Format 2 workflow into a standard Galaxy workflow.""" from __future__ import print_function -from collections import OrderedDict import json import os import sys import uuid +from collections import OrderedDict import yaml @@ -96,7 +96,7 @@ def _python_to_workflow(as_python, conversion_context): as_python["steps"] = steps_as_dict steps = steps_as_dict - for step in steps.itervalues(): + for step in steps.values(): step_type = step.get("type", None) if "run" in step: if step_type is not None: @@ -118,7 +118,7 @@ def _python_to_workflow(as_python, conversion_context): run_to_step_function(conversion_context, step, run_action) del step["run"] - for step in steps.itervalues(): + for step in steps.values(): step_type = step.get("type", "tool") step_type = STEP_TYPE_ALIASES.get(step_type, step_type) if step_type not in STEP_TYPES: @@ -326,7 +326,7 @@ def transform_tool(context, step): return {"__class__": "RuntimeValue"} if isinstance(value, dict): new_values = {} - for k, v in value.iteritems(): + for k, v in value.items(): new_key = _join_prefix(key, k) new_values[k] = replace_links(v, new_key) return new_values @@ -349,7 +349,7 @@ def transform_tool(context, step): step_state = step["state"] step_state = replace_links(step_state) - for key, value in step_state.iteritems(): + for key, value in step_state.items(): tool_state[key] = json.dumps(value) del step["state"] @@ -470,7 +470,7 @@ def _populate_input_connections(context, step, connect): input_connections = step["input_connections"] is_subworkflow_step = step.get("type") == "subworkflow" - for key, values in connect.iteritems(): + for key, values in connect.items(): input_connection_value = [] if not isinstance(values, list): values = [values] diff --git a/test/base/api.py b/test/base/api.py index d366f792193..3cd31428296 100644 --- a/test/base/api.py +++ b/test/base/api.py @@ -1,23 +1,19 @@ from contextlib import contextmanager +from six.moves.urllib.parse import urlencode + +from .api_asserts import ( + assert_error_code_is, + assert_has_keys, + assert_not_has_keys, + assert_status_code_is, +) +from .api_util import get_master_api_key, get_user_api_key +from .interactor import GalaxyInteractorApi as BaseInteractor # TODO: We don't need all of TwillTestCase, strip down to a common super class # shared by API and Twill test cases. from .twilltestcase import TwillTestCase -from base.interactor import GalaxyInteractorApi as BaseInteractor - -from .api_util import get_master_api_key -from .api_util import get_user_api_key -from .api_asserts import ( - assert_status_code_is, - assert_has_keys, - assert_not_has_keys, - assert_error_code_is, -) - -from urllib import urlencode - - TEST_USER = "user@bx.psu.edu" DEFAULT_OTHER_USER = "otheruser@bx.psu.edu" # A second user for API testing. diff --git a/test/base/driver_util.py b/test/base/driver_util.py index 3536efe9ae5..977a88a3d0a 100644 --- a/test/base/driver_util.py +++ b/test/base/driver_util.py @@ -17,24 +17,21 @@ import nose.config import nose.core import nose.loader import nose.plugins.manager - -from paste import httpserver import requests - -from .api_util import get_master_api_key, get_user_api_key -from .tool_shed_util import parse_tool_panel_config -from .nose_util import run -from .instrument import StructuredTestDataPlugin +from paste import httpserver from functional import database_contexts - from galaxy.app import UniverseApplication as GalaxyUniverseApplication -from galaxy.web import buildapp -from galaxy.webapps.tool_shed.app import UniverseApplication as ToolshedUniverseApplication from galaxy.util import asbool from galaxy.util.properties import load_app_properties +from galaxy.web import buildapp +from galaxy.webapps.tool_shed.app import UniverseApplication as ToolshedUniverseApplication -from base.test_logging import logging_config_file +from .api_util import get_master_api_key, get_user_api_key +from .instrument import StructuredTestDataPlugin +from .nose_util import run +from .test_logging import logging_config_file +from .tool_shed_util import parse_tool_panel_config galaxy_root = os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir, os.path.pardir)) DEFAULT_WEB_HOST = "localhost" diff --git a/test/base/integration_util.py b/test/base/integration_util.py index ca603ade4a4..76db3cfeb8f 100644 --- a/test/base/integration_util.py +++ b/test/base/integration_util.py @@ -4,9 +4,9 @@ Tests that start an actual Galaxy server with a particular configuration in order to test something that cannot be tested with the default functional/api tessting configuration. """ +from unittest import TestCase from .driver_util import GalaxyTestDriver -from unittest import TestCase NO_APP_MESSAGE = "test_case._app called though no Galaxy has been configured." diff --git a/test/base/interactor.py b/test/base/interactor.py index 0ede58b30b1..feac086410e 100644 --- a/test/base/interactor.py +++ b/test/base/interactor.py @@ -2,12 +2,12 @@ from __future__ import print_function import os import re +import time from json import dumps from logging import getLogger -from requests import get, post, delete, patch -from six import StringIO -from six import text_type +from requests import delete, get, patch, post +from six import StringIO, text_type from galaxy import util from galaxy.tools.parser.interface import TestCollectionDef @@ -118,7 +118,6 @@ class GalaxyInteractorApi( object ): metadata[ "file_ext" ] = expected_file_type if metadata: - import time time.sleep(5) dataset = self._get( "histories/%s/contents/%s" % ( history_id, hid ) ).json() for key, value in metadata.items(): diff --git a/test/base/test_logging.py b/test/base/test_logging.py index 295f278220a..de11008ca4c 100644 --- a/test/base/test_logging.py +++ b/test/base/test_logging.py @@ -1,9 +1,8 @@ from __future__ import absolute_import -import os - import logging import logging.config +import os # This is done in paster or galaxy.main for server app, provide # an entry point testing as well. diff --git a/test/base/twilltestcase.py b/test/base/twilltestcase.py index 84c300887cd..d077448fe27 100644 --- a/test/base/twilltestcase.py +++ b/test/base/twilltestcase.py @@ -15,7 +15,6 @@ from xml.etree import ElementTree # Be sure to use Galaxy's vanilla pyparsing instead of the older version # imported by twill. import pyparsing # noqa: F401 - import twill import twill.commands as tc from markupsafe import escape @@ -23,17 +22,15 @@ from six import string_types, StringIO from six.moves.urllib.parse import unquote, urlencode, urlparse from twill.other_packages._mechanize_dist import ClientForm -from galaxy.web import security -from galaxy.web.framework.helpers import iff - -from galaxy.tools.verify.test_data import TestDataResolver from galaxy.tools.verify import ( - verify, check_command, files_diff, make_temp_fname, + verify, ) - +from galaxy.tools.verify.test_data import TestDataResolver +from galaxy.web import security +from galaxy.web.framework.helpers import iff # Force twill to log to a buffer -- FIXME: Should this go to stdout and be captured by nose? buffer = StringIO() diff --git a/test/functional/test_data_managers.py b/test/functional/test_data_managers.py index 2843ad070c3..b75925ed13b 100644 --- a/test/functional/test_data_managers.py +++ b/test/functional/test_data_managers.py @@ -1,12 +1,14 @@ +import logging import new -import tempfile import os.path import shutil -from test_toolbox import ToolTestCase -from base.interactor import stage_data_in_history -import logging -log = logging.getLogger( __name__ ) +import tempfile +from base.interactor import stage_data_in_history + +from .test_toolbox import ToolTestCase + +log = logging.getLogger( __name__ ) data_managers = None @@ -68,7 +70,7 @@ def build_tests( tmp_dir=None, testing_shed_tools=False, master_api_key=None, us del G[ key ] # first we will loop through data table loc files and copy them to temporary location, then swap out filenames: - for data_table_name, data_table in data_managers.app.tool_data_tables.get_tables().iteritems(): + for data_table_name, data_table in data_managers.app.tool_data_tables.get_tables().items(): for filename, value in list( data_table.filenames.items() ): new_filename = tempfile.NamedTemporaryFile( prefix=os.path.basename( filename ), dir=tmp_dir ).name try: @@ -81,7 +83,7 @@ def build_tests( tmp_dir=None, testing_shed_tools=False, master_api_key=None, us del data_table.filenames[ filename ] # remove filename:value pair data_table.filenames[ new_filename ] = value # add new value by - for i, ( data_manager_id, data_manager ) in enumerate( data_managers.data_managers.iteritems() ): + for i, ( data_manager_id, data_manager ) in enumerate( data_managers.data_managers.items() ): tool = data_manager.tool if not tool: log.warning( "No Tool has been specified for Data Manager: %s", data_manager_id ) diff --git a/test/functional/test_library_templates.py b/test/functional/test_library_templates.py index d1abfc8fe2b..7986fcf4fa6 100644 --- a/test/functional/test_library_templates.py +++ b/test/functional/test_library_templates.py @@ -1,17 +1,17 @@ -from base.twilltestcase import TwillTestCase +import galaxy.model from base.test_db_util import ( - get_user, - get_private_role, - get_form, - get_library, get_folder, - get_user_address, - get_latest_ldda_by_name, + get_form, get_latest_hda, + get_latest_ldda_by_name, + get_library, + get_private_role, + get_user, + get_user_address, mark_obj_deleted, refresh ) -import galaxy.model +from base.twilltestcase import TwillTestCase AddressField_form = None CheckboxField_form = None diff --git a/test/functional/test_toolbox.py b/test/functional/test_toolbox.py index 2069c543f9f..99d5f97b998 100644 --- a/test/functional/test_toolbox.py +++ b/test/functional/test_toolbox.py @@ -1,12 +1,8 @@ +from __future__ import print_function + import logging import new import sys -from base.twilltestcase import TwillTestCase -from base.interactor import build_interactor, stage_data_in_history, RunToolException -from base.instrument import register_job_data -from galaxy.tools import DataManagerTool -from galaxy.tools.verify.asserts import verify_assertions -from galaxy.util import bunch try: from nose.tools import nottest @@ -14,6 +10,13 @@ except ImportError: def nottest(x): return x +from base.instrument import register_job_data +from base.interactor import build_interactor, RunToolException, stage_data_in_history +from base.twilltestcase import TwillTestCase +from galaxy.tools import DataManagerTool +from galaxy.tools.verify.asserts import verify_assertions +from galaxy.util import bunch + log = logging.getLogger( __name__ ) toolbox = None @@ -83,7 +86,7 @@ class ToolTestCase( TwillTestCase ): if job_stdio is not None: job_data["job"] = job_stdio if job_output_exceptions: - job_data["output_problems"] = map(str, job_output_exceptions) + job_data["output_problems"] = [str(_) for _ in job_output_exceptions] if tool_execution_exception: job_data["execution_problem"] = str(tool_execution_exception) register_job_data(job_data) @@ -120,7 +123,7 @@ class ToolTestCase( TwillTestCase ): # Only print this stuff out once. for stream in ['stdout', 'stderr']: if stream in job_stdio: - print >>sys.stderr, self._format_stream( job_stdio[ stream ], stream=stream, format=True ) + print(self._format_stream( job_stdio[ stream ], stream=stream, format=True ), file=sys.stderr) found_exceptions.append(e) if testdef.expect_failure: @@ -161,7 +164,7 @@ class ToolTestCase( TwillTestCase ): # just a list (case with twill variant or if output changes its # name). if hasattr(data_list, "values"): - output_data = data_list.values()[ output_index ] + output_data = list(data_list.values())[ output_index ] else: output_data = data_list[ len(data_list) - len(testdef.outputs) + output_index ] self.assertTrue( output_data is not None ) diff --git a/test/functional/workflow.py b/test/functional/workflow.py index 9dbba1fbe7e..2d8e466bd06 100644 --- a/test/functional/workflow.py +++ b/test/functional/workflow.py @@ -1,14 +1,15 @@ +from __future__ import print_function + import os import sys -from base.twilltestcase import TwillTestCase -from base.interactor import GalaxyInteractorApi, stage_data_in_history - -from galaxy.util import parse_xml -from galaxy.util import bunch -from galaxy.tools.test import parse_param_elem, require_file, test_data_iter, parse_output_elems -from json import load, dumps - +from json import dumps, load from logging import getLogger + +from base.interactor import GalaxyInteractorApi, stage_data_in_history +from base.twilltestcase import TwillTestCase +from galaxy.tools.test import parse_output_elems, parse_param_elem, require_file, test_data_iter +from galaxy.util import bunch, parse_xml + log = getLogger( __name__ ) @@ -75,7 +76,7 @@ class WorkflowTestCase( TwillTestCase ): except Exception: for stream in ['stdout', 'stderr']: stream_output = galaxy_interactor.get_job_stream( test_history, output_data, stream=stream ) - print >>sys.stderr, self._format_stream( stream_output, stream=stream, format=True ) + print(self._format_stream( stream_output, stream=stream, format=True ), file=sys.stderr) raise def __import_workflow( self, galaxy_interactor, workflow ): @@ -94,8 +95,8 @@ class WorkflowTestCase( TwillTestCase ): imported_workflow = galaxy_interactor.read_workflow( workflow_id ) step_id_map = {} - local_steps_ids = sorted( [ int( step_id ) for step_id in workflow[ 'steps' ].keys() ] ) - imported_steps_ids = sorted( [ int( step_id ) for step_id in imported_workflow[ 'steps' ].keys() ] ) + local_steps_ids = sorted( int( step_id ) for step_id in workflow[ 'steps' ].keys() ) + imported_steps_ids = sorted( int( step_id ) for step_id in imported_workflow[ 'steps' ].keys() ) for local_step_id, imported_step_id in zip( local_steps_ids, imported_steps_ids ): step_id_map[ local_step_id ] = imported_step_id @@ -152,7 +153,7 @@ class WorkflowTest( object ): def input_datasets( self ): steps = self.workflow[ "steps" ] log.info("in input_datasets with steps %s" % steps) - for step_index, step_dict in steps.iteritems(): + for step_index, step_dict in steps.items(): if step_dict.get( "name", None ) == "Input dataset": yield int( step_index ), step_dict[ "inputs" ][0][ "name" ] diff --git a/test/integration/test_resolvers.py b/test/integration/test_resolvers.py index 74fa377f032..f1ee7d9b8eb 100644 --- a/test/integration/test_resolvers.py +++ b/test/integration/test_resolvers.py @@ -1,10 +1,10 @@ """Integration tests for conda dependency resolution.""" - import os import shutil +from tempfile import mkdtemp + from base import integration_util from base.api import ApiTestCase -from tempfile import mkdtemp GNUPLOT = {u'version': u'4.6', u'type': u'package', u'name': u'gnuplot'} diff --git a/test/manual/workflows_scaling.py b/test/manual/workflows_scaling.py index f9d47396a02..f81467d6c43 100644 --- a/test/manual/workflows_scaling.py +++ b/test/manual/workflows_scaling.py @@ -10,17 +10,16 @@ import json import os import random import sys +from argparse import ArgumentParser from threading import Thread from uuid import uuid4 -galaxy_root = os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir, os.path.pardir)) -sys.path[1:1] = [ os.path.join( galaxy_root, "lib" ), os.path.join( galaxy_root, "test" ) ] - -from argparse import ArgumentParser - import requests from bioblend import galaxy +galaxy_root = os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir, os.path.pardir)) +sys.path[1:1] = [ os.path.join( galaxy_root, "lib" ), os.path.join( galaxy_root, "test" ) ] + from api import helpers, yaml_to_workflow LONG_TIMEOUT = 1000000000 diff --git a/test/shed_functional/base/test_db_util.py b/test/shed_functional/base/test_db_util.py index a2f4240fad3..d4750023c15 100644 --- a/test/shed_functional/base/test_db_util.py +++ b/test/shed_functional/base/test_db_util.py @@ -6,9 +6,8 @@ import galaxy.model import galaxy.model.tool_shed_install import galaxy.webapps.tool_shed.model as model -from functional.database_contexts import tool_shed_context as sa_session -from functional.database_contexts import galaxy_context as ga_session -from functional.database_contexts import install_context as install_session +from functional.database_contexts import (galaxy_context as ga_session, + install_context as install_session, tool_shed_context as sa_session) log = logging.getLogger( 'test.tool_shed.test_db_util' ) diff --git a/test/shed_functional/functional/test_0300_reset_all_metadata.py b/test/shed_functional/functional/test_0300_reset_all_metadata.py index c5f6b7cb900..a8a8e10e915 100644 --- a/test/shed_functional/functional/test_0300_reset_all_metadata.py +++ b/test/shed_functional/functional/test_0300_reset_all_metadata.py @@ -594,7 +594,7 @@ class TestResetAllRepositoryMetadata( ShedTwillTestCase ): old_metadata[ self.security.encode_id( repository.id ) ] = dict() for metadata in self.get_repository_metadata( repository ): old_metadata[ self.security.encode_id( repository.id ) ][ metadata.changeset_revision ] = metadata.metadata - self.reset_metadata_on_selected_repositories( old_metadata.keys() ) + self.reset_metadata_on_selected_repositories( list(old_metadata.keys()) ) for repository in repositories: new_metadata[ self.security.encode_id( repository.id ) ] = dict() for metadata in self.get_repository_metadata( repository ): diff --git a/test/shed_functional/functional/test_1300_reset_all_metadata.py b/test/shed_functional/functional/test_1300_reset_all_metadata.py index a7d16dceac8..25bf7b7a1eb 100644 --- a/test/shed_functional/functional/test_1300_reset_all_metadata.py +++ b/test/shed_functional/functional/test_1300_reset_all_metadata.py @@ -443,7 +443,7 @@ class TestResetInstalledRepositoryMetadata( ShedTwillTestCase ): repositories = self.test_db_util.get_all_installed_repositories( actually_installed=True ) for repository in repositories: repository_metadata[ self.security.encode_id( repository.id ) ] = repository.metadata - self.reset_metadata_on_selected_installed_repositories( repository_metadata.keys() ) + self.reset_metadata_on_selected_installed_repositories( list(repository_metadata.keys()) ) for repository in repositories: self.test_db_util.ga_refresh( repository ) old_metadata = repository_metadata[ self.security.encode_id( repository.id ) ] diff --git a/test/shed_functional/functional_tests.py b/test/shed_functional/functional_tests.py index 8a2511f4bd1..1fba4d9b4b7 100644 --- a/test/shed_functional/functional_tests.py +++ b/test/shed_functional/functional_tests.py @@ -15,12 +15,12 @@ galaxy_root = os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pa sys.path[0:1] = [ os.path.join( galaxy_root, "lib" ), os.path.join( galaxy_root, "test" ) ] from base import driver_util -log = driver_util.build_logger() # This is for the tool shed application. -from galaxy.webapps.tool_shed import buildapp as toolshedbuildapp from galaxy.web import buildapp as galaxybuildapp +from galaxy.webapps.tool_shed import buildapp as toolshedbuildapp +log = driver_util.build_logger() tool_sheds_conf_xml_template = ''' From 20dcc4d693ae8fa7e846eb32e65292c3c4a38863 Mon Sep 17 00:00:00 2001 From: Martin Cech Date: Mon, 10 Oct 2016 19:19:51 -0400 Subject: [PATCH 13/32] remove duplicated option from config sample --- config/galaxy.ini.sample | 6 ------ 1 file changed, 6 deletions(-) diff --git a/config/galaxy.ini.sample b/config/galaxy.ini.sample index f60ce30d917..cc5f3b37943 100644 --- a/config/galaxy.ini.sample +++ b/config/galaxy.ini.sample @@ -242,12 +242,6 @@ paste.app_factory = galaxy.web.buildapp:app_factory # enable_beta_mulled_containers. #containers_resolvers_config_file = None -# Container resolvers configuration (beta). Setup a file describing container -# resolvers to use when discovering containers for Galaxy. If this is set to -# None, the default containers loaded is determined by -# enable_beta_mulled_containers. -#containers_resolvers_config_file = None - # involucro is a tool used to build Docker containers for tools from Conda # dependencies referenced in tools as `requirement`s. The following path is # the location of involucro on the Galaxy host. This is ignored if the relevant From bcabbd8707e0064e041e730e5abd1d8dfc0a692c Mon Sep 17 00:00:00 2001 From: Martin Cech Date: Mon, 10 Oct 2016 19:26:36 -0400 Subject: [PATCH 14/32] minor changes to mulled admin docs --- doc/source/admin/mulled_containers.rst | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/doc/source/admin/mulled_containers.rst b/doc/source/admin/mulled_containers.rst index 7c16ba72353..792a47f94ad 100644 --- a/doc/source/admin/mulled_containers.rst +++ b/doc/source/admin/mulled_containers.rst @@ -6,8 +6,8 @@ Galaxy tools (also called wrappers) are able to use Conda packages (see more information in our `Galaxy Conda documentation`_) and Docker containers as dependency resolvers. The IUC_ recommends to use Conda packages as primary dependency resolver, mainly because Docker is not available on every (HPC-) system. Conda on the other hand can be installed by Galaxy and maintained -entirely in user-space. Nevertheless, Docker (Containers in general) has some uniq features and -there are a bunch of use-cases in the Galaxy community that makes containerized systems very appealing. +entirely in user-space. Nevertheless, Docker (Containers in general) has some unique features and +there are many use-cases in the Galaxy community which makes containerized systems very appealing. Since 2014 Galaxy supports running tools in Docker containers via a special `container annotation`_ inside of the requirement field. @@ -22,26 +22,26 @@ requirement field. -This approach has shown two limitations that slowed down the adoption by Tool developers. +This approach has shown two limitations that slowed down the adoption by tool developers. First, every tool needs to be annotated with a container name (as shown above) and this container needs to be created beforehand, usually manually. The second reason is that a Galaxy tool aims to be deployed everywhere, -independet of the underlying system, meaning if Docker is not availble Galaxy should use Conda packages. -This puts an additionl burden to tool developers that needs to take care of two dependency resolvers and can yield to -different results of your tool depending on the resolver, because both the Conda package and the Docker container is -usually not created out of the same recipe and maybe was compiled in a different way, uses different sources etc ... +independet of the underlying system, meaning if Docker is not available Galaxy should use Conda packages. +This puts an additional burden on tool developers who need to take care of two dependency resolvers. This setup can cause +different tool results depending on the resolver, because both the Conda package and the Docker container are +usually not created out of the same recipe and maybe were compiled in a different way, use different sources etc. Not an ideal solution and something we wanted to solve. Here we demonstrate a solution that can create Containers out of Conda packages automatically. This can be either used to support communities like BioContainers_ to create Containers -before deploying a Galaxy tool, or this can be used by Galaxy to create Containers on-demand and on-the-fly if no one -is available already. +before deploying a Galaxy tool, or this can be used by Galaxy to create Containers on-demand and on-the-fly if one +is not available already. -Automatic build of linux containers +Automatic build of Linux containers ----------------------------------- -We utilize [mulled](https://github.com/mulled/mulled) and with this [involucro](https://github.com/involucro/involucro) +We utilize [mulled](https://github.com/mulled/mulled) with [involucro](https://github.com/involucro/involucro) in an automatic way. This is for example used to convert all packages in bioconda_ into Linux Containers (Docker and rkt at the moment) and made available at the `BioContainers Quay.io account`_. @@ -98,7 +98,7 @@ This also demonstrates how you can build a container locally and on-the-fly. --extra-channel file://home/bag/miniconda2/conda-bld/ --test 'samtools --help' The ``--0`` indicates the build version of the conda package. It is recommended to specify this number otherwise -you will override already exsisting images. For python conda packages this extension might look like this ``--py35_1``. +you will override already existing images. For Python Conda packages this extension might look like this ``--py35_1``. Build, test and push a conda-forge package to biocontainers ^^^^^^^^^^^^^^^^^^^^^ From 1a2d75fade1077359b2a9b1072feab793022b7f4 Mon Sep 17 00:00:00 2001 From: Martin Cech Date: Mon, 10 Oct 2016 20:54:26 -0400 Subject: [PATCH 15/32] add muled docs to admin index --- doc/source/admin/index.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/source/admin/index.rst b/doc/source/admin/index.rst index 50bf0a90cd0..64ba2986d04 100644 --- a/doc/source/admin/index.rst +++ b/doc/source/admin/index.rst @@ -11,6 +11,8 @@ documentation. These resources should be used together. dependency_resolvers.rst conda_faq.rst + + mulled_containers.rst interactive_environments.rst From 39fb030f13c45991d4a2ca811b66e7872fb0a4e7 Mon Sep 17 00:00:00 2001 From: Marius van den Beek Date: Tue, 11 Oct 2016 17:07:23 +0200 Subject: [PATCH 16/32] Remove travis-ci python installation, since python2.7 is preinstalled on OSX images now --- .travis.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 83463757ea4..c968701ad6d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,9 +17,6 @@ matrix: env: TOX_ENV=py27-unit language: generic -before_install: - - if [ `uname` == "Darwin" ]; then bash -c "brew update && brew install python"; fi - install: - pip install tox - if [ "$TOX_ENV" == "qunit" ]; then bash -c 'cd test/qunit && npm install'; fi From 2b9281a37832346b0a3e5aed1b13ee84270ffe44 Mon Sep 17 00:00:00 2001 From: Daniel Blankenberg Date: Tue, 11 Oct 2016 13:10:50 -0400 Subject: [PATCH 17/32] Fix dependency ordering broken in #2917. We will store dependencies in an OrderedDict, matching the order of ``. Fixes an issue where an R library was being sourced before R itself during shell command generation, the sourcing of R afterwards overwrote the `R_LIBS` env setting. --- lib/galaxy/tools/deps/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/galaxy/tools/deps/__init__.py b/lib/galaxy/tools/deps/__init__.py index ccf5bb25753..4ec3e42a1a4 100644 --- a/lib/galaxy/tools/deps/__init__.py +++ b/lib/galaxy/tools/deps/__init__.py @@ -5,6 +5,8 @@ Dependency management for tools. import logging import os.path +from collections import OrderedDict + from galaxy.util import plugin_config from .resolvers import NullDependency @@ -97,7 +99,7 @@ class DependencyManager( object ): Takes a list of requirements and returns a dictionary with requirements as key and dependencies as value. """ - requirement_to_dependency = dict() + requirement_to_dependency = OrderedDict() for requirement in requirements: if requirement.type in [ 'package', 'set_environment' ]: dependency = self.find_dep( name=requirement.name, From dcde64ce407b332072525c66885adf03a26eeb5e Mon Sep 17 00:00:00 2001 From: Nicola Soranzo Date: Wed, 12 Oct 2016 11:07:01 +0100 Subject: [PATCH 18/32] Fix import order. Fix typos and improve readability --- .ci/flake8_lint_include_list.txt | 1 + lib/galaxy/workflow/extract.py | 87 ++++++++++++++-------------- test/api/test_workflow_extraction.py | 4 +- 3 files changed, 45 insertions(+), 47 deletions(-) diff --git a/.ci/flake8_lint_include_list.txt b/.ci/flake8_lint_include_list.txt index eb2ab916d61..343e3818220 100644 --- a/.ci/flake8_lint_include_list.txt +++ b/.ci/flake8_lint_include_list.txt @@ -347,6 +347,7 @@ lib/galaxy/web/framework/middleware/statsd.py lib/galaxy/web/framework/middleware/translogger.py lib/galaxy/web/framework/middleware/xforwardedhost.py lib/galaxy/web/params.py +lib/galaxy/workflow/extract.py lib/galaxy/workflow/__init__.py lib/galaxy/workflow/render.py lib/galaxy/workflow/schedulers/__init__.py diff --git a/lib/galaxy/workflow/extract.py b/lib/galaxy/workflow/extract.py index e9469a02767..adc61d0e1c9 100644 --- a/lib/galaxy/workflow/extract.py +++ b/lib/galaxy/workflow/extract.py @@ -1,25 +1,26 @@ """ This module contains functionality to aid in extracting workflows from histories. """ -from galaxy.util.odict import odict -from galaxy import exceptions -from galaxy import model +import logging + +from galaxy import exceptions, model from galaxy.tools.parameters.basic import ( - DataToolParameter, - DataCollectionToolParameter + DataCollectionToolParameter, + DataToolParameter ) -from galaxy.tools.parser import ToolOutputCollectionPart from galaxy.tools.parameters.grouping import ( Conditional, Repeat, Section ) +from galaxy.tools.parser import ToolOutputCollectionPart +from galaxy.util.odict import odict + from .steps import ( attach_ordered_steps, order_workflow_steps_with_levels ) -import logging log = logging.getLogger( __name__ ) WARNING_SOME_DATASETS_NOT_READY = "Some datasets still queued or running were ignored" @@ -67,16 +68,14 @@ def extract_steps( trans, history=None, job_ids=None, dataset_ids=None, dataset_ elif type( dataset_collection_ids) is not list: dataset_collection_ids = [ dataset_collection_ids ] # Convert both sets of ids to integers - job_ids = [ int( id ) for id in job_ids ] - dataset_ids = [ int( id ) for id in dataset_ids ] - dataset_collection_ids = [ int( id ) for id in dataset_collection_ids ] - # Find each job, for security we (implicately) check that they are - # associated witha job in the current history. + job_ids = [ int( _ ) for _ in job_ids ] + dataset_ids = [ int( _ ) for _ in dataset_ids ] + dataset_collection_ids = [ int( _ ) for _ in dataset_collection_ids ] + # Find each job, for security we (implicitly) check that they are + # associated with a job in the current history. summary = WorkflowSummary( trans, history ) jobs = summary.jobs - jobs_by_id = dict( ( job.id, job ) for job in jobs.keys() ) steps = [] - steps_by_job_id = {} hid_to_output_pair = {} # Input dataset steps for i, hid in enumerate( dataset_ids ): @@ -103,6 +102,7 @@ def extract_steps( trans, history=None, job_ids=None, dataset_ids=None, dataset_ hid_to_output_pair[ hid ] = ( step, 'output' ) steps.append( step ) # Tool steps + jobs_by_id = dict( ( job.id, job ) for job in jobs.keys() ) for job_id in job_ids: if job_id not in jobs_by_id: log.warning( "job_id %s not found in jobs_by_id %s" % ( job_id, jobs_by_id ) ) @@ -134,7 +134,6 @@ def extract_steps( trans, history=None, job_ids=None, dataset_ids=None, dataset_ conn.output_step = other_step conn.output_name = other_name steps.append( step ) - steps_by_job_id[ job_id ] = step # Store created dataset hids for assoc in (job.output_datasets + job.output_dataset_collection_instances): assoc_name = assoc.name @@ -147,8 +146,8 @@ def extract_steps( trans, history=None, job_ids=None, dataset_ids=None, dataset_ if query_assoc_name == assoc_name: hid = dataset_collection.hid if hid is None: - template = "Failed to find matching implicit job - job is %s, jobs are %s, assoc_name is %s." - message = template % ( job.id, jobs, assoc.name ) + template = "Failed to find matching implicit job - job id is %s, implicit pairs are %s, assoc_name is %s." + message = template % ( job.id, jobs[job], assoc_name ) log.warning( message ) raise Exception( "Failed to extract job." ) else: @@ -223,25 +222,24 @@ class WorkflowSummary( object ): else: self.__summarize_dataset( content ) - def __summarize_dataset_collection( self, content ): - content = self.__original_hdca( content ) - dataset_collection = content - hid = content.hid - self.collection_types[ hid ] = content.collection.collection_type - cja = content.creating_job_associations + def __summarize_dataset_collection( self, dataset_collection ): + dataset_collection = self.__original_hdca( dataset_collection ) + hid = dataset_collection.hid + self.collection_types[ hid ] = dataset_collection.collection.collection_type + cja = dataset_collection.creating_job_associations if cja: - # Use the first job to represent all mapped jobs. - representive_job_assoc = content.creating_job_associations[0] - job = representive_job_assoc.job - if job not in self.jobs or self.jobs[ job ][ 0 ][ 1 ].history_content_type == "dataset": - self.jobs[ job ] = [ ( representive_job_assoc.name, dataset_collection ) ] - if content.implicit_output_name: - self.implicit_map_jobs.append( job ) + # Use the "first" job to represent all mapped jobs. + representative_assoc = cja[0] + representative_job = representative_assoc.job + if representative_job not in self.jobs or self.jobs[ representative_job ][ 0 ][ 1 ].history_content_type == "dataset": + self.jobs[ representative_job ] = [ ( representative_assoc.name, dataset_collection ) ] + if dataset_collection.implicit_output_name: + self.implicit_map_jobs.append( representative_job ) else: - self.jobs[ job ].append( ( representive_job_assoc.name, dataset_collection ) ) + self.jobs[ representative_job ].append( ( representative_assoc.name, dataset_collection ) ) # This whole elif condition may no longer be needed do to additional # tracking with creating_job_associations. Will delete at some point. - elif content.implicit_output_name: + elif dataset_collection.implicit_output_name: # TODO: Optimize db call dataset_instance = dataset_collection.collection.dataset_instances[ 0 ] if not self.__check_state( dataset_instance ): @@ -249,13 +247,13 @@ class WorkflowSummary( object ): # makes me wonder if even need this check at all? return - job_hda = self.__original_hda( dataset_instance ) - if not job_hda.creating_job_associations: + original_hda = self.__original_hda( dataset_instance ) + if not original_hda.creating_job_associations: log.warning( "An implicitly create output dataset collection doesn't have a creating_job_association, should not happen!" ) job = DatasetCollectionCreationJob( dataset_collection ) self.jobs[ job ] = [ ( None, dataset_collection ) ] - for assoc in job_hda.creating_job_associations: + for assoc in original_hda.creating_job_associations: job = assoc.job if job not in self.jobs or self.jobs[ job ][ 0 ][ 1 ].history_content_type == "dataset": self.jobs[ job ] = [ ( assoc.name, dataset_collection ) ] @@ -263,19 +261,19 @@ class WorkflowSummary( object ): else: self.jobs[ job ].append( ( assoc.name, dataset_collection ) ) else: - job = DatasetCollectionCreationJob( content ) - self.jobs[ job ] = [ ( None, content ) ] + job = DatasetCollectionCreationJob( dataset_collection ) + self.jobs[ job ] = [ ( None, dataset_collection ) ] def __summarize_dataset( self, dataset ): if not self.__check_state( dataset ): return - job_hda = self.__original_hda( dataset ) + original_hda = self.__original_hda( dataset ) - if not job_hda.creating_job_associations: + if not original_hda.creating_job_associations: self.jobs[ FakeJob( dataset ) ] = [ ( None, dataset ) ] - for assoc in job_hda.creating_job_associations: + for assoc in original_hda.creating_job_associations: job = assoc.job if job in self.jobs: self.jobs[ job ].append( ( assoc.name, dataset ) ) @@ -288,11 +286,10 @@ class WorkflowSummary( object ): return hdca def __original_hda( self, hda ): - # if this hda was copied from another, we need to find the job that created the origial hda - job_hda = hda - while job_hda.copied_from_history_dataset_association: - job_hda = job_hda.copied_from_history_dataset_association - return job_hda + # if this hda was copied from another, we need to find the job that created the original hda + while hda.copied_from_history_dataset_association: + hda = hda.copied_from_history_dataset_association + return hda def __check_state( self, hda ): # FIXME: Create "Dataset.is_finished" diff --git a/test/api/test_workflow_extraction.py b/test/api/test_workflow_extraction.py index 41545bbe3e4..53864393edd 100644 --- a/test/api/test_workflow_extraction.py +++ b/test/api/test_workflow_extraction.py @@ -216,7 +216,7 @@ test_data: text_input2: "samp1\t30.0\nsamp2\t40.0\n" """) tool_ids = [ "cat1", "collection_split_on_column", "cat_list" ] - job_ids = list(map( functools.partial(self._job_id_for_tool, jobs_summary.jobs ), tool_ids )) + job_ids = [ functools.partial(self._job_id_for_tool, jobs_summary.jobs )(_) for _ in tool_ids ] downloaded_workflow = self._extract_and_download_workflow( dataset_ids=[ "1", "2" ], job_ids=job_ids, @@ -266,7 +266,7 @@ test_data: content: "samp1\t30.0\nsamp2\t40.0\n" """) tool_ids = [ "cat1", "collection_creates_pair", "cat_collection", "cat_list" ] - job_ids = list(map( functools.partial(self._job_id_for_tool, jobs_summary.jobs ), tool_ids )) + job_ids = [ functools.partial(self._job_id_for_tool, jobs_summary.jobs )(_) for _ in tool_ids ] downloaded_workflow = self._extract_and_download_workflow( dataset_collection_ids=[ "3" ], job_ids=job_ids, From 04bf2153b8f61c60288cb180d448e7c20352f6e7 Mon Sep 17 00:00:00 2001 From: Nicola Soranzo Date: Wed, 12 Oct 2016 14:01:29 +0100 Subject: [PATCH 19/32] WorkflowSummary: add dict to map a job id to its representative job Fix #2742 introduced in commit 9ede8e556be1f6038361b579c1457068e50e951a . The representative job is the first in the returned query, so substantially random and not necessarily the one with the lowest job id. Mapping a job id to its representative job in extract_steps() makes it robust also against API requests with manually chosen collection job ids. --- lib/galaxy/workflow/extract.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/galaxy/workflow/extract.py b/lib/galaxy/workflow/extract.py index adc61d0e1c9..4b3a3bc9289 100644 --- a/lib/galaxy/workflow/extract.py +++ b/lib/galaxy/workflow/extract.py @@ -102,12 +102,11 @@ def extract_steps( trans, history=None, job_ids=None, dataset_ids=None, dataset_ hid_to_output_pair[ hid ] = ( step, 'output' ) steps.append( step ) # Tool steps - jobs_by_id = dict( ( job.id, job ) for job in jobs.keys() ) for job_id in job_ids: - if job_id not in jobs_by_id: - log.warning( "job_id %s not found in jobs_by_id %s" % ( job_id, jobs_by_id ) ) + if job_id not in summary.job_id2representative_job: + log.warning( "job_id %s not found in job_id2representative_job %s" % ( job_id, summary.job_id2representative_job ) ) raise AssertionError( "Attempt to create workflow with job not connected to current history" ) - job = jobs_by_id[ job_id ] + job = summary.job_id2representative_job[job_id] tool_inputs, associations = step_inputs( trans, job ) step = model.WorkflowStep() step.type = 'tool' @@ -201,6 +200,7 @@ class WorkflowSummary( object ): self.history = history self.warnings = set() self.jobs = odict() + self.job_id2representative_job = {} # map a non-fake job id to its representative job self.implicit_map_jobs = [] self.collection_types = {} @@ -237,6 +237,9 @@ class WorkflowSummary( object ): self.implicit_map_jobs.append( representative_job ) else: self.jobs[ representative_job ].append( ( representative_assoc.name, dataset_collection ) ) + for assoc in cja: + job = assoc.job + self.job_id2representative_job[job.id] = representative_job # This whole elif condition may no longer be needed do to additional # tracking with creating_job_associations. Will delete at some point. elif dataset_collection.implicit_output_name: @@ -257,6 +260,7 @@ class WorkflowSummary( object ): job = assoc.job if job not in self.jobs or self.jobs[ job ][ 0 ][ 1 ].history_content_type == "dataset": self.jobs[ job ] = [ ( assoc.name, dataset_collection ) ] + self.job_id2representative_job[job.id] = job self.implicit_map_jobs.append( job ) else: self.jobs[ job ].append( ( assoc.name, dataset_collection ) ) @@ -279,6 +283,7 @@ class WorkflowSummary( object ): self.jobs[ job ].append( ( assoc.name, dataset ) ) else: self.jobs[ job ] = [ ( assoc.name, dataset ) ] + self.job_id2representative_job[job.id] = job def __original_hdca( self, hdca ): while hdca.copied_from_history_dataset_collection_association: From 77f78cfbc79ddc345bc22277afc335f11e189491 Mon Sep 17 00:00:00 2001 From: Nicola Soranzo Date: Thu, 13 Oct 2016 12:51:36 +0100 Subject: [PATCH 20/32] Change __all__ from list to tuple Also fix import order and Python3 compatibility. --- .ci/flake8_lint_include_list.txt | 17 +++++++++++--- .ci/py3_sources.txt | 12 ++++++++++ lib/galaxy/managers/collections_util.py | 13 +++++------ lib/galaxy/model/item_attrs.py | 8 ++++--- lib/galaxy/model/metadata.py | 23 ++++++++++--------- lib/galaxy/model/orm/now.py | 2 +- lib/galaxy/tools/cwl/__init__.py | 7 +++--- lib/galaxy/tools/cwl/cwltool_deps.py | 7 +++--- lib/galaxy/tools/cwl/parser.py | 16 +++++++------ lib/galaxy/tools/cwl/runtime_actions.py | 6 ++--- lib/galaxy/tools/deps/commands.py | 16 +++++-------- lib/galaxy/tools/deps/conda_compat.py | 8 +++---- lib/galaxy/tools/deps/conda_util.py | 8 +++---- .../tools/deps/container_resolvers/mulled.py | 6 ++--- lib/galaxy/tools/deps/docker_util.py | 23 +++++++++++-------- lib/galaxy/tools/deps/mulled/_cli.py | 6 ++--- lib/galaxy/tools/deps/mulled/mulled_build.py | 10 ++++---- .../tools/deps/mulled/mulled_build_channel.py | 6 ++--- .../tools/deps/mulled/mulled_build_files.py | 3 +-- .../tools/deps/mulled/mulled_build_tool.py | 6 ++--- lib/galaxy/tools/deps/mulled/util.py | 4 ++-- .../resolvers/brewed_tool_shed_packages.py | 3 +-- lib/galaxy/tools/deps/resolvers/conda.py | 2 +- .../tools/deps/resolvers/galaxy_packages.py | 6 ++++- lib/galaxy/tools/deps/resolvers/homebrew.py | 2 +- lib/galaxy/tools/deps/resolvers/modules.py | 3 +-- .../deps/resolvers/tool_shed_packages.py | 3 +-- .../resolvers/unlinked_tool_shed_packages.py | 3 +-- lib/galaxy/tools/exception_handling.py | 4 ++-- lib/galaxy/tools/execute.py | 19 +++++++-------- lib/galaxy/tools/loader.py | 2 +- lib/galaxy/tools/loader_directory.py | 8 +++---- lib/galaxy/tools/parameters/__init__.py | 15 ++++++------ .../tools/parameters/dataset_matcher.py | 5 ++-- lib/galaxy/tools/parameters/wrapped.py | 23 ++++++++++--------- lib/galaxy/tools/parameters/wrapped_json.py | 8 +++---- lib/galaxy/tools/parser/__init__.py | 7 +++--- lib/galaxy/tools/parser/factory.py | 5 ++-- lib/galaxy/tools/toolbox/__init__.py | 11 ++++----- lib/galaxy/tools/toolbox/lineages/__init__.py | 2 +- lib/galaxy/tools/toolbox/lineages/factory.py | 2 +- .../tools/toolbox/lineages/tool_shed.py | 9 ++++---- lib/galaxy/tools/toolbox/parser.py | 21 ++++++++--------- lib/galaxy/workflow/extract.py | 2 +- lib/galaxy/workflow/run.py | 16 ++++++------- lib/galaxy/workflow/schedulers/core.py | 9 ++++---- test/api/workflows_format_2/__init__.py | 4 ++-- test/api/workflows_format_2/converter.py | 4 ++-- test/api/workflows_format_2/main.py | 4 ++-- test/base/driver_util.py | 4 ++-- test/unit/tools_support.py | 18 +++++++-------- test/unit/unittest_utils/utility.py | 11 +++------ 52 files changed, 226 insertions(+), 216 deletions(-) diff --git a/.ci/flake8_lint_include_list.txt b/.ci/flake8_lint_include_list.txt index 343e3818220..0d9cbbba508 100644 --- a/.ci/flake8_lint_include_list.txt +++ b/.ci/flake8_lint_include_list.txt @@ -95,6 +95,7 @@ lib/galaxy/main.py lib/galaxy/managers/annotatable.py lib/galaxy/managers/api_keys.py lib/galaxy/managers/base.py +lib/galaxy/managers/collections_util.py lib/galaxy/managers/context.py lib/galaxy/managers/deletable.py lib/galaxy/managers/__init__.py @@ -103,7 +104,9 @@ lib/galaxy/managers/libraries.py lib/galaxy/managers/secured.py lib/galaxy/managers/taggable.py lib/galaxy/model/__init__.py +lib/galaxy/model/item_attrs.py lib/galaxy/model/mapping.py +lib/galaxy/model/metadata.py lib/galaxy/model/migrate/__init__.py lib/galaxy/model/migrate/versions/0001_initial_tables.py lib/galaxy/model/migrate/versions/0002_metadata_file_table.py @@ -246,9 +249,11 @@ lib/galaxy/sample_tracking/sample.py lib/galaxy/security/validate_user_input.py lib/galaxy/tags/ lib/galaxy/tools/actions/metadata.py -lib/galaxy/tools/cwl +lib/galaxy/tools/cwl/ lib/galaxy/tools/data_manager/__init__.py -lib/galaxy/tools/deps +lib/galaxy/tools/deps/ +lib/galaxy/tools/exception_handling.py +lib/galaxy/tools/execute.py lib/galaxy/tools/filters/ lib/galaxy/tools/imp_exp/export_history.py lib/galaxy/tools/imp_exp/__init__.py @@ -256,11 +261,15 @@ lib/galaxy/tools/linters/ lib/galaxy/tools/lint.py lib/galaxy/tools/lint_util.py lib/galaxy/tools/loader_directory.py +lib/galaxy/tools/loader.py +lib/galaxy/tools/parameters/dataset_matcher.py lib/galaxy/tools/parameters/history_query.py +lib/galaxy/tools/parameters/__init__.py lib/galaxy/tools/parameters/input_translation.py lib/galaxy/tools/parameters/sanitize.py lib/galaxy/tools/parameters/validation.py lib/galaxy/tools/parameters/wrapped_json.py +lib/galaxy/tools/parameters/wrapped.py lib/galaxy/tools/parser/ lib/galaxy/tools/special_tools.py lib/galaxy/tools/test.py @@ -350,7 +359,8 @@ lib/galaxy/web/params.py lib/galaxy/workflow/extract.py lib/galaxy/workflow/__init__.py lib/galaxy/workflow/render.py -lib/galaxy/workflow/schedulers/__init__.py +lib/galaxy/workflow/run.py +lib/galaxy/workflow/schedulers/ lib/galaxy/workflow/steps.py lib/galaxy/work/__init__.py lib/mimeparse.py @@ -518,6 +528,7 @@ test/unit/tools/test_tool_deps.py test/unit/tools/test_tool_loader.py test/unit/tools/test_toolbox_filters.py test/unit/tools/test_watcher.py +test/unit/tools_support.py test/unit/unittest_utils/__init__.py test/unit/unittest_utils/utility.py test/unit/visualizations/__init__.py diff --git a/.ci/py3_sources.txt b/.ci/py3_sources.txt index ecd7f79c9d4..6ff2e8cd0a9 100644 --- a/.ci/py3_sources.txt +++ b/.ci/py3_sources.txt @@ -28,8 +28,11 @@ lib/galaxy/forms/ lib/galaxy/jobs/ lib/galaxy/managers/ lib/galaxy/model/__init__.py +lib/galaxy/model/item_attrs.py lib/galaxy/model/mapping.py +lib/galaxy/model/metadata.py lib/galaxy/model/migrate/ +lib/galaxy/model/orm/now.py lib/galaxy/objectstore/ lib/galaxy/openid/ lib/galaxy/quota/ @@ -38,17 +41,26 @@ lib/galaxy/security/ lib/galaxy/tags/ lib/galaxy/tools/cwl/ lib/galaxy/tools/deps/ +lib/galaxy/tools/exception_handling.py +lib/galaxy/tools/execute.py lib/galaxy/tools/lint.py lib/galaxy/tools/lint_util.py lib/galaxy/tools/linters/ lib/galaxy/tools/loader.py lib/galaxy/tools/loader_directory.py +lib/galaxy/tools/parameters/dataset_matcher.py +lib/galaxy/tools/parameters/__init__.py +lib/galaxy/tools/parameters/wrapped_json.py +lib/galaxy/tools/parameters/wrapped.py lib/galaxy/tools/parser/ lib/galaxy/tools/toolbox/ lib/galaxy/tours/ lib/galaxy/util/ lib/galaxy/visualization/ lib/galaxy/work/ +lib/galaxy/workflow/extract.py +lib/galaxy/workflow/run.py +lib/galaxy/workflow/schedulers/core.py lib/galaxy_ext/ lib/galaxy_utils/ lib/log_tempfile.py diff --git a/lib/galaxy/managers/collections_util.py b/lib/galaxy/managers/collections_util.py index d6938071274..21725877075 100644 --- a/lib/galaxy/managers/collections_util.py +++ b/lib/galaxy/managers/collections_util.py @@ -1,8 +1,7 @@ -from galaxy import exceptions -from galaxy import web -from galaxy import model - import logging + +from galaxy import exceptions, model, web + log = logging.getLogger( __name__ ) ERROR_MESSAGE_UNKNOWN_SRC = "Unknown dataset source (src) %s." @@ -77,7 +76,7 @@ def dictify_dataset_collection_instance( dataset_collection_instance, parent, se dict_value[ 'url' ] = web.url_for( 'library_content', library_id=encoded_library_id, id=encoded_id, folder_id=encoded_folder_id ) if view == "element": collection = dataset_collection_instance.collection - dict_value[ 'elements' ] = map( dictify_element, collection.elements ) + dict_value[ 'elements' ] = [ dictify_element(_) for _ in collection.elements ] dict_value[ 'populated' ] = collection.populated security.encode_all_ids( dict_value, recursive=True ) # TODO: Use Kyle's recursive formulation of this. return dict_value @@ -89,10 +88,10 @@ def dictify_element( element ): if element.child_collection: # Recursively yield elements for each nested collection... child_collection = element.child_collection - object_detials[ "elements" ] = map( dictify_element, child_collection.elements ) + object_detials[ "elements" ] = [ dictify_element(_) for _ in child_collection.elements ] object_detials[ "populated" ] = child_collection.populated dictified[ "object" ] = object_detials return dictified -__all__ = [ api_payload_to_create_params, dictify_dataset_collection_instance ] +__all__ = ( 'api_payload_to_create_params', 'dictify_dataset_collection_instance' ) diff --git a/lib/galaxy/model/item_attrs.py b/lib/galaxy/model/item_attrs.py index dc9666a13af..b4dd1f305d9 100644 --- a/lib/galaxy/model/item_attrs.py +++ b/lib/galaxy/model/item_attrs.py @@ -1,7 +1,9 @@ +import logging + from sqlalchemy.sql.expression import func + # Cannot import galaxy.model b/c it creates a circular import graph. import galaxy -import logging log = logging.getLogger( __name__ ) @@ -162,8 +164,8 @@ class UsesAnnotations: return getattr( galaxy.model, class_name, None ) -__all__ = [ +__all__ = ( 'UsesAnnotations', 'UsesItemRatings', 'RuntimeException', -] +) diff --git a/lib/galaxy/model/metadata.py b/lib/galaxy/model/metadata.py index 2b869213c33..52c459d2503 100644 --- a/lib/galaxy/model/metadata.py +++ b/lib/galaxy/model/metadata.py @@ -4,7 +4,6 @@ Galaxy Metadata """ import copy -import cPickle import json import logging import os @@ -15,6 +14,7 @@ import weakref from os.path import abspath from six import string_types +from six.moves import cPickle from sqlalchemy.orm import object_session import galaxy.model @@ -93,13 +93,14 @@ class MetadataCollection( object ): return default def items(self): - return iter( [ ( k, self.get( k ) ) for k in self.spec.iterkeys() ] ) + return iter( [ ( k, self.get( k ) ) for k in self.spec.keys() ] ) def __str__(self): return dict( self.items() ).__str__() - def __nonzero__( self ): + def __bool__( self ): return bool( self.parent._metadata ) + __nonzero__ = __bool__ def __getattr__( self, name ): if name in self.spec: @@ -205,7 +206,7 @@ class MetadataSpecCollection( odict ): self[item.name] = item def iter( self ): - return self.itervalues() + return iter(self.values()) def __getattr__( self, name ): return self.get( name ) @@ -452,7 +453,7 @@ class RangeParameter( SelectParameter ): other_values = other_values or {} if values is None: - values = zip( range( self.min, self.max, self.step ), range( self.min, self.max, self.step )) + values = list(zip( range( self.min, self.max, self.step ), range( self.min, self.max, self.step ) )) return SelectParameter.get_html_field( self, value=value, context=context, other_values=other_values, values=values, **kwd ) def get_html( self, value, context=None, other_values=None, values=None, **kwd ): @@ -460,7 +461,7 @@ class RangeParameter( SelectParameter ): other_values = other_values or {} if values is None: - values = zip( range( self.min, self.max, self.step ), range( self.min, self.max, self.step )) + values = list(zip( range( self.min, self.max, self.step ), range( self.min, self.max, self.step ) )) return SelectParameter.get_html( self, value, context=context, other_values=other_values, values=values, **kwd ) @classmethod @@ -478,7 +479,7 @@ class ColumnParameter( RangeParameter ): if values is None and context: column_range = range( 1, ( context.columns or 0 ) + 1, 1 ) - values = zip( column_range, column_range ) + values = list(zip( column_range, column_range )) return RangeParameter.get_html_field( self, value=value, context=context, other_values=other_values, values=values, **kwd ) def get_html( self, value, context=None, other_values=None, values=None, **kwd ): @@ -487,7 +488,7 @@ class ColumnParameter( RangeParameter ): if values is None and context: column_range = range( 1, ( context.columns or 0 ) + 1, 1 ) - values = zip( column_range, column_range ) + values = list(zip( column_range, column_range )) return RangeParameter.get_html( self, value, context=context, other_values=other_values, values=values, **kwd ) @@ -815,7 +816,7 @@ class JobExternalOutputMetadataWrapper( object ): metadata_files.filename_override_metadata = abspath( tempfile.NamedTemporaryFile( dir=tmp_dir, prefix="metadata_override_%s_" % key ).name ) open( metadata_files.filename_override_metadata, 'wb+' ) # create the file on disk, so it cannot be reused by tempfile (unlikely, but possible) override_metadata = [] - for meta_key, spec_value in dataset.metadata.spec.iteritems(): + for meta_key, spec_value in dataset.metadata.spec.items(): if isinstance( spec_value.param, FileParameter ) and dataset.metadata.get( meta_key, None ) is not None: metadata_temp = MetadataTempFile() shutil.copy( dataset.metadata.get( meta_key, None ).file_name, metadata_temp.file_name ) @@ -871,7 +872,7 @@ class JobExternalOutputMetadataWrapper( object ): sa_session.add( metadata_files ) sa_session.flush() -__all__ = [ +__all__ = ( "Statement", "MetadataElement", "MetadataCollection", @@ -889,4 +890,4 @@ __all__ = [ "FileParameter", "MetadataTempFile", "JobExternalOutputMetadataWrapper", -] +) diff --git a/lib/galaxy/model/orm/now.py b/lib/galaxy/model/orm/now.py index b6b4de8fa0d..1b90b5bff9a 100644 --- a/lib/galaxy/model/orm/now.py +++ b/lib/galaxy/model/orm/now.py @@ -10,4 +10,4 @@ from datetime import datetime # Return the current time in UTC without any timezone information now = datetime.utcnow -__all__ = [now] +__all__ = ('now', ) diff --git a/lib/galaxy/tools/cwl/__init__.py b/lib/galaxy/tools/cwl/__init__.py index b51eddbfcb8..fe28381120d 100644 --- a/lib/galaxy/tools/cwl/__init__.py +++ b/lib/galaxy/tools/cwl/__init__.py @@ -2,13 +2,12 @@ from .cwltool_deps import ( needs_shell_quoting, shellescape, ) -from .parser import tool_proxy -from .parser import workflow_proxy +from .parser import tool_proxy, workflow_proxy from .representation import to_cwl_job, to_galaxy_parameters from .runtime_actions import handle_outputs -__all__ = [ +__all__ = ( 'tool_proxy', 'workflow_proxy', 'handle_outputs', @@ -16,4 +15,4 @@ __all__ = [ 'to_galaxy_parameters', 'needs_shell_quoting', 'shellescape', -] +) diff --git a/lib/galaxy/tools/cwl/cwltool_deps.py b/lib/galaxy/tools/cwl/cwltool_deps.py index 376fd30f5ca..a2a946c91a1 100644 --- a/lib/galaxy/tools/cwl/cwltool_deps.py +++ b/lib/galaxy/tools/cwl/cwltool_deps.py @@ -4,6 +4,7 @@ Use this as the import interface for cwltool and just call :func:`ensure_cwltool_available` before using any of the imported functionality at runtime. """ +import re try: import requests @@ -40,8 +41,6 @@ except (ImportError, SyntaxError): # Drop SyntaxError once schema_salad supports Python 3 schema_salad = None -import re - needs_shell_quoting = re.compile(r"""(^$|[\s|&;()<>\'"$@])""").search @@ -65,7 +64,7 @@ def ensure_cwltool_available(): raise ImportError(message) -__all__ = [ +__all__ = ( 'main', 'load_tool', 'workflow', @@ -74,4 +73,4 @@ __all__ = [ 'schema_salad', 'shellescape', 'needs_shell_quoting', -] +) diff --git a/lib/galaxy/tools/cwl/parser.py b/lib/galaxy/tools/cwl/parser.py index ff4a56691a6..3e106440933 100644 --- a/lib/galaxy/tools/cwl/parser.py +++ b/lib/galaxy/tools/cwl/parser.py @@ -10,6 +10,8 @@ import logging import os from abc import ABCMeta, abstractmethod +import six + from galaxy.util import safe_makedirs from galaxy.util.bunch import Bunch from galaxy.util.odict import odict @@ -119,8 +121,8 @@ def check_requirements(rec, tool=True): check_requirements(d, tool=tool) +@six.add_metaclass(ABCMeta) class ToolProxy( object ): - __metaclass__ = ABCMeta def __init__(self, tool, tool_path): self._tool = tool @@ -176,7 +178,7 @@ class CommandLineToolProxy(ToolProxy): schema = self._tool.schemaDefs[schema_type] if schema["type"] == "record": - return map(_simple_field_to_input, schema["fields"]) + return [_simple_field_to_input(_) for _ in schema["fields"]] def output_instances(self): outputs_schema = self._tool.outputs_record_schema @@ -235,12 +237,12 @@ class JobProxy(object): def _ensure_cwl_job_initialized(self): if self._cwl_job is None: - self._cwl_job = self._tool_proxy._tool.job( + self._cwl_job = next(self._tool_proxy._tool.job( self._input_dict, self._output_callback, basedir=self._job_directory, use_container=False - ).next() + )) self._is_command_line_job = hasattr(self._cwl_job, "command_line") @property @@ -536,7 +538,7 @@ class ConditionalInstance(object): when=odict(), ) for value, block in self.whens: - as_dict["when"][value] = map(lambda i: i.to_dict(), block) + as_dict["when"][value] = [i.to_dict() for i in block] return as_dict @@ -616,7 +618,7 @@ class OutputInstance(object): self.path = path -__all__ = [ +__all__ = ( 'tool_proxy', 'load_job_proxy', -] +) diff --git a/lib/galaxy/tools/cwl/runtime_actions.py b/lib/galaxy/tools/cwl/runtime_actions.py index 7b596b4ca1b..84fe2c6f339 100644 --- a/lib/galaxy/tools/cwl/runtime_actions.py +++ b/lib/galaxy/tools/cwl/runtime_actions.py @@ -20,7 +20,7 @@ def handle_outputs(job_directory=None): job_proxy = load_job_proxy(job_directory) tool_working_directory = os.path.join(job_directory, "working") outputs = job_proxy.collect_outputs(tool_working_directory) - for output_name, output in outputs.iteritems(): + for output_name, output in outputs.items(): target_path = job_proxy.output_path( output_name ) if isinstance(output, dict) and "path" in output: output_path = output["path"] @@ -48,6 +48,6 @@ def handle_outputs(job_directory=None): with open(target_path, "w") as f: f.write(json.dumps(output)) -__all__ = [ +__all__ = ( 'handle_outputs', -] +) diff --git a/lib/galaxy/tools/deps/commands.py b/lib/galaxy/tools/deps/commands.py index 0a2046c4d9e..3b131f8552d 100644 --- a/lib/galaxy/tools/deps/commands.py +++ b/lib/galaxy/tools/deps/commands.py @@ -3,15 +3,12 @@ import os import subprocess import sys as _sys +from six.moves import shlex_quote + from galaxy.util import which STDOUT_INDICATOR = "-" -try: - from shlex import quote as shell_quote -except ImportError: - from pipes import quote as shell_quote - def redirecting_io(sys=_sys): """Predicate to determine if we are redicting I/O in process.""" @@ -83,11 +80,11 @@ def argv_to_str(command_argv, quote=True): If None appears in the command list it is simply excluded. - Arguments are quoted with shlex.quote. That said, this method is not meant to be + Arguments are quoted with shlex_quote. That said, this method is not meant to be used in security critical paths of code and should not be used to sanitize code. """ - map_func = shell_quote if quote else lambda x: x + map_func = shlex_quote if quote else lambda x: x return " ".join([map_func(c) for c in command_argv if c is not None]) @@ -144,7 +141,7 @@ class CommandLineException(Exception): return self.message -__all__ = [ +__all__ = ( 'argv_to_str', 'CommandLineException', 'download_command', @@ -153,6 +150,5 @@ __all__ = [ 'redirecting_io', 'shell', 'shell_process', - 'shell_quote', 'which', -] +) diff --git a/lib/galaxy/tools/deps/conda_compat.py b/lib/galaxy/tools/deps/conda_compat.py index 1443378f9f5..c45573ef49d 100644 --- a/lib/galaxy/tools/deps/conda_compat.py +++ b/lib/galaxy/tools/deps/conda_compat.py @@ -6,8 +6,8 @@ PyPI. This module serves as a PyPI capable interface to these utilities. """ import collections import os -import yaml +import yaml try: from conda_build.metadata import MetaData @@ -15,7 +15,7 @@ except ImportError: MetaData = None try: - from anaconda_verify.recipe import render_jinja2, parse + from anaconda_verify.recipe import parse, render_jinja2 except ImportError: render_jinja2 = None parse = None @@ -106,7 +106,7 @@ class _MetaData(object): if MetaData is None: MetaData = _MetaData -__all__ = [ +__all__ = ( "MetaData", "raw_metadata", -] +) diff --git a/lib/galaxy/tools/deps/conda_util.py b/lib/galaxy/tools/deps/conda_util.py index 117acca4389..d63dd02055f 100644 --- a/lib/galaxy/tools/deps/conda_util.py +++ b/lib/galaxy/tools/deps/conda_util.py @@ -445,7 +445,7 @@ def filter_installed_targets(conda_targets, conda_context=None, verbose_install_ installed = functools.partial(is_conda_target_installed, conda_context=conda_context, verbose_install_check=verbose_install_check) - return filter(installed, conda_targets) + return list(filter(installed, conda_targets)) def build_isolated_environment( @@ -510,7 +510,7 @@ def requirement_to_conda_targets(requirement, conda_context=None): def requirements_to_conda_targets(requirements, conda_context=None): r_to_ct = functools.partial(requirement_to_conda_targets, conda_context=conda_context) - conda_targets = map(r_to_ct, requirements) + conda_targets = (r_to_ct(_) for _ in requirements) return [c for c in conda_targets if c is not None] @@ -520,10 +520,10 @@ def _ensure_conda_context(conda_context): return conda_context -__all__ = [ +__all__ = ( 'CondaContext', 'CondaTarget', 'install_conda', 'install_conda_target', 'requirements_to_conda_targets', -] +) diff --git a/lib/galaxy/tools/deps/container_resolvers/mulled.py b/lib/galaxy/tools/deps/container_resolvers/mulled.py index 2b2701270ef..3ada6fa08e2 100644 --- a/lib/galaxy/tools/deps/container_resolvers/mulled.py +++ b/lib/galaxy/tools/deps/container_resolvers/mulled.py @@ -58,7 +58,7 @@ def list_cached_mulled_images(namespace=None): return image - return map(output_line_to_image, filter(name_filter, images_and_versions.splitlines())) + return [output_line_to_image(_) for _ in filter(name_filter, images_and_versions.splitlines())] def get_filter(namespace): @@ -200,8 +200,8 @@ def mulled_targets(tool_info): return requirements_to_mulled_targets(tool_info.requirements) -__all__ = [ +__all__ = ( "CachedMulledContainerResolver", "MulledContainerResolver", "BuildMulledContainerResolver", -] +) diff --git a/lib/galaxy/tools/deps/docker_util.py b/lib/galaxy/tools/deps/docker_util.py index c25c2def80b..5d27d2b353a 100644 --- a/lib/galaxy/tools/deps/docker_util.py +++ b/lib/galaxy/tools/deps/docker_util.py @@ -3,7 +3,10 @@ ...using common defaults and configuration mechanisms. """ import os -from .commands import argv_to_str, shell_quote + +from six.moves import shlex_quote + +from .commands import argv_to_str DEFAULT_DOCKER_COMMAND = "docker" DEFAULT_SUDO = True @@ -33,7 +36,7 @@ class DockerVolume(object): if not volumes_as_str: return [] volume_strs = [v.strip() for v in volumes_as_str.split(",")] - return map(DockerVolume.volume_from_str, volume_strs) + return [DockerVolume.volume_from_str(_) for _ in volume_strs] @staticmethod def volume_from_str(as_str): @@ -152,19 +155,19 @@ def build_docker_run_command( if terminal: command_parts.append("-t") for env_directive in env_directives: - command_parts.extend(["-e", shell_quote(env_directive)]) + command_parts.extend(["-e", shlex_quote(env_directive)]) for volume in volumes: - command_parts.extend(["-v", shell_quote(str(volume))]) + command_parts.extend(["-v", shlex_quote(str(volume))]) if volumes_from: - command_parts.extend(["--volumes-from", shell_quote(str(volumes_from))]) + command_parts.extend(["--volumes-from", shlex_quote(str(volumes_from))]) if memory: - command_parts.extend(["-m", shell_quote(memory)]) + command_parts.extend(["-m", shlex_quote(memory)]) if name: - command_parts.extend(["--name", shell_quote(name)]) + command_parts.extend(["--name", shlex_quote(name)]) if working_directory: - command_parts.extend(["-w", shell_quote(working_directory)]) + command_parts.extend(["-w", shlex_quote(working_directory)]) if net: - command_parts.extend(["--net", shell_quote(net)]) + command_parts.extend(["--net", shlex_quote(net)]) if auto_rm: command_parts.append("--rm") if run_extra_arguments: @@ -177,7 +180,7 @@ def build_docker_run_command( full_image = image if tag: full_image = "%s:%s" % (full_image, tag) - command_parts.append(shell_quote(full_image)) + command_parts.append(shlex_quote(full_image)) command_parts.append(container_command) return " ".join(command_parts) diff --git a/lib/galaxy/tools/deps/mulled/_cli.py b/lib/galaxy/tools/deps/mulled/_cli.py index 3cf32bad5de..8cf4e6fc1e3 100644 --- a/lib/galaxy/tools/deps/mulled/_cli.py +++ b/lib/galaxy/tools/deps/mulled/_cli.py @@ -14,6 +14,6 @@ def arg_parser(argv, globals): ) return parser -__all__ = [ - "arg_parser" -] +__all__ = ( + "arg_parser", +) diff --git a/lib/galaxy/tools/deps/mulled/mulled_build.py b/lib/galaxy/tools/deps/mulled/mulled_build.py index e110969b203..cf55e4e46d1 100644 --- a/lib/galaxy/tools/deps/mulled/mulled_build.py +++ b/lib/galaxy/tools/deps/mulled/mulled_build.py @@ -14,17 +14,15 @@ import json import os import string import subprocess - from sys import platform as _platform -from galaxy.tools.deps import commands -from galaxy.tools.deps import installable - try: import yaml except ImportError: yaml = None +from galaxy.tools.deps import commands, installable + from ._cli import arg_parser from .util import build_target, conda_build_target_str, image_name from ..conda_compat import MetaData @@ -236,7 +234,7 @@ def target_str_to_targets(targets_raw): target = build_target(target_str) return target - targets = map(parse_target, targets_raw.split(",")) + targets = [parse_target(_) for _ in targets_raw.split(",")] return targets @@ -281,7 +279,7 @@ def main(argv=None): mull_targets(targets, **args_to_mull_targets_kwds(args)) -__all__ = ["main"] +__all__ = ("main", ) if __name__ == '__main__': diff --git a/lib/galaxy/tools/deps/mulled/mulled_build_channel.py b/lib/galaxy/tools/deps/mulled/mulled_build_channel.py index b5082224649..272940eed7a 100644 --- a/lib/galaxy/tools/deps/mulled/mulled_build_channel.py +++ b/lib/galaxy/tools/deps/mulled/mulled_build_channel.py @@ -16,7 +16,7 @@ See recent changes that would be built with: mulled-build-channel list """ - +from __future__ import print_function import os import time @@ -75,7 +75,7 @@ def run_channel(args, build_last_n_versions=1): def get_pkg_names(args): """Print package names that would be affected.""" - print('\n'.join([pkg_name for pkg_name, pkg_tests in get_affected_packages(args)])) + print('\n'.join(pkg_name for pkg_name, pkg_tests in get_affected_packages(args))) def add_channel_arguments(parser): @@ -102,7 +102,7 @@ def main(argv=None): run_channel(args) -__all__ = ["main"] +__all__ = ("main", ) if __name__ == '__main__': diff --git a/lib/galaxy/tools/deps/mulled/mulled_build_files.py b/lib/galaxy/tools/deps/mulled/mulled_build_files.py index 0fd065c504d..931da7647c1 100644 --- a/lib/galaxy/tools/deps/mulled/mulled_build_files.py +++ b/lib/galaxy/tools/deps/mulled/mulled_build_files.py @@ -17,7 +17,6 @@ import glob import os from ._cli import arg_parser - from .mulled_build import ( add_build_arguments, args_to_mull_targets_kwds, @@ -73,7 +72,7 @@ def _parse_line(line_str): return _Line(*line_parts) -__all__ = ["main"] +__all__ = ("main", ) if __name__ == '__main__': diff --git a/lib/galaxy/tools/deps/mulled/mulled_build_tool.py b/lib/galaxy/tools/deps/mulled/mulled_build_tool.py index 7281c7940ae..fbe353d38c7 100644 --- a/lib/galaxy/tools/deps/mulled/mulled_build_tool.py +++ b/lib/galaxy/tools/deps/mulled/mulled_build_tool.py @@ -40,12 +40,12 @@ def requirements_to_mulled_targets(requirements): Only package requirements are retained. """ - package_requirements = filter(lambda r: r.type == "package", requirements) - targets = map(lambda r: build_target(r.name, r.version), package_requirements) + package_requirements = [r for r in requirements if r.type == "package"] + targets = [build_target(r.name, r.version) for r in package_requirements] return targets -__all__ = ["main", "requirements_to_mulled_targets"] +__all__ = ("main", "requirements_to_mulled_targets") if __name__ == '__main__': diff --git a/lib/galaxy/tools/deps/mulled/util.py b/lib/galaxy/tools/deps/mulled/util.py index a190d0e2192..6ea62f241c9 100644 --- a/lib/galaxy/tools/deps/mulled/util.py +++ b/lib/galaxy/tools/deps/mulled/util.py @@ -105,7 +105,7 @@ def image_name(targets, image_build=None, name_override=None): return "mulled-v1-%s%s" % (m.hexdigest(), suffix) -__all__ = [ +__all__ = ( "build_target", "conda_build_target_str", "image_name", @@ -114,4 +114,4 @@ __all__ = [ "split_tag", "Target", "version_sorted", -] +) diff --git a/lib/galaxy/tools/deps/resolvers/brewed_tool_shed_packages.py b/lib/galaxy/tools/deps/resolvers/brewed_tool_shed_packages.py index 79464e23a46..5585de56c42 100644 --- a/lib/galaxy/tools/deps/resolvers/brewed_tool_shed_packages.py +++ b/lib/galaxy/tools/deps/resolvers/brewed_tool_shed_packages.py @@ -5,7 +5,6 @@ via shed2tap (e.g. https://github.com/jmchilton/homebrew-toolshed). """ import logging import os - from xml.etree import ElementTree as ET from .resolver_mixins import ( @@ -147,4 +146,4 @@ def build_recipe_name(package_name, package_version, repository_owner, repositor return base -__all__ = ['HomebrewToolShedDependencyResolver'] +__all__ = ('HomebrewToolShedDependencyResolver', ) diff --git a/lib/galaxy/tools/deps/resolvers/conda.py b/lib/galaxy/tools/deps/resolvers/conda.py index f16734a268e..1ffe821527f 100644 --- a/lib/galaxy/tools/deps/resolvers/conda.py +++ b/lib/galaxy/tools/deps/resolvers/conda.py @@ -244,4 +244,4 @@ def _string_as_bool( value ): return str( value ).lower() == "true" -__all__ = ['CondaDependencyResolver'] +__all__ = ('CondaDependencyResolver', ) diff --git a/lib/galaxy/tools/deps/resolvers/galaxy_packages.py b/lib/galaxy/tools/deps/resolvers/galaxy_packages.py index 755c8849330..858ff0faf98 100644 --- a/lib/galaxy/tools/deps/resolvers/galaxy_packages.py +++ b/lib/galaxy/tools/deps/resolvers/galaxy_packages.py @@ -122,4 +122,8 @@ def _is_dependency_directory(directory): return exists(join(directory, 'env.sh')) or exists(join(directory, 'bin')) -__all__ = ['GalaxyPackageDependencyResolver', 'GalaxyPackageDependency', 'ToolShedDependency'] +__all__ = ( + 'GalaxyPackageDependency', + 'GalaxyPackageDependencyResolver', + 'ToolShedDependency' +) diff --git a/lib/galaxy/tools/deps/resolvers/homebrew.py b/lib/galaxy/tools/deps/resolvers/homebrew.py index 3d4a741e7b0..395eb1eb518 100644 --- a/lib/galaxy/tools/deps/resolvers/homebrew.py +++ b/lib/galaxy/tools/deps/resolvers/homebrew.py @@ -53,4 +53,4 @@ def _string_as_bool( value ): return str( value ).lower() == "true" -__all__ = ['HomebrewDependencyResolver'] +__all__ = ('HomebrewDependencyResolver', ) diff --git a/lib/galaxy/tools/deps/resolvers/modules.py b/lib/galaxy/tools/deps/resolvers/modules.py index 9ac9c38a8a6..be64e6b7489 100644 --- a/lib/galaxy/tools/deps/resolvers/modules.py +++ b/lib/galaxy/tools/deps/resolvers/modules.py @@ -7,7 +7,6 @@ it, hence support for it will be minimal. The Galaxy team eagerly welcomes community contribution and maintenance however. """ import logging - from os import environ, pathsep from os.path import exists, isdir, join from subprocess import PIPE, Popen @@ -180,4 +179,4 @@ class ModuleDependency(Dependency): def _string_as_bool( value ): return str( value ).lower() == "true" -__all__ = ['ModuleDependencyResolver'] +__all__ = ('ModuleDependencyResolver', ) diff --git a/lib/galaxy/tools/deps/resolvers/tool_shed_packages.py b/lib/galaxy/tools/deps/resolvers/tool_shed_packages.py index f60de4ad805..8b6ddac682f 100644 --- a/lib/galaxy/tools/deps/resolvers/tool_shed_packages.py +++ b/lib/galaxy/tools/deps/resolvers/tool_shed_packages.py @@ -2,7 +2,6 @@ from os.path import abspath, exists, join from .galaxy_packages import BaseGalaxyPackageDependencyResolver, ToolShedDependency from .resolver_mixins import UsesInstalledRepositoriesMixin - from ..resolvers import NullDependency @@ -64,4 +63,4 @@ class ToolShedPackageDependencyResolver(BaseGalaxyPackageDependencyResolver, Use return NullDependency(version=None, name=name) -__all__ = ['ToolShedPackageDependencyResolver'] +__all__ = ('ToolShedPackageDependencyResolver', ) diff --git a/lib/galaxy/tools/deps/resolvers/unlinked_tool_shed_packages.py b/lib/galaxy/tools/deps/resolvers/unlinked_tool_shed_packages.py index dfa7380a48f..c02bcd4a314 100644 --- a/lib/galaxy/tools/deps/resolvers/unlinked_tool_shed_packages.py +++ b/lib/galaxy/tools/deps/resolvers/unlinked_tool_shed_packages.py @@ -19,7 +19,6 @@ See bottom for instructions on how to add this resolver. """ import logging - from os import listdir from os.path import exists, getmtime, join @@ -153,7 +152,7 @@ class CandidateDependency(Dependency): return self.dependency.shell_commands( requirement ) -__all__ = ['UnlinkedToolShedPackageDependencyResolver'] +__all__ = ('UnlinkedToolShedPackageDependencyResolver', ) """ At the time of writing July 3 2015 this resolver has to be plugged in. diff --git a/lib/galaxy/tools/exception_handling.py b/lib/galaxy/tools/exception_handling.py index a9017ad3d3d..0c161622e8c 100644 --- a/lib/galaxy/tools/exception_handling.py +++ b/lib/galaxy/tools/exception_handling.py @@ -1,6 +1,6 @@ # We put a tool that references this package into the tool shed # so we have to provide this legacy location for import indefinitely # it seems. -from galaxy.util.ucsc import UCSCOutWrapper, UCSCLimitException +from galaxy.util.ucsc import UCSCLimitException, UCSCOutWrapper -__all__ = ['UCSCOutWrapper', 'UCSCLimitException'] +__all__ = ('UCSCOutWrapper', 'UCSCLimitException') diff --git a/lib/galaxy/tools/execute.py b/lib/galaxy/tools/execute.py index 39cec6b346a..0a15dc80079 100644 --- a/lib/galaxy/tools/execute.py +++ b/lib/galaxy/tools/execute.py @@ -4,13 +4,15 @@ from various states, tracking results, and building implicit dataset collections from matched collections. """ import collections +import logging +from threading import Thread + +from six.moves.queue import Queue + +from galaxy.tools.actions import on_text_for_names, ToolExecutionCache from galaxy.tools.parser import ToolOutputCollectionPart from galaxy.util import ExecutionTimer -from galaxy.tools.actions import on_text_for_names, ToolExecutionCache -from threading import Thread -from Queue import Queue -import logging log = logging.getLogger( __name__ ) EXECUTION_SUCCESS_MESSAGE = "Tool [%s] created job [%s] %s" @@ -135,7 +137,6 @@ class ToolExecutionTracker( object ): return [] structure = self.collection_info.structure - collections = self.collection_info.collections.values() # params is just one sample tool param execution with parallelized # collection replaced with a specific dataset. Need to replace this @@ -143,13 +144,13 @@ class ToolExecutionTracker( object ): # label. params.update( self.collection_info.collections ) # Replace datasets with source collections for labelling outputs. - collection_names = map( lambda c: "collection %d" % c.hid, collections ) + collection_names = ["collection %d" % c.hid for c in self.collection_info.collections.values()] on_text = on_text_for_names( collection_names ) collections = {} - implicit_inputs = list(self.collection_info.collections.iteritems()) - for output_name, outputs in self.outputs_by_output_name.iteritems(): + implicit_inputs = list(self.collection_info.collections.items()) + for output_name, outputs in self.outputs_by_output_name.items(): if not len( structure ) == len( outputs ): # Output does not have the same structure, if all jobs were # successfully submitted this shouldn't have happened. @@ -202,4 +203,4 @@ class ToolExecutionTracker( object ): trans.sa_session.flush() self.implicit_collections = collections -__all__ = [ execute ] +__all__ = ( 'execute', ) diff --git a/lib/galaxy/tools/loader.py b/lib/galaxy/tools/loader.py index 123e9ef409f..0a8629321f1 100644 --- a/lib/galaxy/tools/loader.py +++ b/lib/galaxy/tools/loader.py @@ -7,4 +7,4 @@ from galaxy.util.xml_macros import ( load_tool = load -__all__ = ["load_tool", "raw_tool_xml_tree", "imported_macro_paths", "template_macro_params"] +__all__ = ("load_tool", "raw_tool_xml_tree", "imported_macro_paths", "template_macro_params") diff --git a/lib/galaxy/tools/loader_directory.py b/lib/galaxy/tools/loader_directory.py index 74b3a3404b7..bf78802d480 100644 --- a/lib/galaxy/tools/loader_directory.py +++ b/lib/galaxy/tools/loader_directory.py @@ -241,11 +241,11 @@ def _find_tool_files(path, recursive, enable_beta_formats): files = glob.glob(path + "/*.xml") else: files = _find_files(path, "*.xml") - return map(os.path.abspath, files) + return [os.path.abspath(_) for _ in files] def _has_extension(path, extensions): - return any(map(lambda e: path.endswith(e), extensions)) + return any(path.endswith(e) for e in extensions) def _find_files(directory, pattern='*'): @@ -266,7 +266,7 @@ BETA_TOOL_CHECKERS = { 'cwl': looks_like_a_tool_cwl, } -__all__ = [ +__all__ = ( "find_possible_tools_from_path", "is_a_yaml_with_class", "is_tool_load_error", @@ -276,4 +276,4 @@ __all__ = [ "looks_like_a_tool_cwl", "looks_like_a_tool_xml", "looks_like_a_tool_yaml", -] +) diff --git a/lib/galaxy/tools/parameters/__init__.py b/lib/galaxy/tools/parameters/__init__.py index cbb58e107f0..2652bf7ca3b 100644 --- a/lib/galaxy/tools/parameters/__init__.py +++ b/lib/galaxy/tools/parameters/__init__.py @@ -4,15 +4,16 @@ Classes encapsulating Galaxy tool parameters. import re from json import dumps, loads -from basic import RuntimeValue, DataCollectionToolParameter, DataToolParameter, SelectToolParameter -from grouping import Conditional, Repeat, Section, UploadDataset from galaxy.util.expressions import ExpressionContext from galaxy.util.json import json_fix +from .basic import DataCollectionToolParameter, DataToolParameter, RuntimeValue, SelectToolParameter +from .grouping import Conditional, Repeat, Section, UploadDataset + REPLACE_ON_TRUTHY = object() # Some tools use the code tag and access the code base, expecting certain tool parameters to be available here. -__all__ = [ DataCollectionToolParameter, DataToolParameter, SelectToolParameter ] +__all__ = ( 'DataCollectionToolParameter', 'DataToolParameter', 'SelectToolParameter' ) def visit_input_values( inputs, input_values, callback, name_prefix='', label_prefix='', parent_prefix='', context=None, no_replacement_value=REPLACE_ON_TRUTHY ): @@ -82,7 +83,7 @@ def visit_input_values( inputs, input_values, callback, name_prefix='', label_pr context = ExpressionContext( input_values, context ) payload = { 'context': context, 'no_replacement_value': no_replacement_value } - for input in inputs.itervalues(): + for input in inputs.values(): if isinstance( input, Repeat ) or isinstance( input, UploadDataset ): values = input_values[ input.name ] = input_values.get( input.name, [] ) for i, d in enumerate( values ): @@ -143,7 +144,7 @@ def params_to_strings( params, param_values, app ): such). """ rval = dict() - for key, value in param_values.iteritems(): + for key, value in param_values.items(): if key in params: value = params[ key ].value_to_basic( value, app ) rval[ key ] = str( dumps( value ) ) @@ -158,7 +159,7 @@ def params_from_strings( params, param_values, app, ignore_errors=False ): preferred form). """ rval = dict() - for key, value in param_values.iteritems(): + for key, value in param_values.items(): value = json_fix( loads( value ) ) if key in params: value = params[ key ].value_from_basic( value, app, ignore_errors ) @@ -173,7 +174,7 @@ def params_to_incoming( incoming, inputs, input_values, app, name_prefix="" ): Useful for e.g. the rerun function. """ - for input in inputs.itervalues(): + for input in inputs.values(): if isinstance( input, Repeat ) or isinstance( input, UploadDataset ): for d in input_values[ input.name ]: index = d[ '__index__' ] diff --git a/lib/galaxy/tools/parameters/dataset_matcher.py b/lib/galaxy/tools/parameters/dataset_matcher.py index ca06e1e5d61..6ff696e6880 100644 --- a/lib/galaxy/tools/parameters/dataset_matcher.py +++ b/lib/galaxy/tools/parameters/dataset_matcher.py @@ -1,6 +1,7 @@ +from logging import getLogger + import galaxy.model -from logging import getLogger log = getLogger( __name__ ) ROLES_UNSET = object() @@ -179,4 +180,4 @@ class DatasetCollectionMatcher( object ): break return valid -__all__ = [ DatasetMatcher, DatasetCollectionMatcher ] +__all__ = ( 'DatasetMatcher', 'DatasetCollectionMatcher' ) diff --git a/lib/galaxy/tools/parameters/wrapped.py b/lib/galaxy/tools/parameters/wrapped.py index a193088b2f5..b6dbebdedd8 100644 --- a/lib/galaxy/tools/parameters/wrapped.py +++ b/lib/galaxy/tools/parameters/wrapped.py @@ -1,20 +1,21 @@ from galaxy.tools.parameters.basic import ( - DataToolParameter, DataCollectionToolParameter, + DataToolParameter, SelectToolParameter ) -from galaxy.tools.wrappers import ( - InputValueWrapper, - SelectToolParameterWrapper, - DatasetFilenameWrapper, - DatasetListWrapper, - DatasetCollectionWrapper -) from galaxy.tools.parameters.grouping import ( - Repeat, Conditional, + Repeat, Section ) +from galaxy.tools.wrappers import ( + DatasetCollectionWrapper, + DatasetFilenameWrapper, + DatasetListWrapper, + InputValueWrapper, + SelectToolParameterWrapper +) + PARAMS_UNWRAPPED = object() @@ -40,7 +41,7 @@ class WrappedParameters( object ): incoming = self.incoming # Wrap tool inputs as necessary - for input in inputs.itervalues(): + for input in inputs.values(): if input.name not in input_values and skip_missing_values: continue value = input_values[ input.name ] @@ -112,4 +113,4 @@ def make_list_copy( from_list ): return new_list -__all__ = [ 'WrappedParameters', 'make_dict_copy' ] +__all__ = ( 'WrappedParameters', 'make_dict_copy' ) diff --git a/lib/galaxy/tools/parameters/wrapped_json.py b/lib/galaxy/tools/parameters/wrapped_json.py index d5d8a5afdf0..b5b4989776a 100644 --- a/lib/galaxy/tools/parameters/wrapped_json.py +++ b/lib/galaxy/tools/parameters/wrapped_json.py @@ -1,6 +1,6 @@ import logging -log = logging.getLogger(__name__) +log = logging.getLogger(__name__) SKIP_INPUT = object() @@ -9,7 +9,7 @@ def json_wrap(inputs, input_values, as_dict=None, handle_files="SKIP"): if as_dict is None: as_dict = {} - for input in inputs.itervalues(): + for input in inputs.values(): input_name = input.name value = input_values[input_name] json_value = _json_wrap_input(input, value, handle_files=handle_files) @@ -65,7 +65,7 @@ def _json_wrap_input(input, value, handle_files="SKIP"): json_value = _cast_if_not_none(value, bool) elif input_type == "data_column": # value is a SelectToolParameterWrapper() - json_value = map(int, _cast_if_not_none(value.value, list)) + json_value = [int(_) for _ in _cast_if_not_none(value.value, list)] else: raise NotImplementedError("input_type [%s] not implemented" % input_type) @@ -80,4 +80,4 @@ def _cast_if_not_none(value, cast_to, empty_to_none=False): return cast_to(value) -__all__ = ['json_wrap'] +__all__ = ('json_wrap', ) diff --git a/lib/galaxy/tools/parser/__init__.py b/lib/galaxy/tools/parser/__init__.py index 7ef870ceec0..7f7fdf15579 100644 --- a/lib/galaxy/tools/parser/__init__.py +++ b/lib/galaxy/tools/parser/__init__.py @@ -1,15 +1,14 @@ """ Package responsible for parsing tools from files/abstract tool sources. """ -from .factory import get_input_source -from .factory import get_tool_source +from .factory import get_input_source, get_tool_source from .interface import ToolSource from .output_objects import ( ToolOutputCollectionPart, ) -__all__ = [ +__all__ = ( "get_input_source", "get_tool_source", "ToolOutputCollectionPart", "ToolSource", -] +) diff --git a/lib/galaxy/tools/parser/factory.py b/lib/galaxy/tools/parser/factory.py index e72e4ec1f1b..d38949f8498 100644 --- a/lib/galaxy/tools/parser/factory.py +++ b/lib/galaxy/tools/parser/factory.py @@ -10,8 +10,7 @@ from galaxy.util.odict import odict from .cwl import CwlToolSource from .interface import InputSource -from .xml import XmlInputSource -from .xml import XmlToolSource +from .xml import XmlInputSource, XmlToolSource from .yaml import YamlToolSource log = logging.getLogger(__name__) @@ -72,4 +71,4 @@ def get_input_source(content): return content -__all__ = ["get_tool_source", "get_input_source"] +__all__ = ("get_tool_source", "get_input_source") diff --git a/lib/galaxy/tools/toolbox/__init__.py b/lib/galaxy/tools/toolbox/__init__.py index 4983a5331fa..604c4e49231 100644 --- a/lib/galaxy/tools/toolbox/__init__.py +++ b/lib/galaxy/tools/toolbox/__init__.py @@ -1,16 +1,13 @@ """API for this module containing functionality related to the toolbox.""" -from .base import AbstractToolBox -from .base import BaseGalaxyToolBox +from .base import AbstractToolBox, BaseGalaxyToolBox -from .panel import panel_item_types -from .panel import ToolSection -from .panel import ToolSectionLabel +from .panel import panel_item_types, ToolSection, ToolSectionLabel -__all__ = [ +__all__ = ( "AbstractToolBox", "BaseGalaxyToolBox", "panel_item_types", "ToolSection", "ToolSectionLabel", -] +) diff --git a/lib/galaxy/tools/toolbox/lineages/__init__.py b/lib/galaxy/tools/toolbox/lineages/__init__.py index d178d4f96ab..f79c808b79b 100644 --- a/lib/galaxy/tools/toolbox/lineages/__init__.py +++ b/lib/galaxy/tools/toolbox/lineages/__init__.py @@ -3,4 +3,4 @@ from .interface import ToolLineage from .tool_shed import ToolVersionCache -__all__ = ["LineageMap", "ToolLineage", "ToolVersionCache"] +__all__ = ("LineageMap", "ToolLineage", "ToolVersionCache") diff --git a/lib/galaxy/tools/toolbox/lineages/factory.py b/lib/galaxy/tools/toolbox/lineages/factory.py index bd1f049e0d0..2da533471c6 100644 --- a/lib/galaxy/tools/toolbox/lineages/factory.py +++ b/lib/galaxy/tools/toolbox/lineages/factory.py @@ -46,4 +46,4 @@ class LineageMap(object): return self.lineage_map.get(versionless_tool_id, None) -__all__ = ["LineageMap"] +__all__ = ("LineageMap", ) diff --git a/lib/galaxy/tools/toolbox/lineages/tool_shed.py b/lib/galaxy/tools/toolbox/lineages/tool_shed.py index 347b35591c5..a2412d12ad3 100644 --- a/lib/galaxy/tools/toolbox/lineages/tool_shed.py +++ b/lib/galaxy/tools/toolbox/lineages/tool_shed.py @@ -1,5 +1,4 @@ -from .interface import ToolLineage -from .interface import ToolLineageVersion +from .interface import ToolLineage, ToolLineageVersion try: from galaxy.model.tool_shed_install import ToolVersion @@ -78,13 +77,13 @@ class ToolShedLineage(ToolLineage): return result def get_versions( self, reverse=False ): - return map( ToolLineageVersion.from_guid, self.get_version_ids( reverse=reverse ) ) + return [ ToolLineageVersion.from_guid(_) for _ in self.get_version_ids( reverse=reverse ) ] def to_dict(self): tool_shed_repository = self._tool_shed_repository rval = dict( tool_version_id=self.tool_version_id, - tool_versions=map(lambda v: v.to_dict(), self.get_versions()), + tool_versions=[v.to_dict() for v in self.get_versions()], tool_shed_repository=tool_shed_repository if tool_shed_repository is not None else None, lineage_type='tool_shed', ) @@ -94,4 +93,4 @@ class ToolShedLineage(ToolLineage): def get_installed_tool_version( app, tool_id ): return app.tool_version_cache.tool_version_by_tool_id.get(tool_id, None) -__all__ = [ "ToolShedLineage" ] +__all__ = ( "ToolShedLineage", ) diff --git a/lib/galaxy/tools/toolbox/parser.py b/lib/galaxy/tools/toolbox/parser.py index 834ebdd450b..8023988d9ba 100644 --- a/lib/galaxy/tools/toolbox/parser.py +++ b/lib/galaxy/tools/toolbox/parser.py @@ -3,9 +3,9 @@ These files define tool lists, sections, labels, etc... the elements of the Galaxy tool panel. """ -from abc import ABCMeta -from abc import abstractmethod +from abc import ABCMeta, abstractmethod +import six import yaml from galaxy.util import parse_xml, string_as_bool @@ -13,11 +13,10 @@ from galaxy.util import parse_xml, string_as_bool DEFAULT_MONITOR = False +@six.add_metaclass(ABCMeta) class ToolConfSource(object): """Interface represents a container of tool references.""" - __metaclass__ = ABCMeta - @abstractmethod def parse_items(self): """Return a list of ToolConfItem describing source.""" @@ -45,7 +44,7 @@ class XmlToolConfSource(ToolConfSource): return self.root.get('tool_path') def parse_items(self): - return map(ensure_tool_conf_item, self.root.getchildren()) + return [ensure_tool_conf_item(_) for _ in self.root.getchildren()] def is_shed_tool_conf(self): has_tool_path = self.parse_tool_path() is not None @@ -67,7 +66,7 @@ class YamlToolConfSource(ToolConfSource): return self.as_dict.get('tool_path') def parse_items(self): - return map(ToolConfItem.from_dict, self.as_dict.get('items')) + return [ToolConfItem.from_dict(_) for _ in self.as_dict.get('items')] def parse_monitor(self): return self.as_dict.get('monitor', DEFAULT_MONITOR) @@ -94,7 +93,7 @@ class ToolConfItem(object): del as_dict['type'] attributes = as_dict if type == 'section': - items = map(cls.from_dict, as_dict['items']) + items = [cls.from_dict(_) for _ in as_dict['items']] del as_dict['items'] item = ToolConfSection(attributes, items) else: @@ -141,18 +140,18 @@ def ensure_tool_conf_item(xml_or_item): if type != "section": return ToolConfItem(type, attributes, elem) else: - items = map(ensure_tool_conf_item, elem.getchildren()) + items = [ensure_tool_conf_item(_) for _ in elem.getchildren()] return ToolConfSection(attributes, items, elem=elem) def get_toolbox_parser(config_filename): - is_yaml = any(map(lambda e: config_filename.endswith(e), [".yml", ".yaml", ".json"])) + is_yaml = any(config_filename.endswith(e) for e in [".yml", ".yaml", ".json"]) if is_yaml: return YamlToolConfSource(config_filename) else: return XmlToolConfSource(config_filename) -__all__ = [ +__all__ = ( "get_toolbox_parser", "ensure_tool_conf_item", -] +) diff --git a/lib/galaxy/workflow/extract.py b/lib/galaxy/workflow/extract.py index 4b3a3bc9289..4f47835fd76 100644 --- a/lib/galaxy/workflow/extract.py +++ b/lib/galaxy/workflow/extract.py @@ -371,4 +371,4 @@ def __cleanup_param_values( inputs, values ): cleanup( "", inputs, values ) return associations -__all__ = [ summarize, extract_workflow ] +__all__ = ( 'summarize', 'extract_workflow' ) diff --git a/lib/galaxy/workflow/run.py b/lib/galaxy/workflow/run.py index 8b99cf75e95..6061254f58c 100644 --- a/lib/galaxy/workflow/run.py +++ b/lib/galaxy/workflow/run.py @@ -1,15 +1,13 @@ +import logging import uuid -from galaxy import model -from galaxy import util - -from galaxy.util.odict import odict +from galaxy import model, util from galaxy.util import ExecutionTimer +from galaxy.util.odict import odict from galaxy.workflow import modules -from galaxy.workflow.run_request import WorkflowRunConfig -from galaxy.workflow.run_request import workflow_run_config_to_request +from galaxy.workflow.run_request import (workflow_run_config_to_request, + WorkflowRunConfig) -import logging log = logging.getLogger( __name__ ) @@ -253,7 +251,7 @@ class WorkflowProgress( object ): self.module_injector.inject( step ) if step_id not in step_states: template = "Workflow invocation [%s] has no step state for step id [%s]. States ids are %s." - message = template % (self.workflow_invocation.id, step_id, step_states.keys()) + message = template % (self.workflow_invocation.id, step_id, list(step_states.keys())) raise Exception(message) runtime_state = step_states[ step_id ].value step.state = step.module.recover_runtime_state( runtime_state ) @@ -399,4 +397,4 @@ class WorkflowProgress( object ): except modules.DelayedWorkflowEvaluation: self.mark_step_outputs_delayed( step ) -__all__ = [ invoke, WorkflowRunConfig ] +__all__ = ( 'invoke', 'WorkflowRunConfig' ) diff --git a/lib/galaxy/workflow/schedulers/core.py b/lib/galaxy/workflow/schedulers/core.py index 7a111a7cab1..e513a258ed9 100644 --- a/lib/galaxy/workflow/schedulers/core.py +++ b/lib/galaxy/workflow/schedulers/core.py @@ -1,14 +1,13 @@ """ The class defines the stock Galaxy workflow scheduling plugin - currently it simply schedules the whole workflow up front when offered. """ -from ..schedulers import ActiveWorkflowSchedulingPlugin +import logging from galaxy.work import context +from galaxy.workflow import run, run_request -from galaxy.workflow import run -from galaxy.workflow import run_request +from ..schedulers import ActiveWorkflowSchedulingPlugin -import logging log = logging.getLogger( __name__ ) @@ -43,4 +42,4 @@ class CoreWorkflowSchedulingPlugin( ActiveWorkflowSchedulingPlugin ): workflow_invocation=workflow_invocation, ) -__all__ = [ 'CoreWorkflowSchedulingPlugin' ] +__all__ = ( 'CoreWorkflowSchedulingPlugin', ) diff --git a/test/api/workflows_format_2/__init__.py b/test/api/workflows_format_2/__init__.py index e680a3db612..c5e73c59a2d 100644 --- a/test/api/workflows_format_2/__init__.py +++ b/test/api/workflows_format_2/__init__.py @@ -5,7 +5,7 @@ from .interface import ImporterGalaxyInterface from .main import convert_and_import_workflow -__all__ = [ +__all__ = ( 'convert_and_import_workflow', 'ImporterGalaxyInterface', -] +) diff --git a/test/api/workflows_format_2/converter.py b/test/api/workflows_format_2/converter.py index 61ef54e6685..05a9a319c49 100644 --- a/test/api/workflows_format_2/converter.py +++ b/test/api/workflows_format_2/converter.py @@ -512,7 +512,7 @@ def main(argv): if __name__ == "__main__": main(sys.argv) -__all__ = [ +__all__ = ( 'yaml_to_workflow', 'python_to_workflow', -] +) diff --git a/test/api/workflows_format_2/main.py b/test/api/workflows_format_2/main.py index e147c52ec23..0a79382ea5e 100644 --- a/test/api/workflows_format_2/main.py +++ b/test/api/workflows_format_2/main.py @@ -36,6 +36,6 @@ def convert_and_import_workflow(has_workflow, **kwds): import_kwds["publish"] = True return galaxy_interface.import_workflow(workflow, **import_kwds) -__all__ = [ +__all__ = ( 'convert_and_import_workflow', -] +) diff --git a/test/base/driver_util.py b/test/base/driver_util.py index 977a88a3d0a..0b2e8b5585b 100644 --- a/test/base/driver_util.py +++ b/test/base/driver_util.py @@ -692,7 +692,7 @@ def drive_test(test_driver_class): sys.exit(test_driver_class().run()) -__all__ = [ +__all__ = ( "copy_database_template", "build_logger", "drive_test", @@ -705,4 +705,4 @@ __all__ = [ "setup_galaxy_config", "TestDriver", "wait_for_http_server", -] +) diff --git a/test/unit/tools_support.py b/test/unit/tools_support.py index a3d2528ed5d..51f23cb4ccc 100644 --- a/test/unit/tools_support.py +++ b/test/unit/tools_support.py @@ -3,22 +3,22 @@ tool evaluation. Such extensive "fixtures" are something of an anti-pattern so use of this should be limitted to tests of very 'extensive' classes. """ -from collections import defaultdict import os.path -import tempfile import shutil import string +import tempfile +from collections import defaultdict -from galaxy.util.bunch import Bunch -from galaxy.web.security import SecurityHelper +import galaxy.datatypes.registry import galaxy.model +from galaxy.jobs import NoopQueue from galaxy.model import mapping from galaxy.tools import Tool -from galaxy.util.dbkeys import GenomeBuilds -from galaxy.jobs import NoopQueue -from galaxy.tools.parser import get_tool_source from galaxy.tools.deps.containers import NullContainerFinder -import galaxy.datatypes.registry +from galaxy.tools.parser import get_tool_source +from galaxy.util.bunch import Bunch +from galaxy.util.dbkeys import GenomeBuilds +from galaxy.web.security import SecurityHelper datatypes_registry = galaxy.datatypes.registry.Registry() datatypes_registry.load_datatypes() @@ -207,4 +207,4 @@ class MockQuery(object): return self.class_objects.get(id, None) -__all__ = [ UsesApp ] +__all__ = ( 'UsesApp', ) diff --git a/test/unit/unittest_utils/utility.py b/test/unit/unittest_utils/utility.py index 3341ee3879b..465fd4c42c9 100644 --- a/test/unit/unittest_utils/utility.py +++ b/test/unit/unittest_utils/utility.py @@ -4,12 +4,7 @@ Unit test utilities. import os import sys import textwrap - -from sys import version_info -if version_info < (2, 7): - import unittest2 as unittest -else: - import unittest as unittest +import unittest # ============================================================================= @@ -45,10 +40,10 @@ def clean_multiline_string( multiline_string, sep='\n' ): sys.path[1:1] = [ get_galaxy_libpath(), get_unittest_utils_path() ] -__all__ = [ +__all__ = ( "clean_multiline_string", "get_unittest_utils_path", "get_galaxy_root", "get_galaxy_libpath", "unittest", -] +) From bb4428896839898fb5d0c9b88ae18fc9b705a27b Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Thu, 13 Oct 2016 09:54:55 -0400 Subject: [PATCH 21/32] Failthrough to using imghdr when PIL does not detect an image correctly. Some misc cleanup, avoiding use of system functions as variable names, etc. --- lib/galaxy/datatypes/images.py | 8 --- lib/galaxy/datatypes/util/image_util.py | 71 ++++++++++++------------- lib/galaxy/util/checkers.py | 23 ++------ tools/data_source/upload.py | 8 +-- 4 files changed, 39 insertions(+), 71 deletions(-) diff --git a/lib/galaxy/datatypes/images.py b/lib/galaxy/datatypes/images.py index 6832790e842..fe5f87b18fc 100644 --- a/lib/galaxy/datatypes/images.py +++ b/lib/galaxy/datatypes/images.py @@ -12,14 +12,6 @@ from galaxy.datatypes.util.image_util import check_image_type from galaxy.util import nice_size from . import data -try: - import Image as PIL -except ImportError: - try: - from PIL import Image as PIL - except: - PIL = None - log = logging.getLogger(__name__) # TODO: Uploading image files of various types is supported in Galaxy, but on diff --git a/lib/galaxy/datatypes/util/image_util.py b/lib/galaxy/datatypes/util/image_util.py index 1d825b80111..89c397d407e 100644 --- a/lib/galaxy/datatypes/util/image_util.py +++ b/lib/galaxy/datatypes/util/image_util.py @@ -13,66 +13,63 @@ except ImportError: log = logging.getLogger(__name__) -def image_type( filename, image=None ): - format = '' +def image_type( filename ): + fmt = None if PIL is not None: - if image is not None: - format = image.format - else: - try: - im = PIL.open( filename ) - format = im.format - im.close() - except: - return False + try: + im = PIL.open( filename ) + fmt = im.format + im.close() + except: + # We continue to try with imghdr, so this is a rare case of an + # exception we expect to happen frequently, so we're not logging + pass + if not fmt: + fmt = imghdr.what( filename ) + if fmt: + return fmt.upper() else: - format = imghdr.what( filename ) - if format is not None: - format = format.upper() - else: - return False - return format + return False def check_image_type( filename, types, image=None ): - format = image_type( filename, image ) - # First check if we can use PIL - if format in types: + fmt = image_type( filename, image ) + if fmt in types: return True return False -def get_image_ext( file_path, image ): +def get_image_ext( file_path ): # determine ext - format = image_type( file_path, image ) - if format in [ 'JPG', 'JPEG' ]: + fmt = image_type( file_path ) + if fmt in [ 'JPG', 'JPEG' ]: return 'jpg' - if format == 'PNG': + if fmt == 'PNG': return 'png' - if format == 'TIFF': + if fmt == 'TIFF': return 'tiff' - if format == 'BMP': + if fmt == 'BMP': return 'bmp' - if format == 'GIF': + if fmt == 'GIF': return 'gif' - if format == 'IM': + if fmt == 'IM': return 'im' - if format == 'PCD': + if fmt == 'PCD': return 'pcd' - if format == 'PCX': + if fmt == 'PCX': return 'pcx' - if format == 'PPM': + if fmt == 'PPM': return 'ppm' - if format == 'PSD': + if fmt == 'PSD': return 'psd' - if format == 'XBM': + if fmt == 'XBM': return 'xbm' - if format == 'XPM': + if fmt == 'XPM': return 'xpm' - if format == 'RGB': + if fmt == 'RGB': return 'rgb' - if format == 'PBM': + if fmt == 'PBM': return 'pbm' - if format == 'PGM': + if fmt == 'PGM': return 'pgm' return None diff --git a/lib/galaxy/util/checkers.py b/lib/galaxy/util/checkers.py index 9cf66ea7c51..6d187b5dad8 100644 --- a/lib/galaxy/util/checkers.py +++ b/lib/galaxy/util/checkers.py @@ -10,28 +10,11 @@ from galaxy import util HTML_CHECK_LINES = 100 -try: - import Image as PIL -except ImportError: - try: - from PIL import Image as PIL - except: - PIL = None - def check_image( file_path ): - if PIL is not None: - try: - im = PIL.open( file_path ) - except: - return False - if im: - return im - return False - else: - if imghdr.what( file_path ) is not None: - return True - return False + if imghdr.what( file_path ) is not None: + return True + return False def check_html( file_path, chunk=None ): diff --git a/tools/data_source/upload.py b/tools/data_source/upload.py index a123aa327f2..f725cf19782 100644 --- a/tools/data_source/upload.py +++ b/tools/data_source/upload.py @@ -116,12 +116,8 @@ def add_file( dataset, registry, json_file, output_path ): except UnicodeDecodeError as e: dataset.is_multi_byte = False # Is dataset an image? - image = check_image( dataset.path ) - if image: - if not PIL: - image = None - # get_image_ext() returns None if nor a supported Image type - ext = get_image_ext( dataset.path, image ) + if check_image( dataset.path ): + ext = get_image_ext( dataset.path ) data_type = ext # Is dataset content multi-byte? elif dataset.is_multi_byte: From cb6e25278c4a59d4ea2edd9ce5e9c757b1ebf7f8 Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Thu, 13 Oct 2016 10:25:18 -0400 Subject: [PATCH 22/32] Refactoring, because it was bugging me. --- lib/galaxy/datatypes/images.py | 2 +- lib/galaxy/datatypes/sequence.py | 2 +- lib/galaxy/util/checkers.py | 15 ++++++++------- lib/galaxy/{datatypes => }/util/image_util.py | 0 tools/data_source/upload.py | 16 +++++----------- 5 files changed, 15 insertions(+), 20 deletions(-) rename lib/galaxy/{datatypes => }/util/image_util.py (100%) diff --git a/lib/galaxy/datatypes/images.py b/lib/galaxy/datatypes/images.py index fe5f87b18fc..f3858ccc68e 100644 --- a/lib/galaxy/datatypes/images.py +++ b/lib/galaxy/datatypes/images.py @@ -8,8 +8,8 @@ from six.moves.urllib.parse import quote_plus from galaxy.datatypes.binary import Binary from galaxy.datatypes.sniff import get_headers from galaxy.datatypes.text import Html as HtmlFromText -from galaxy.datatypes.util.image_util import check_image_type from galaxy.util import nice_size +from galaxy.util.image_util import check_image_type from . import data log = logging.getLogger(__name__) diff --git a/lib/galaxy/datatypes/sequence.py b/lib/galaxy/datatypes/sequence.py index 1b4619d731e..a6d67f2af32 100644 --- a/lib/galaxy/datatypes/sequence.py +++ b/lib/galaxy/datatypes/sequence.py @@ -15,9 +15,9 @@ from six import PY3 from galaxy import util from galaxy.datatypes import metadata from galaxy.util.checkers import is_gzip +from galaxy.util.image_util import check_image_type from galaxy.datatypes.metadata import MetadataElement from galaxy.datatypes.sniff import get_headers -from galaxy.datatypes.util.image_util import check_image_type from galaxy.util import nice_size from . import data diff --git a/lib/galaxy/util/checkers.py b/lib/galaxy/util/checkers.py index 6d187b5dad8..daf8866424b 100644 --- a/lib/galaxy/util/checkers.py +++ b/lib/galaxy/util/checkers.py @@ -1,22 +1,16 @@ import bz2 import gzip -import imghdr import re import zipfile from six import StringIO from galaxy import util +from galaxy.util.image_util import image_type HTML_CHECK_LINES = 100 -def check_image( file_path ): - if imghdr.what( file_path ) is not None: - return True - return False - - def check_html( file_path, chunk=None ): if chunk is None: temp = open( file_path, "U" ) @@ -126,6 +120,13 @@ def is_gzip( file_path ): return is_gzipped +def check_image( file_path ): + """ Simple wrapper around image_type to yield a True/False verdict """ + if image_type( file_path ): + return True + return False + + __all__ = [ 'check_binary', 'check_bz2', diff --git a/lib/galaxy/datatypes/util/image_util.py b/lib/galaxy/util/image_util.py similarity index 100% rename from lib/galaxy/datatypes/util/image_util.py rename to lib/galaxy/util/image_util.py diff --git a/tools/data_source/upload.py b/tools/data_source/upload.py index f725cf19782..85355c3eb3b 100644 --- a/tools/data_source/upload.py +++ b/tools/data_source/upload.py @@ -20,17 +20,10 @@ from galaxy import util from galaxy.datatypes import sniff from galaxy.datatypes.binary import Binary from galaxy.datatypes.registry import Registry -from galaxy.datatypes.util.image_util import get_image_ext from galaxy.util import multi_byte -from galaxy.util.checkers import check_binary, check_bz2, check_gzip, check_html, check_image, check_zip +from galaxy.util.checkers import check_binary, check_bz2, check_gzip, check_html, check_zip +from galaxy.util.image_util import get_image_ext -try: - import Image as PIL -except ImportError: - try: - from PIL import Image as PIL - except: - PIL = None try: import bz2 @@ -116,8 +109,9 @@ def add_file( dataset, registry, json_file, output_path ): except UnicodeDecodeError as e: dataset.is_multi_byte = False # Is dataset an image? - if check_image( dataset.path ): - ext = get_image_ext( dataset.path ) + i_ext = get_image_ext( dataset.path ) + if i_ext: + ext = i_ext data_type = ext # Is dataset content multi-byte? elif dataset.is_multi_byte: From ba07956117e5e4527fa5d2837da2c267f6ab981d Mon Sep 17 00:00:00 2001 From: Daniel Blankenberg Date: Thu, 13 Oct 2016 10:40:40 -0400 Subject: [PATCH 23/32] Fix tabular filtering tool broken in #2983 --- tools/stats/filtering.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/stats/filtering.py b/tools/stats/filtering.py index 94640ef61ec..c6781e3b598 100644 --- a/tools/stats/filtering.py +++ b/tools/stats/filtering.py @@ -219,7 +219,7 @@ for i, line in enumerate( open( in_fname ) ): if i < num_header_lines: lines_kept += 1 - print >> out, line + print( line, file=out ) continue if not line or line.startswith( '#' ): @@ -230,7 +230,7 @@ for i, line in enumerate( open( in_fname ) ): %s if %s: lines_kept += 1 - print >> out, line + print( line, file=out ) except: invalid_lines += 1 if not invalid_line: From 5d294ea7d3a14561a15c30f3527dfcd21d517369 Mon Sep 17 00:00:00 2001 From: Daniel Blankenberg Date: Thu, 13 Oct 2016 10:54:41 -0400 Subject: [PATCH 24/32] Fix GFF attribute filtering tool broken in #2983 --- tools/filters/gff/gff_filter_by_attribute.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/filters/gff/gff_filter_by_attribute.py b/tools/filters/gff/gff_filter_by_attribute.py index 7d5051dfd9e..452e812e895 100644 --- a/tools/filters/gff/gff_filter_by_attribute.py +++ b/tools/filters/gff/gff_filter_by_attribute.py @@ -125,9 +125,9 @@ for i, line in enumerate( open( in_fname ) ): %s if %s: lines_kept += 1 - print >> out, line + print( line, file=out ) except Exception as e: - print e + print ( e ) skipped_lines += 1 if not invalid_line: first_invalid_line = i + 1 From 32bc17cdaa636ba0ba2a97a372b37d518cdd6390 Mon Sep 17 00:00:00 2001 From: Daniel Blankenberg Date: Thu, 13 Oct 2016 11:08:18 -0400 Subject: [PATCH 25/32] Whitespace fix --- tools/filters/gff/gff_filter_by_attribute.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/filters/gff/gff_filter_by_attribute.py b/tools/filters/gff/gff_filter_by_attribute.py index 452e812e895..a731df826b5 100644 --- a/tools/filters/gff/gff_filter_by_attribute.py +++ b/tools/filters/gff/gff_filter_by_attribute.py @@ -127,7 +127,7 @@ for i, line in enumerate( open( in_fname ) ): lines_kept += 1 print( line, file=out ) except Exception as e: - print ( e ) + print( e ) skipped_lines += 1 if not invalid_line: first_invalid_line = i + 1 From b6d4473aa0975f88b2d2cf8aa1372aecfc92919b Mon Sep 17 00:00:00 2001 From: Daniel Blankenberg Date: Thu, 13 Oct 2016 11:19:37 -0400 Subject: [PATCH 26/32] Fix for displaying parameters to the user, that have a False value. Previously, e.g. an integer with a value of 0 would display "Not available.". --- lib/galaxy/tools/parameters/basic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/galaxy/tools/parameters/basic.py b/lib/galaxy/tools/parameters/basic.py index 236c6616115..ddb50cecc16 100644 --- a/lib/galaxy/tools/parameters/basic.py +++ b/lib/galaxy/tools/parameters/basic.py @@ -151,7 +151,7 @@ class ToolParameter( object, Dictifiable ): Convert a value to a text representation suitable for displaying to the user """ - if value: + if value is not None: return unicodify( value ) return "Not available." From ed263bf8a52b37199cc7822126598a75a9984335 Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Thu, 13 Oct 2016 12:07:25 -0400 Subject: [PATCH 27/32] import order. --- lib/galaxy/util/image_util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/galaxy/util/image_util.py b/lib/galaxy/util/image_util.py index 89c397d407e..9430d2eb5fb 100644 --- a/lib/galaxy/util/image_util.py +++ b/lib/galaxy/util/image_util.py @@ -1,6 +1,6 @@ """Provides utilities for working with image files.""" -import logging import imghdr +import logging try: import Image as PIL From 4bc429776853239961c45c9656c69b3be0ae01bc Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Thu, 13 Oct 2016 12:09:18 -0400 Subject: [PATCH 28/32] Simplify, strip out passing of image. --- lib/galaxy/util/image_util.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/galaxy/util/image_util.py b/lib/galaxy/util/image_util.py index 9430d2eb5fb..a54043bfc31 100644 --- a/lib/galaxy/util/image_util.py +++ b/lib/galaxy/util/image_util.py @@ -32,8 +32,8 @@ def image_type( filename ): return False -def check_image_type( filename, types, image=None ): - fmt = image_type( filename, image ) +def check_image_type( filename, types ): + fmt = image_type( filename ) if fmt in types: return True return False From 6c17d1a823b4367b93fff61d7f2fbed23dc5de3b Mon Sep 17 00:00:00 2001 From: Eric Rasche Date: Thu, 13 Oct 2016 16:21:12 +0000 Subject: [PATCH 29/32] Remove outdated warning After digging through the code I am not finding evidence that this was ever possible to do, at least not since sometime pre-2010. --- config/galaxy.ini.sample | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/config/galaxy.ini.sample b/config/galaxy.ini.sample index 3062bd1d9d4..a2a4bfac44f 100644 --- a/config/galaxy.ini.sample +++ b/config/galaxy.ini.sample @@ -886,8 +886,7 @@ use_interactive = True # Galaxy encodes various internal values when these values will be output in # some format (for example, in a URL or cookie). You should set a key to be # used by the algorithm that encodes and decodes these values. It can be any -# string. If left unchanged, anyone could construct a cookie that would grant -# them access to others' sessions. +# string. # One simple way to generate a value for this is with the shell command: # python -c 'import time; print time.time()' | md5sum | cut -f 1 -d ' ' #id_secret = USING THE DEFAULT IS NOT SECURE! From f6d5f3c4a2e51bd6b75a99dc015360e4ca9a6461 Mon Sep 17 00:00:00 2001 From: Dave B Date: Thu, 13 Oct 2016 12:42:54 -0400 Subject: [PATCH 30/32] Fix downloading tool tarball with boolean test input values. --- lib/galaxy/tools/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/galaxy/tools/__init__.py b/lib/galaxy/tools/__init__.py index 8b965d48f59..bddd0a0292f 100755 --- a/lib/galaxy/tools/__init__.py +++ b/lib/galaxy/tools/__init__.py @@ -1538,9 +1538,10 @@ class Tool( object, Dictifiable ): # Add input file tuples to the list. for input in test.inputs: for input_value in test.inputs[ input ]: - input_path = os.path.abspath( os.path.join( 'test-data', input_value ) ) + input_filename = str( input_value ) + input_path = os.path.abspath( os.path.join( 'test-data', input_filename ) ) if os.path.exists( input_path ): - td_tup = ( input_path, os.path.join( 'test-data', input_value ) ) + td_tup = ( input_path, os.path.join( 'test-data', input_filename ) ) tarball_files.append( td_tup ) # And add output file tuples to the list. for label, filename, _ in test.outputs: From 36134bca694c9dc6b2584fb661dfbce4ddb793e6 Mon Sep 17 00:00:00 2001 From: Scott Szakonyi Date: Thu, 6 Oct 2016 15:17:30 -0400 Subject: [PATCH 31/32] Added code to adjust top setting of tool search if messagebox is visible, issue 2907 --- client/galaxy/scripts/mvc/tool/tools.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/client/galaxy/scripts/mvc/tool/tools.js b/client/galaxy/scripts/mvc/tool/tools.js index 710b0230ec2..12323a58cc7 100644 --- a/client/galaxy/scripts/mvc/tool/tools.js +++ b/client/galaxy/scripts/mvc/tool/tools.js @@ -633,6 +633,12 @@ var ToolSearchView = Backbone.View.extend({ if (!this.model.is_visible()) { this.$el.hide(); } + + // Adjust top for issue 2907 depending on whether the messagebox is visible. + if ($("#messagebox").is(":visible")) { + this.$el.css("top","95px"); + } + this.$el.find('[title]').tooltip(); return this; }, From 6312d8052b9668a03b3bb25ea02b9d42ac32de6d Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Fri, 14 Oct 2016 09:43:41 -0400 Subject: [PATCH 32/32] Rebuild cilent. --- client/galaxy/scripts/mvc/tool/tools.js | 2 +- static/maps/mvc/tool/tools.js.map | 2 +- static/scripts/bundled/analysis.bundled.js | 25 ++++++++------- .../scripts/bundled/analysis.bundled.js.map | 2 +- static/scripts/bundled/libs.bundled.js | 32 +++++++++---------- static/scripts/bundled/libs.bundled.js.map | 2 +- static/scripts/mvc/tool/tools.js | 2 +- 7 files changed, 34 insertions(+), 33 deletions(-) diff --git a/client/galaxy/scripts/mvc/tool/tools.js b/client/galaxy/scripts/mvc/tool/tools.js index 12323a58cc7..8de4925a705 100644 --- a/client/galaxy/scripts/mvc/tool/tools.js +++ b/client/galaxy/scripts/mvc/tool/tools.js @@ -638,7 +638,7 @@ var ToolSearchView = Backbone.View.extend({ if ($("#messagebox").is(":visible")) { this.$el.css("top","95px"); } - + this.$el.find('[title]').tooltip(); return this; }, diff --git a/static/maps/mvc/tool/tools.js.map b/static/maps/mvc/tool/tools.js.map index 379f959a153..581ef5cbe13 100644 --- a/static/maps/mvc/tool/tools.js.map +++ b/static/maps/mvc/tool/tools.js.map @@ -1 +1 @@ -{"version":3,"file":"tools.js","sources":["../../../src/mvc/tool/tools.js"],"names":["define","_","util","data","ToolForm","VisibilityMixin","hidden","show","this","set","hide","toggle","get","is_visible","attributes","ToolParameter","Backbone","Model","extend","defaults","name","label","type","value","html","num_samples","initialize","unescape","copy","toJSON","set_value","ToolParameterCollection","Collection","model","DataToolParameter","IntegerToolParameter","parseInt","get_samples","d3","scale","linear","domain","ticks","FloatToolParameter","parseFloat","SelectToolParameter","map","option","subModelTypes","integer","float","select","Tool","id","description","target","inputs","outputs","urlRoot","Galaxy","root","options","p","p_class","rval","prototype","call","i","remove_inputs","types","tool","incompatible_inputs","filter","input","indexOf","remove","only_samplable_inputs","valid_inputs","each","push","apply_search_results","results","set_input_value","find","set_input_values","inputs_dict","self","keys","input_name","run","_run","rerun","target_dataset","regions","action","target_dataset_id","get_inputs_dict","input_dict","additional_params","payload","tool_id","run_deferred","$","Deferred","ss_deferred","ServerStateDeferred","ajax_settings","url","JSON","stringify","dataType","contentType","interval","success_fn","response","when","go","then","result","resolve","DatasetCollection","ToolCollection","View","ToolSectionLabel","ToolSection","elems","open","clear_search_results","elt","cur_label","all_hidden","ToolSearch","search_hint_string","min_chars_for_search","clear_btn_url","search_url","visible","query","clear_key","on","do_search","length","q","timer","clearTimeout","setTimeout","ga","clear_search","ToolPanel","tool_search","tools","layout","parse","parse_elt","elt_dict","model_class","panel_elt","BaseView","update_visible","$el","ToolLinkView","tagName","render","$link","append","templates","tool_link","formStyle","e","preventDefault","upload","form","version","deferred","execute","app","display","ToolSectionLabelView","className","text","ToolSectionView","update_open","panel_section","section_body","tool_view","label_view","events","click .toolSectionTitle > a","children","slideDown","slideUp","ToolSearchView","click","keyup :input","click #search-clear-btn","tooltip","focus_and_select","focus","clear","val","query_changed","evData","which","ToolPanelView","handle_search_results","search_view","section_title_view","attr","split","trigger","ToolFormView","tool_form","tool_panel_view","collection","tool_form_view","css","show_tool","fetch","done","width","template","join","variable"],"mappings":"AAICA,QACG,kBACA,qBACA,mBACA,sBAED,SAASC,EAAGC,EAAMC,EAAMC,GACvB,YAKJ,IAAIC,IACAC,QAAQ,EAERC,KAAM,WACFC,KAAKC,IAAI,UAAU,IAGvBC,KAAM,WACFF,KAAKC,IAAI,UAAU,IAGvBE,OAAQ,WACJH,KAAKC,IAAI,UAAWD,KAAKI,IAAI,YAGjCC,WAAY,WACR,OAAQL,KAAKM,WAAWR,SAQ5BS,EAAgBC,SAASC,MAAMC,QAC/BC,UACIC,KAAM,KACNC,MAAO,KACPC,KAAM,KACNC,MAAO,KACPC,KAAM,KACNC,YAAa,GAGjBC,WAAY,WACRlB,KAAKM,WAAWU,KAAOG,SAASnB,KAAKM,WAAWU,OAGpDI,KAAM,WACF,MAAO,IAAIb,GAAcP,KAAKqB,WAGlCC,UAAW,SAASP,GAChBf,KAAKC,IAAI,QAASc,GAAS,OAI/BQ,EAA0Bf,SAASgB,WAAWd,QAC9Ce,MAAOlB,IAMPmB,EAAoBnB,EAAcG,WAKlCiB,EAAuBpB,EAAcG,QACrCY,UAAW,SAASP,GAChBf,KAAKC,IAAI,QAAS2B,SAASb,EAAO,MAMtCc,YAAa,WACT,MAAOC,IAAGC,MAAMC,SACCC,QAAQjC,KAAKI,IAAI,OAAQJ,KAAKI,IAAI,SAClC8B,MAAMlC,KAAKI,IAAI,mBAIpC+B,EAAqBR,EAAqBjB,QAC1CY,UAAW,SAASP,GAChBf,KAAKC,IAAI,QAASmC,WAAWrB,OAOjCsB,EAAsB9B,EAAcG,QAIpCmB,YAAa,WACT,MAAOpC,GAAE6C,IAAItC,KAAKI,IAAI,WAAY,SAASmC,GACvC,MAAOA,GAAO,OAM1BhC,GAAciC,eACVC,QAAWd,EACXe,QAASP,EACTxC,KAAQ+B,EACRiB,OAAUN,EAMd,IAAIO,GAAOpC,SAASC,MAAMC,QAEtBC,UACIkC,GAAI,KACJjC,KAAM,KACNkC,YAAa,KACbC,OAAQ,KACRC,UACAC,YAGJC,QAASC,OAAOC,KAAO,YAEvBlC,WAAY,SAASmC,GAGjBrD,KAAKC,IAAI,SAAU,GAAIsB,GAAwB9B,EAAE6C,IAAIe,EAAQL,OAAQ,SAASM,GAC1E,GAAIC,GAAUhD,EAAciC,cAAcc,EAAExC,OAASP,CACrD,OAAO,IAAIgD,GAAQD,QAO3BjC,OAAQ,WACJ,GAAImC,GAAOhD,SAASC,MAAMgD,UAAUpC,OAAOqC,KAAK1D,KAIhD,OADAwD,GAAKR,OAAShD,KAAKI,IAAI,UAAUkC,IAAI,SAASqB,GAAK,MAAOA,GAAEtC,WACrDmC,GAOXI,cAAe,SAASC,GACpB,GAAIC,GAAO9D,KACP+D,EAAsBD,EAAK1D,IAAI,UAAU4D,OAAQ,SAASC,GACtD,MAAgD,KAAvCJ,EAAMK,QAASD,EAAM7D,IAAI,UAE1C0D,GAAK1D,IAAI,UAAU+D,OAAOJ,IAM9B3C,KAAM,SAASgD,GACX,GAAIhD,GAAO,GAAIwB,GAAK5C,KAAKqB,SAGzB,IAAI+C,EAAuB,CACvB,GAAIC,GAAe,GAAI7D,UAASgB,UAChCJ,GAAKhB,IAAI,UAAUkE,KAAK,SAASL,GACzBA,EAAMpC,eACNwC,EAAaE,KAAKN,KAG1B7C,EAAKnB,IAAI,SAAUoE,GAGvB,MAAOjD,IAGXoD,qBAAsB,SAASC,GAE3B,MAD6C,KAA3ChF,EAAEyE,QAAQO,EAASzE,KAAKM,WAAWuC,IAAa7C,KAAKD,OAASC,KAAKE,OAC9DF,KAAKK,cAMhBqE,gBAAiB,SAAS9D,EAAMG,GAC5Bf,KAAKI,IAAI,UAAUuE,KAAK,SAASV,GAC7B,MAAOA,GAAM7D,IAAI,UAAYQ,IAC9BX,IAAI,QAASc,IAMpB6D,iBAAkB,SAASC,GACvB,GAAIC,GAAO9E,IACXP,GAAE6E,KAAK7E,EAAEsF,KAAKF,GAAc,SAASG,GACjCF,EAAKJ,gBAAgBM,EAAYH,EAAYG,OAOrDC,IAAK,WACD,MAAOjF,MAAKkF,QAMhBC,MAAO,SAASC,EAAgBC,GAC5B,MAAOrF,MAAKkF,MACRI,OAAQ,QACRC,kBAAmBH,EAAevC,GAClCwC,QAASA,KAOjBG,gBAAiB,WACb,GAAIC,KAIJ,OAHAzF,MAAKI,IAAI,UAAUkE,KAAK,SAASL,GAC7BwB,EAAWxB,EAAM7D,IAAI,SAAW6D,EAAM7D,IAAI,WAEvCqF,GAOXP,KAAM,SAASQ,GAEX,GAAIC,GAAUlG,EAAEiB,QACRkF,QAAS5F,KAAK6C,GACdG,OAAQhD,KAAKwF,mBACdE,GAKHG,EAAeC,EAAEC,WACjBC,EAAc,GAAItG,GAAKuG,qBACvBC,eACIC,IAAKnG,KAAKkD,QACVvD,KAAMyG,KAAKC,UAAUV,GACrBW,SAAU,OACVC,YAAa,mBACbzF,KAAM,QAEV0F,SAAU,IACVC,WAAY,SAASC,GACjB,MAAoB,YAAbA,IAQf,OAHAZ,GAAEa,KAAKX,EAAYY,MAAMC,KAAK,SAASC,GACnCjB,EAAakB,QAAQ,GAAIpH,GAAKqH,kBAAkBF,MAE7CjB,IAGfpG,GAAEiB,OAAOkC,EAAKa,UAAW5D,EAKzB,IAOIoH,IAPWzG,SAAS0G,KAAKxG,WAORF,SAASgB,WAAWd,QACrCe,MAAOmB,KAMPuE,EAAmB3G,SAASC,MAAMC,OAAOb,GAKzCuH,EAAc5G,SAASC,MAAMC,QAC7BC,UACI0G,SACAC,MAAM,GAGVC,qBAAsB,WAClB9H,EAAE6E,KAAKtE,KAAKM,WAAW+G,MAAO,SAASG,GACnCA,EAAIzH,SAGRC,KAAKD,OACLC,KAAKC,IAAI,QAAQ,IAGrBuE,qBAAsB,SAASC,GAC3B,GACIgD,GADAC,GAAa,CAEjBjI,GAAE6E,KAAKtE,KAAKM,WAAW+G,MAAO,SAASG,GAC/BA,YAAeL,IACfM,EAAYD,EACZC,EAAUvH,QAELsH,YAAe5E,IAChB4E,EAAIhD,qBAAqBC,KACzBiD,GAAa,EACTD,GACAA,EAAU1H,UAMtB2H,EACA1H,KAAKE,QAGLF,KAAKD,OACLC,KAAKC,IAAI,QAAQ,MAI7BR,GAAEiB,OAAO0G,EAAY3D,UAAW5D,EAOhC,IAAI8H,GAAanH,SAASC,MAAMC,QAC5BC,UACIiH,mBAAoB,eACpBC,qBAAsB,EACtBC,cAAe,GACfC,WAAY,GACZC,SAAS,EACTC,MAAO,GACPxD,QAAS,KAETyD,UAAW,IAGfhF,QAASC,OAAOC,KAAO,YAEvBlC,WAAY,WACRlB,KAAKmI,GAAG,eAAgBnI,KAAKoI,YAMjCA,UAAW,WACP,GAAIH,GAAQjI,KAAKM,WAAW2H,KAG5B,IAAIA,EAAMI,OAASrI,KAAKM,WAAWuH,qBAE/B,WADA7H,MAAKC,IAAI,UAAW,KAKxB,IAAIqI,GAAIL,CAEJjI,MAAKuI,OACLC,aAAaxI,KAAKuI,OAGtBzC,EAAE,qBAAqB5F,OACvB4F,EAAE,mBAAmB/F,MACrB,IAAI+E,GAAO9E,IACXA,MAAKuI,MAAQE,WAAW,WAED,mBAAPC,KACRA,GAAI,OAAQ,WAAYvF,OAAOC,KAAO,MAAQkF,GAElDxC,EAAE1F,IAAK0E,EAAK5B,SAAWoF,EAAGA,GAAK,SAAU3I,GACrCmF,EAAK7E,IAAI,UAAWN,GACpBmG,EAAE,mBAAmB5F,OACrB4F,EAAE,qBAAqB/F,QACxB,SACJ,MAGP4I,aAAc,WACV3I,KAAKC,IAAI,QAAS,IAClBD,KAAKC,IAAI,UAAW,QAI5BR,GAAEiB,OAAOiH,EAAWlE,UAAW5D,EAK/B,IAAI+I,GAAYpI,SAASC,MAAMC,QAE3BQ,WAAY,SAASmC,GACjBrD,KAAKM,WAAWuI,YAAcxF,EAAQwF,YACtC7I,KAAKM,WAAWuI,YAAYV,GAAG,iBAAkBnI,KAAKwE,qBAAsBxE,MAC5EA,KAAKM,WAAWwI,MAAQzF,EAAQyF,MAChC9I,KAAKM,WAAWyI,OAAS,GAAIvI,UAASgB,WAAYxB,KAAKgJ,MAAM3F,EAAQ0F,UAMzEC,MAAO,SAAStC,GAEZ,GAAI5B,GAAO9E,KAEPiJ,EAAY,SAASC,GACjB,GAAIpI,GAAOoI,EAASC,WAGpB,IAAKrI,EAAKoD,QAAQ,UAAYpD,EAAKuH,OAAS,EACxC,MAAOvD,GAAKxE,WAAWwI,MAAM1I,IAAI8I,EAASrG,GAEzC,IAAa,gBAAT/B,EAAwB,CAE7B,GAAIuG,GAAQ5H,EAAE6C,IAAI4G,EAAS7B,MAAO4B,EAElC,OADAC,GAAS7B,MAAQA,EACV,GAAID,GAAY8B,GAEtB,MAAa,qBAATpI,EACE,GAAIqG,GAAiB+B,GAD3B,OAKb,OAAOzJ,GAAE6C,IAAIoE,EAAUuC,IAG3B1B,qBAAsB,WAClBvH,KAAKI,IAAI,UAAUkE,KAAK,SAAS8E,GACzBA,YAAqBhC,GACrBgC,EAAU7B,uBAIV6B,EAAUrJ,UAKtByE,qBAAsB,WAClB,GAAIC,GAAUzE,KAAKI,IAAI,eAAeA,IAAI,UAC1C,IAAgB,OAAZqE,EAEA,WADAzE,MAAKuH,sBAIT,IAAIE,GAAY,IAChBzH,MAAKI,IAAI,UAAUkE,KAAK,SAAS8E,GACzBA,YAAqBjC,IACrBM,EAAY2B,EACZ3B,EAAUvH,QAELkJ,YAAqBxG,GACtBwG,EAAU5E,qBAAqBC,IAC3BgD,GACAA,EAAU1H,QAMlB0H,EAAY,KACZ2B,EAAU5E,qBAAqBC,SAiB3C4E,EAAW7I,SAAS0G,KAAKxG,QACzBQ,WAAY,WACRlB,KAAKyB,MAAM0G,GAAG,gBAAiBnI,KAAKsJ,eAAgBtJ,MACpDA,KAAKsJ,kBAETA,eAAgB,WACVtJ,KAAKyB,MAAMnB,WAAWR,OAASE,KAAKuJ,IAAIrJ,OAASF,KAAKuJ,IAAIxJ,UAOhEyJ,EAAeH,EAAS3I,QACxB+I,QAAS,MAETC,OAAQ,WAEJ,GAAIC,GAAQ7D,EAAE,SACd6D,GAAMC,OAAOC,EAAUC,UAAU9J,KAAKyB,MAAMJ,UAE5C,IAAI0I,GAAY/J,KAAKyB,MAAMrB,IAAK,aAAc,KAE9C,IAAsB,YAAlBJ,KAAKyB,MAAMoB,GACX8G,EAAMhF,KAAK,KAAKwD,GAAG,QAAS,SAAS6B,GACjCA,EAAEC,iBACF9G,OAAO+G,OAAOnK,aAGjB,IAAmB,YAAdgK,EAA0B,CAChC,GAAIjF,GAAO9E,IACX2J,GAAMhF,KAAK,KAAKwD,GAAG,QAAS,SAAS6B,GACjCA,EAAEC,gBACF,IAAIE,GAAO,GAAIvK,GAASsH,MAAQrE,GAAKiC,EAAKrD,MAAMoB,GAAIuH,QAAUtF,EAAKrD,MAAMrB,IAAI,YAC7E+J,GAAKE,SAASC,QAAQ,WAClBnH,OAAOoH,IAAIC,QAASL,OAOhC,MADAnK,MAAKuJ,IAAIK,OAAOD,GACT3J,QAOXyK,EAAuBpB,EAAS3I,QAChC+I,QAAS,MACTiB,UAAW,iBAEXhB,OAAQ,WAEJ,MADA1J,MAAKuJ,IAAIK,OAAQ9D,EAAE,WAAW6E,KAAK3K,KAAKyB,MAAMnB,WAAWqK,OAClD3K,QAOX4K,EAAkBvB,EAAS3I,QAC3B+I,QAAS,MACTiB,UAAW,qBAEXxJ,WAAY,WACRmI,EAAS5F,UAAUvC,WAAWwC,KAAK1D,MACnCA,KAAKyB,MAAM0G,GAAG,cAAenI,KAAK6K,YAAa7K,OAGnD0J,OAAQ,WAEJ1J,KAAKuJ,IAAIK,OAAQC,EAAUiB,cAAc9K,KAAKyB,MAAMJ,UAGpD,IAAI0J,GAAe/K,KAAKuJ,IAAI5E,KAAK,mBAgBjC,OAfAlF,GAAE6E,KAAKtE,KAAKyB,MAAMnB,WAAW+G,MAAO,SAASG,GACzC,GAAIA,YAAe5E,GAAM,CACrB,GAAIoI,GAAY,GAAIxB,IAAc/H,MAAO+F,EAAKkD,UAAW,aACzDM,GAAUtB,SACVqB,EAAanB,OAAOoB,EAAUzB,SAE7B,IAAI/B,YAAeL,GAAkB,CACtC,GAAI8D,GAAa,GAAIR,IAAsBhJ,MAAO+F,GAClDyD,GAAWvB,SACXqB,EAAanB,OAAOqB,EAAW1B,QAMhCvJ,MAGXkL,QACIC,8BAA+B,UAMnChL,OAAQ,WACJH,KAAKyB,MAAMxB,IAAI,QAASD,KAAKyB,MAAMnB,WAAWgH,OAMlDuD,YAAa,WACR7K,KAAKyB,MAAMnB,WAAWgH,KACnBtH,KAAKuJ,IAAI6B,SAAS,oBAAoBC,UAAU,QAChDrL,KAAKuJ,IAAI6B,SAAS,oBAAoBE,QAAQ,WAKtDC,EAAiB/K,SAAS0G,KAAKxG,QAC/B+I,QAAS,MACT5G,GAAI,cACJ6H,UAAW,MAEXQ,QACIM,MAAS,mBACTC,eAAgB,gBAChBC,0BAA2B,SAG/BhC,OAAQ,WAMJ,MALA1J,MAAKuJ,IAAIK,OAAQC,EAAUhB,YAAY7I,KAAKyB,MAAMJ,WAC7CrB,KAAKyB,MAAMpB,cACZL,KAAKuJ,IAAIrJ,OAEbF,KAAKuJ,IAAI5E,KAAK,WAAWgH,UAClB3L,MAGX4L,iBAAkB,WACd5L,KAAKuJ,IAAI5E,KAAK,UAAUkH,QAAQlJ,UAGpCmJ,MAAO,WAIH,MAHA9L,MAAKyB,MAAMkH,eACX3I,KAAKuJ,IAAI5E,KAAK,UAAUoH,IAAI,IAC5B/L,KAAK4L,oBACE,GAGXI,cAAe,SAAUC,GAErB,MAAMjM,MAAKyB,MAAMnB,WAAqB,WAChCN,KAAKyB,MAAMnB,WAAW4H,YAAc+D,EAAOC,OAC7ClM,KAAK8L,SACE,OAEX9L,MAAKyB,MAAMxB,IAAI,QAASD,KAAKuJ,IAAI5E,KAAK,UAAUoH,UAQpDI,EAAgB3L,SAAS0G,KAAKxG,QAC9B+I,QAAS,MACTiB,UAAW,WAKXxJ,WAAY,WACRlB,KAAKyB,MAAMrB,IAAI,eAAe+H,GAAG,iBAAkBnI,KAAKoM,sBAAuBpM,OAGnF0J,OAAQ,WACJ,GAAI5E,GAAO9E,KAGPqM,EAAc,GAAId,IAAkB9J,MAAOzB,KAAKyB,MAAMrB,IAAI,gBAiC9D,OAhCAiM,GAAY3C,SACZ5E,EAAKyE,IAAIK,OAAOyC,EAAY9C,KAG5BvJ,KAAKyB,MAAMrB,IAAI,UAAUkE,KAAK,SAAS8E,GACnC,GAAIA,YAAqBhC,GAAa,CAClC,GAAIkF,GAAqB,GAAI1B,IAAiBnJ,MAAO2H,GACrDkD,GAAmB5C,SACnB5E,EAAKyE,IAAIK,OAAO0C,EAAmB/C,SAElC,IAAIH,YAAqBxG,GAAM,CAChC,GAAIoI,GAAY,GAAIxB,IAAc/H,MAAO2H,EAAWsB,UAAW,sBAC/DM,GAAUtB,SACV5E,EAAKyE,IAAIK,OAAOoB,EAAUzB,SAEzB,IAAIH,YAAqBjC,GAAkB,CAC5C,GAAI8D,GAAa,GAAIR,IAAsBhJ,MAAO2H,GAClD6B,GAAWvB,SACX5E,EAAKyE,IAAIK,OAAOqB,EAAW1B,QAKnCzE,EAAKyE,IAAI5E,KAAK,eAAe6G,MAAM,SAASxB,GAExC,GACIpE,GAAUE,EAAE9F,MAAMuM,KAAK,SAASC,MAAM,OAAO,GAC7C1I,EAAOgB,EAAKrD,MAAMrB,IAAI,SAASA,IAAIwF,EAEvCd,GAAK2H,QAAQ,kBAAmBzC,EAAGlG,KAGhC9D,MAGXoM,sBAAuB,WACnB,GAAI3H,GAAUzE,KAAKyB,MAAMrB,IAAI,eAAeA,IAAI,UAC5CqE,IAA8B,IAAnBA,EAAQ4D,OACnBvC,EAAE,sBAAsB/F,OAGxB+F,EAAE,sBAAsB5F,UAQhCwM,EAAelM,SAAS0G,KAAKxG,QAC7BgK,UAAW,WAEXhB,OAAQ,WACJ1J,KAAKuJ,IAAI6B,WAAWjH,SACpBnE,KAAKuJ,IAAIK,OAAQC,EAAU8C,UAAU3M,KAAKyB,MAAMJ,cAkDpDwI,GA3C4BrJ,SAAS0G,KAAKxG,QAC1CgK,UAAW,kBAEXxJ,WAAY,WACRlB,KAAK4M,gBAAkB,GAAIT,IAAeU,WAAY7M,KAAK6M,aAC3D7M,KAAK8M,eAAiB,GAAIJ,IAG9BhD,OAAQ,WAEJ1J,KAAK4M,gBAAgBlD,SACrB1J,KAAK4M,gBAAgBrD,IAAIwD,IAAI,QAAS,QACtC/M,KAAKuJ,IAAIK,OAAO5J,KAAK4M,gBAAgBrD,KAGrCvJ,KAAK8M,eAAevD,IAAIrJ,OACxBF,KAAKuJ,IAAIK,OAAO5J,KAAK8M,eAAevD,IAGpC,IAAIzE,GAAO9E,IACXA,MAAK4M,gBAAgBzE,GAAG,kBAAmB,SAAS6B,EAAGlG,GAEnDkG,EAAEC,iBAEFnF,EAAKkI,UAAUlJ,MAOvBkJ,UAAW,SAASlJ,GAChB,GAAIgB,GAAO9E,IACX8D,GAAKmJ,QAAQC,KAAM,WACfpI,EAAKgI,eAAerL,MAAQqC,EAC5BgB,EAAKgI,eAAepD,SACpB5E,EAAKgI,eAAevD,IAAIxJ,OACxB+F,EAAE,SAASqH,MAAM,eAQzBtE,YAAcpJ,EAAE2N,UACZ,gFACQ,4EACR,4DAEA,kFACFC,KAAK,KAGPvC,cAAgBrL,EAAE2N,UACd,sDACI,4DACJ,SACA,sEACI,oCACJ,SACFC,KAAK,KAGPvD,UAAYrK,EAAE2N,UACV,wBACI,2CACA,wDACI,eACJ,UACA,YACJ,UACA,2GACI,cACJ,OACA,uBACFC,KAAK,KAIPV,UAAYlN,EAAE2N,UACV,kFACA,6BACI,gDACA,yBACI,6DACA,+BACI,oBACJ,SACA,mDACI,oBACJ,SACA,mCACJ,SACA,YACJ,SACA,sCACI,qFACJ,SACA,yBACI,kDACJ,UAGFC,KAAK,KAAOC,SAAU,UAK5B,QACI/M,cAAeA,EACfoB,qBAAsBA,EACtBU,oBAAqBA,EACrBO,KAAMA,EACNqE,eAAgBA,EAChBU,WAAYA,EACZiB,UAAWA,EACXuD,cAAeA,EACfO,aAAcA"} \ No newline at end of file +{"version":3,"file":"tools.js","sources":["../../../src/mvc/tool/tools.js"],"names":["define","_","util","data","ToolForm","VisibilityMixin","hidden","show","this","set","hide","toggle","get","is_visible","attributes","ToolParameter","Backbone","Model","extend","defaults","name","label","type","value","html","num_samples","initialize","unescape","copy","toJSON","set_value","ToolParameterCollection","Collection","model","DataToolParameter","IntegerToolParameter","parseInt","get_samples","d3","scale","linear","domain","ticks","FloatToolParameter","parseFloat","SelectToolParameter","map","option","subModelTypes","integer","float","select","Tool","id","description","target","inputs","outputs","urlRoot","Galaxy","root","options","p","p_class","rval","prototype","call","i","remove_inputs","types","tool","incompatible_inputs","filter","input","indexOf","remove","only_samplable_inputs","valid_inputs","each","push","apply_search_results","results","set_input_value","find","set_input_values","inputs_dict","self","keys","input_name","run","_run","rerun","target_dataset","regions","action","target_dataset_id","get_inputs_dict","input_dict","additional_params","payload","tool_id","run_deferred","$","Deferred","ss_deferred","ServerStateDeferred","ajax_settings","url","JSON","stringify","dataType","contentType","interval","success_fn","response","when","go","then","result","resolve","DatasetCollection","ToolCollection","View","ToolSectionLabel","ToolSection","elems","open","clear_search_results","elt","cur_label","all_hidden","ToolSearch","search_hint_string","min_chars_for_search","clear_btn_url","search_url","visible","query","clear_key","on","do_search","length","q","timer","clearTimeout","setTimeout","ga","clear_search","ToolPanel","tool_search","tools","layout","parse","parse_elt","elt_dict","model_class","panel_elt","BaseView","update_visible","$el","ToolLinkView","tagName","render","$link","append","templates","tool_link","formStyle","e","preventDefault","upload","form","version","deferred","execute","app","display","ToolSectionLabelView","className","text","ToolSectionView","update_open","panel_section","section_body","tool_view","label_view","events","click .toolSectionTitle > a","children","slideDown","slideUp","ToolSearchView","click","keyup :input","click #search-clear-btn","is","css","tooltip","focus_and_select","focus","clear","val","query_changed","evData","which","ToolPanelView","handle_search_results","search_view","section_title_view","attr","split","trigger","ToolFormView","tool_form","tool_panel_view","collection","tool_form_view","show_tool","fetch","done","width","template","join","variable"],"mappings":"AAICA,QACG,kBACA,qBACA,mBACA,sBAED,SAASC,EAAGC,EAAMC,EAAMC,GACvB,YAKJ,IAAIC,IACAC,QAAQ,EAERC,KAAM,WACFC,KAAKC,IAAI,UAAU,IAGvBC,KAAM,WACFF,KAAKC,IAAI,UAAU,IAGvBE,OAAQ,WACJH,KAAKC,IAAI,UAAWD,KAAKI,IAAI,YAGjCC,WAAY,WACR,OAAQL,KAAKM,WAAWR,SAQ5BS,EAAgBC,SAASC,MAAMC,QAC/BC,UACIC,KAAM,KACNC,MAAO,KACPC,KAAM,KACNC,MAAO,KACPC,KAAM,KACNC,YAAa,GAGjBC,WAAY,WACRlB,KAAKM,WAAWU,KAAOG,SAASnB,KAAKM,WAAWU,OAGpDI,KAAM,WACF,MAAO,IAAIb,GAAcP,KAAKqB,WAGlCC,UAAW,SAASP,GAChBf,KAAKC,IAAI,QAASc,GAAS,OAI/BQ,EAA0Bf,SAASgB,WAAWd,QAC9Ce,MAAOlB,IAMPmB,EAAoBnB,EAAcG,WAKlCiB,EAAuBpB,EAAcG,QACrCY,UAAW,SAASP,GAChBf,KAAKC,IAAI,QAAS2B,SAASb,EAAO,MAMtCc,YAAa,WACT,MAAOC,IAAGC,MAAMC,SACCC,QAAQjC,KAAKI,IAAI,OAAQJ,KAAKI,IAAI,SAClC8B,MAAMlC,KAAKI,IAAI,mBAIpC+B,EAAqBR,EAAqBjB,QAC1CY,UAAW,SAASP,GAChBf,KAAKC,IAAI,QAASmC,WAAWrB,OAOjCsB,EAAsB9B,EAAcG,QAIpCmB,YAAa,WACT,MAAOpC,GAAE6C,IAAItC,KAAKI,IAAI,WAAY,SAASmC,GACvC,MAAOA,GAAO,OAM1BhC,GAAciC,eACVC,QAAWd,EACXe,QAASP,EACTxC,KAAQ+B,EACRiB,OAAUN,EAMd,IAAIO,GAAOpC,SAASC,MAAMC,QAEtBC,UACIkC,GAAI,KACJjC,KAAM,KACNkC,YAAa,KACbC,OAAQ,KACRC,UACAC,YAGJC,QAASC,OAAOC,KAAO,YAEvBlC,WAAY,SAASmC,GAGjBrD,KAAKC,IAAI,SAAU,GAAIsB,GAAwB9B,EAAE6C,IAAIe,EAAQL,OAAQ,SAASM,GAC1E,GAAIC,GAAUhD,EAAciC,cAAcc,EAAExC,OAASP,CACrD,OAAO,IAAIgD,GAAQD,QAO3BjC,OAAQ,WACJ,GAAImC,GAAOhD,SAASC,MAAMgD,UAAUpC,OAAOqC,KAAK1D,KAIhD,OADAwD,GAAKR,OAAShD,KAAKI,IAAI,UAAUkC,IAAI,SAASqB,GAAK,MAAOA,GAAEtC,WACrDmC,GAOXI,cAAe,SAASC,GACpB,GAAIC,GAAO9D,KACP+D,EAAsBD,EAAK1D,IAAI,UAAU4D,OAAQ,SAASC,GACtD,MAAgD,KAAvCJ,EAAMK,QAASD,EAAM7D,IAAI,UAE1C0D,GAAK1D,IAAI,UAAU+D,OAAOJ,IAM9B3C,KAAM,SAASgD,GACX,GAAIhD,GAAO,GAAIwB,GAAK5C,KAAKqB,SAGzB,IAAI+C,EAAuB,CACvB,GAAIC,GAAe,GAAI7D,UAASgB,UAChCJ,GAAKhB,IAAI,UAAUkE,KAAK,SAASL,GACzBA,EAAMpC,eACNwC,EAAaE,KAAKN,KAG1B7C,EAAKnB,IAAI,SAAUoE,GAGvB,MAAOjD,IAGXoD,qBAAsB,SAASC,GAE3B,MAD6C,KAA3ChF,EAAEyE,QAAQO,EAASzE,KAAKM,WAAWuC,IAAa7C,KAAKD,OAASC,KAAKE,OAC9DF,KAAKK,cAMhBqE,gBAAiB,SAAS9D,EAAMG,GAC5Bf,KAAKI,IAAI,UAAUuE,KAAK,SAASV,GAC7B,MAAOA,GAAM7D,IAAI,UAAYQ,IAC9BX,IAAI,QAASc,IAMpB6D,iBAAkB,SAASC,GACvB,GAAIC,GAAO9E,IACXP,GAAE6E,KAAK7E,EAAEsF,KAAKF,GAAc,SAASG,GACjCF,EAAKJ,gBAAgBM,EAAYH,EAAYG,OAOrDC,IAAK,WACD,MAAOjF,MAAKkF,QAMhBC,MAAO,SAASC,EAAgBC,GAC5B,MAAOrF,MAAKkF,MACRI,OAAQ,QACRC,kBAAmBH,EAAevC,GAClCwC,QAASA,KAOjBG,gBAAiB,WACb,GAAIC,KAIJ,OAHAzF,MAAKI,IAAI,UAAUkE,KAAK,SAASL,GAC7BwB,EAAWxB,EAAM7D,IAAI,SAAW6D,EAAM7D,IAAI,WAEvCqF,GAOXP,KAAM,SAASQ,GAEX,GAAIC,GAAUlG,EAAEiB,QACRkF,QAAS5F,KAAK6C,GACdG,OAAQhD,KAAKwF,mBACdE,GAKHG,EAAeC,EAAEC,WACjBC,EAAc,GAAItG,GAAKuG,qBACvBC,eACIC,IAAKnG,KAAKkD,QACVvD,KAAMyG,KAAKC,UAAUV,GACrBW,SAAU,OACVC,YAAa,mBACbzF,KAAM,QAEV0F,SAAU,IACVC,WAAY,SAASC,GACjB,MAAoB,YAAbA,IAQf,OAHAZ,GAAEa,KAAKX,EAAYY,MAAMC,KAAK,SAASC,GACnCjB,EAAakB,QAAQ,GAAIpH,GAAKqH,kBAAkBF,MAE7CjB,IAGfpG,GAAEiB,OAAOkC,EAAKa,UAAW5D,EAKzB,IAOIoH,IAPWzG,SAAS0G,KAAKxG,WAORF,SAASgB,WAAWd,QACrCe,MAAOmB,KAMPuE,EAAmB3G,SAASC,MAAMC,OAAOb,GAKzCuH,EAAc5G,SAASC,MAAMC,QAC7BC,UACI0G,SACAC,MAAM,GAGVC,qBAAsB,WAClB9H,EAAE6E,KAAKtE,KAAKM,WAAW+G,MAAO,SAASG,GACnCA,EAAIzH,SAGRC,KAAKD,OACLC,KAAKC,IAAI,QAAQ,IAGrBuE,qBAAsB,SAASC,GAC3B,GACIgD,GADAC,GAAa,CAEjBjI,GAAE6E,KAAKtE,KAAKM,WAAW+G,MAAO,SAASG,GAC/BA,YAAeL,IACfM,EAAYD,EACZC,EAAUvH,QAELsH,YAAe5E,IAChB4E,EAAIhD,qBAAqBC,KACzBiD,GAAa,EACTD,GACAA,EAAU1H,UAMtB2H,EACA1H,KAAKE,QAGLF,KAAKD,OACLC,KAAKC,IAAI,QAAQ,MAI7BR,GAAEiB,OAAO0G,EAAY3D,UAAW5D,EAOhC,IAAI8H,GAAanH,SAASC,MAAMC,QAC5BC,UACIiH,mBAAoB,eACpBC,qBAAsB,EACtBC,cAAe,GACfC,WAAY,GACZC,SAAS,EACTC,MAAO,GACPxD,QAAS,KAETyD,UAAW,IAGfhF,QAASC,OAAOC,KAAO,YAEvBlC,WAAY,WACRlB,KAAKmI,GAAG,eAAgBnI,KAAKoI,YAMjCA,UAAW,WACP,GAAIH,GAAQjI,KAAKM,WAAW2H,KAG5B,IAAIA,EAAMI,OAASrI,KAAKM,WAAWuH,qBAE/B,WADA7H,MAAKC,IAAI,UAAW,KAKxB,IAAIqI,GAAIL,CAEJjI,MAAKuI,OACLC,aAAaxI,KAAKuI,OAGtBzC,EAAE,qBAAqB5F,OACvB4F,EAAE,mBAAmB/F,MACrB,IAAI+E,GAAO9E,IACXA,MAAKuI,MAAQE,WAAW,WAED,mBAAPC,KACRA,GAAI,OAAQ,WAAYvF,OAAOC,KAAO,MAAQkF,GAElDxC,EAAE1F,IAAK0E,EAAK5B,SAAWoF,EAAGA,GAAK,SAAU3I,GACrCmF,EAAK7E,IAAI,UAAWN,GACpBmG,EAAE,mBAAmB5F,OACrB4F,EAAE,qBAAqB/F,QACxB,SACJ,MAGP4I,aAAc,WACV3I,KAAKC,IAAI,QAAS,IAClBD,KAAKC,IAAI,UAAW,QAI5BR,GAAEiB,OAAOiH,EAAWlE,UAAW5D,EAK/B,IAAI+I,GAAYpI,SAASC,MAAMC,QAE3BQ,WAAY,SAASmC,GACjBrD,KAAKM,WAAWuI,YAAcxF,EAAQwF,YACtC7I,KAAKM,WAAWuI,YAAYV,GAAG,iBAAkBnI,KAAKwE,qBAAsBxE,MAC5EA,KAAKM,WAAWwI,MAAQzF,EAAQyF,MAChC9I,KAAKM,WAAWyI,OAAS,GAAIvI,UAASgB,WAAYxB,KAAKgJ,MAAM3F,EAAQ0F,UAMzEC,MAAO,SAAStC,GAEZ,GAAI5B,GAAO9E,KAEPiJ,EAAY,SAASC,GACjB,GAAIpI,GAAOoI,EAASC,WAGpB,IAAKrI,EAAKoD,QAAQ,UAAYpD,EAAKuH,OAAS,EACxC,MAAOvD,GAAKxE,WAAWwI,MAAM1I,IAAI8I,EAASrG,GAEzC,IAAa,gBAAT/B,EAAwB,CAE7B,GAAIuG,GAAQ5H,EAAE6C,IAAI4G,EAAS7B,MAAO4B,EAElC,OADAC,GAAS7B,MAAQA,EACV,GAAID,GAAY8B,GAEtB,MAAa,qBAATpI,EACE,GAAIqG,GAAiB+B,GAD3B,OAKb,OAAOzJ,GAAE6C,IAAIoE,EAAUuC,IAG3B1B,qBAAsB,WAClBvH,KAAKI,IAAI,UAAUkE,KAAK,SAAS8E,GACzBA,YAAqBhC,GACrBgC,EAAU7B,uBAIV6B,EAAUrJ,UAKtByE,qBAAsB,WAClB,GAAIC,GAAUzE,KAAKI,IAAI,eAAeA,IAAI,UAC1C,IAAgB,OAAZqE,EAEA,WADAzE,MAAKuH,sBAIT,IAAIE,GAAY,IAChBzH,MAAKI,IAAI,UAAUkE,KAAK,SAAS8E,GACzBA,YAAqBjC,IACrBM,EAAY2B,EACZ3B,EAAUvH,QAELkJ,YAAqBxG,GACtBwG,EAAU5E,qBAAqBC,IAC3BgD,GACAA,EAAU1H,QAMlB0H,EAAY,KACZ2B,EAAU5E,qBAAqBC,SAiB3C4E,EAAW7I,SAAS0G,KAAKxG,QACzBQ,WAAY,WACRlB,KAAKyB,MAAM0G,GAAG,gBAAiBnI,KAAKsJ,eAAgBtJ,MACpDA,KAAKsJ,kBAETA,eAAgB,WACVtJ,KAAKyB,MAAMnB,WAAWR,OAASE,KAAKuJ,IAAIrJ,OAASF,KAAKuJ,IAAIxJ,UAOhEyJ,EAAeH,EAAS3I,QACxB+I,QAAS,MAETC,OAAQ,WAEJ,GAAIC,GAAQ7D,EAAE,SACd6D,GAAMC,OAAOC,EAAUC,UAAU9J,KAAKyB,MAAMJ,UAE5C,IAAI0I,GAAY/J,KAAKyB,MAAMrB,IAAK,aAAc,KAE9C,IAAsB,YAAlBJ,KAAKyB,MAAMoB,GACX8G,EAAMhF,KAAK,KAAKwD,GAAG,QAAS,SAAS6B,GACjCA,EAAEC,iBACF9G,OAAO+G,OAAOnK,aAGjB,IAAmB,YAAdgK,EAA0B,CAChC,GAAIjF,GAAO9E,IACX2J,GAAMhF,KAAK,KAAKwD,GAAG,QAAS,SAAS6B,GACjCA,EAAEC,gBACF,IAAIE,GAAO,GAAIvK,GAASsH,MAAQrE,GAAKiC,EAAKrD,MAAMoB,GAAIuH,QAAUtF,EAAKrD,MAAMrB,IAAI,YAC7E+J,GAAKE,SAASC,QAAQ,WAClBnH,OAAOoH,IAAIC,QAASL,OAOhC,MADAnK,MAAKuJ,IAAIK,OAAOD,GACT3J,QAOXyK,EAAuBpB,EAAS3I,QAChC+I,QAAS,MACTiB,UAAW,iBAEXhB,OAAQ,WAEJ,MADA1J,MAAKuJ,IAAIK,OAAQ9D,EAAE,WAAW6E,KAAK3K,KAAKyB,MAAMnB,WAAWqK,OAClD3K,QAOX4K,EAAkBvB,EAAS3I,QAC3B+I,QAAS,MACTiB,UAAW,qBAEXxJ,WAAY,WACRmI,EAAS5F,UAAUvC,WAAWwC,KAAK1D,MACnCA,KAAKyB,MAAM0G,GAAG,cAAenI,KAAK6K,YAAa7K,OAGnD0J,OAAQ,WAEJ1J,KAAKuJ,IAAIK,OAAQC,EAAUiB,cAAc9K,KAAKyB,MAAMJ,UAGpD,IAAI0J,GAAe/K,KAAKuJ,IAAI5E,KAAK,mBAgBjC,OAfAlF,GAAE6E,KAAKtE,KAAKyB,MAAMnB,WAAW+G,MAAO,SAASG,GACzC,GAAIA,YAAe5E,GAAM,CACrB,GAAIoI,GAAY,GAAIxB,IAAc/H,MAAO+F,EAAKkD,UAAW,aACzDM,GAAUtB,SACVqB,EAAanB,OAAOoB,EAAUzB,SAE7B,IAAI/B,YAAeL,GAAkB,CACtC,GAAI8D,GAAa,GAAIR,IAAsBhJ,MAAO+F,GAClDyD,GAAWvB,SACXqB,EAAanB,OAAOqB,EAAW1B,QAMhCvJ,MAGXkL,QACIC,8BAA+B,UAMnChL,OAAQ,WACJH,KAAKyB,MAAMxB,IAAI,QAASD,KAAKyB,MAAMnB,WAAWgH,OAMlDuD,YAAa,WACR7K,KAAKyB,MAAMnB,WAAWgH,KACnBtH,KAAKuJ,IAAI6B,SAAS,oBAAoBC,UAAU,QAChDrL,KAAKuJ,IAAI6B,SAAS,oBAAoBE,QAAQ,WAKtDC,EAAiB/K,SAAS0G,KAAKxG,QAC/B+I,QAAS,MACT5G,GAAI,cACJ6H,UAAW,MAEXQ,QACIM,MAAS,mBACTC,eAAgB,gBAChBC,0BAA2B,SAG/BhC,OAAQ,WAYJ,MAXA1J,MAAKuJ,IAAIK,OAAQC,EAAUhB,YAAY7I,KAAKyB,MAAMJ,WAC7CrB,KAAKyB,MAAMpB,cACZL,KAAKuJ,IAAIrJ,OAIT4F,EAAE,eAAe6F,GAAG,aACpB3L,KAAKuJ,IAAIqC,IAAI,MAAM,QAGvB5L,KAAKuJ,IAAI5E,KAAK,WAAWkH,UAClB7L,MAGX8L,iBAAkB,WACd9L,KAAKuJ,IAAI5E,KAAK,UAAUoH,QAAQpJ,UAGpCqJ,MAAO,WAIH,MAHAhM,MAAKyB,MAAMkH,eACX3I,KAAKuJ,IAAI5E,KAAK,UAAUsH,IAAI,IAC5BjM,KAAK8L,oBACE,GAGXI,cAAe,SAAUC,GAErB,MAAMnM,MAAKyB,MAAMnB,WAAqB,WAChCN,KAAKyB,MAAMnB,WAAW4H,YAAciE,EAAOC,OAC7CpM,KAAKgM,SACE,OAEXhM,MAAKyB,MAAMxB,IAAI,QAASD,KAAKuJ,IAAI5E,KAAK,UAAUsH,UAQpDI,EAAgB7L,SAAS0G,KAAKxG,QAC9B+I,QAAS,MACTiB,UAAW,WAKXxJ,WAAY,WACRlB,KAAKyB,MAAMrB,IAAI,eAAe+H,GAAG,iBAAkBnI,KAAKsM,sBAAuBtM,OAGnF0J,OAAQ,WACJ,GAAI5E,GAAO9E,KAGPuM,EAAc,GAAIhB,IAAkB9J,MAAOzB,KAAKyB,MAAMrB,IAAI,gBAiC9D,OAhCAmM,GAAY7C,SACZ5E,EAAKyE,IAAIK,OAAO2C,EAAYhD,KAG5BvJ,KAAKyB,MAAMrB,IAAI,UAAUkE,KAAK,SAAS8E,GACnC,GAAIA,YAAqBhC,GAAa,CAClC,GAAIoF,GAAqB,GAAI5B,IAAiBnJ,MAAO2H,GACrDoD,GAAmB9C,SACnB5E,EAAKyE,IAAIK,OAAO4C,EAAmBjD,SAElC,IAAIH,YAAqBxG,GAAM,CAChC,GAAIoI,GAAY,GAAIxB,IAAc/H,MAAO2H,EAAWsB,UAAW,sBAC/DM,GAAUtB,SACV5E,EAAKyE,IAAIK,OAAOoB,EAAUzB,SAEzB,IAAIH,YAAqBjC,GAAkB,CAC5C,GAAI8D,GAAa,GAAIR,IAAsBhJ,MAAO2H,GAClD6B,GAAWvB,SACX5E,EAAKyE,IAAIK,OAAOqB,EAAW1B,QAKnCzE,EAAKyE,IAAI5E,KAAK,eAAe6G,MAAM,SAASxB,GAExC,GACIpE,GAAUE,EAAE9F,MAAMyM,KAAK,SAASC,MAAM,OAAO,GAC7C5I,EAAOgB,EAAKrD,MAAMrB,IAAI,SAASA,IAAIwF,EAEvCd,GAAK6H,QAAQ,kBAAmB3C,EAAGlG,KAGhC9D,MAGXsM,sBAAuB,WACnB,GAAI7H,GAAUzE,KAAKyB,MAAMrB,IAAI,eAAeA,IAAI,UAC5CqE,IAA8B,IAAnBA,EAAQ4D,OACnBvC,EAAE,sBAAsB/F,OAGxB+F,EAAE,sBAAsB5F,UAQhC0M,EAAepM,SAAS0G,KAAKxG,QAC7BgK,UAAW,WAEXhB,OAAQ,WACJ1J,KAAKuJ,IAAI6B,WAAWjH,SACpBnE,KAAKuJ,IAAIK,OAAQC,EAAUgD,UAAU7M,KAAKyB,MAAMJ,cAkDpDwI,GA3C4BrJ,SAAS0G,KAAKxG,QAC1CgK,UAAW,kBAEXxJ,WAAY,WACRlB,KAAK8M,gBAAkB,GAAIT,IAAeU,WAAY/M,KAAK+M,aAC3D/M,KAAKgN,eAAiB,GAAIJ,IAG9BlD,OAAQ,WAEJ1J,KAAK8M,gBAAgBpD,SACrB1J,KAAK8M,gBAAgBvD,IAAIqC,IAAI,QAAS,QACtC5L,KAAKuJ,IAAIK,OAAO5J,KAAK8M,gBAAgBvD,KAGrCvJ,KAAKgN,eAAezD,IAAIrJ,OACxBF,KAAKuJ,IAAIK,OAAO5J,KAAKgN,eAAezD,IAGpC,IAAIzE,GAAO9E,IACXA,MAAK8M,gBAAgB3E,GAAG,kBAAmB,SAAS6B,EAAGlG,GAEnDkG,EAAEC,iBAEFnF,EAAKmI,UAAUnJ,MAOvBmJ,UAAW,SAASnJ,GAChB,GAAIgB,GAAO9E,IACX8D,GAAKoJ,QAAQC,KAAM,WACfrI,EAAKkI,eAAevL,MAAQqC,EAC5BgB,EAAKkI,eAAetD,SACpB5E,EAAKkI,eAAezD,IAAIxJ,OACxB+F,EAAE,SAASsH,MAAM,eAQzBvE,YAAcpJ,EAAE4N,UACZ,gFACQ,4EACR,4DAEA,kFACFC,KAAK,KAGPxC,cAAgBrL,EAAE4N,UACd,sDACI,4DACJ,SACA,sEACI,oCACJ,SACFC,KAAK,KAGPxD,UAAYrK,EAAE4N,UACV,wBACI,2CACA,wDACI,eACJ,UACA,YACJ,UACA,2GACI,cACJ,OACA,uBACFC,KAAK,KAIPT,UAAYpN,EAAE4N,UACV,kFACA,6BACI,gDACA,yBACI,6DACA,+BACI,oBACJ,SACA,mDACI,oBACJ,SACA,mCACJ,SACA,YACJ,SACA,sCACI,qFACJ,SACA,yBACI,kDACJ,UAGFC,KAAK,KAAOC,SAAU,UAK5B,QACIhN,cAAeA,EACfoB,qBAAsBA,EACtBU,oBAAqBA,EACrBO,KAAMA,EACNqE,eAAgBA,EAChBU,WAAYA,EACZiB,UAAWA,EACXyD,cAAeA,EACfO,aAAcA"} \ No newline at end of file diff --git a/static/scripts/bundled/analysis.bundled.js b/static/scripts/bundled/analysis.bundled.js index 7a5cf1a420c..c713aeb1e68 100644 --- a/static/scripts/bundled/analysis.bundled.js +++ b/static/scripts/bundled/analysis.bundled.js @@ -1,13 +1,14 @@ -webpackJsonp([3,1],[function(e,t,i){(function(e,t){var n=i(1),s=n,o=i(57).GalaxyApp,a=i(56),r=i(10),l=i(90),c=i(89),d=i(59),h=i(18),u=i(45);window.app=function(i,n){window.Galaxy=new o(i,n),Galaxy.debug("analysis app");var p=i.config,g=new l({el:"#left",userIsAnonymous:Galaxy.user.isAnonymous(),search_url:p.search_url,toolbox:p.toolbox,toolbox_in_panel:p.toolbox_in_panel,stored_workflow_menu_entries:p.stored_workflow_menu_entries,nginx_upload_path:p.nginx_upload_path,ftp_upload_site:p.ftp_upload_site,default_genome:p.default_genome,default_extension:p.default_extension}),f=new r.CenterPanel({el:"#center"}),m=new c({el:"#right",galaxyRoot:Galaxy.root,userIsAnonymous:Galaxy.user.isAnonymous(),allow_user_dataset_purge:p.allow_user_dataset_purge}),v=new d.PageLayoutView(e.extend(i,{el:"body",left:g,center:f,right:m}));Galaxy.page=v,Galaxy.params=Galaxy.config.params,Galaxy.toolPanel=g.tool_panel,Galaxy.upload=g.uploadButton,Galaxy.currHistoryPanel=m.historyView,Galaxy.currHistoryPanel.listenToGalaxy(Galaxy),Galaxy.app={display:function(e,t){s(".select2-hidden-accessible").remove(),f.display(e)}};new(t.Router.extend({initialize:function(e){this.options=e},execute:function(e,t,i){Galaxy.debug("router execute:",e,t,i);var n=a.parse(t.pop());t.push(n),e&&e.apply(this,t)},routes:{"(/)":"home","(/)root*":"home","(/)tours(/)(:tour_id)":"show_tours"},show_tours:function(e){e?u.giveTour(e):f.display(new u.ToursView)},home:function(e){e.tool_id||e.job_id?"upload1"===e.tool_id?(Galaxy.upload.show(),this._loadCenterIframe("welcome")):this._loadToolForm(e):e.workflow_id?this._loadCenterIframe("workflow/run?id="+e.workflow_id):e.m_c?this._loadCenterIframe(e.m_c+"/"+e.m_a):this._loadCenterIframe("welcome")},_loadToolForm:function(e){e.id=e.tool_id,f.display(new h.View(e))},_loadCenterIframe:function(e,t){t=t||Galaxy.root,e=t+e,f.$("#galaxy_main").prop("src",e)}}))(i);s(function(){v.render(),v.right.historyView.loadCurrentHistory(),Galaxy.listenTo(v.right.historyView,"history-size-change",function(){Galaxy.user.fetch({url:Galaxy.user.urlRoot()+"/"+(Galaxy.user.id||"current")})}),v.right.historyView.connectToQuotaMeter(v.masthead.quotaMeter),t.history.start({root:Galaxy.root,pushState:!0})})}}).call(t,i(2),i(3))},,,,,,,function(e,t,i){var n,s;(function(o,a){n=[i(4),i(21),i(51),i(20),i(47),i(13),i(8)],s=function(e,t,i,n,s,r,l){var c=o.View.extend({tagName:"label",initialize:function(e){this.model=e&&e.model||new o.Model(e),this.tagName=e.tagName||this.tagName,this.setElement(a("<"+this.tagName+"/>")),this.listenTo(this.model,"change",this.render,this),this.render()},title:function(e){this.model.set("title",e)},value:function(){return this.model.get("title")},render:function(){return this.$el.removeClass().addClass("ui-label").addClass(this.model.get("cls")).html(this.model.get("title")),this}}),d=o.View.extend({initialize:function(e){this.model=e&&e.model||new o.Model({message:null,status:"info",cls:"",persistent:!1,fade:!0}).set(e),this.listenTo(this.model,"change",this.render,this),this.render()},update:function(e){this.model.set(e)},render:function(){this.$el.removeClass().addClass("ui-message").addClass(this.model.get("cls"));var e=this.model.get("status");if(this.model.get("large")?this.$el.addClass(("success"==e&&"done"||"danger"==e&&"error"||e)+"messagelarge"):this.$el.addClass("alert").addClass("alert-"+e),this.model.get("message")){if(this.$el.html(this.model.get("message")),this.$el[this.model.get("fade")?"fadeIn":"show"](),this.timeout&&window.clearTimeout(this.timeout),!this.model.get("persistent")){var t=this;this.timeout=window.setTimeout(function(){t.model.set("message","")},3e3)}}else this.$el.fadeOut();return this}}),h=o.View.extend({initialize:function(e){this.model=e&&e.model||new o.Model({type:"text",placeholder:"",disabled:!1,visible:!0,cls:"",area:!1,color:null,style:null}).set(e),this.tagName=this.model.get("area")?"textarea":"input",this.setElement(a("<"+this.tagName+"/>")),this.listenTo(this.model,"change",this.render,this),this.render()},events:{input:"_onchange"},value:function(e){return void 0!==e&&this.model.set("value","string"==typeof e?e:""),this.model.get("value")},render:function(){return this.$el.removeClass().addClass("ui-"+this.tagName).addClass(this.model.get("cls")).addClass(this.model.get("style")).attr("id",this.model.id).attr("type",this.model.get("type")).attr("placeholder",this.model.get("placeholder")).css("color",this.model.get("color")||"").css("border-color",this.model.get("color")||""),this.model.get("value")!==this.$el.val()&&this.$el.val(this.model.get("value")),this.model.get("disabled")?this.$el.attr("disabled",!0):this.$el.removeAttr("disabled"),this.$el[this.model.get("visible")?"show":"hide"](),this},_onchange:function(){this.value(this.$el.val()),this.model.get("onchange")&&this.model.get("onchange")(this.model.get("value"))}}),u=o.View.extend({initialize:function(e){this.model=e&&e.model||new o.Model(e),this.setElement(a("
").append(this.$info=a("
")).append(this.$hidden=a("
"))),this.listenTo(this.model,"change",this.render,this),this.render()},value:function(e){return void 0!==e&&this.model.set("value",e),this.model.get("value")},render:function(){return this.$el.attr("id",this.model.id),this.$hidden.val(this.model.get("value")),this.model.get("info")?this.$info.show().html(this.model.get("info")):this.$info.hide(),this}});return{Button:r.ButtonDefault,ButtonIcon:r.ButtonIcon,ButtonCheck:r.ButtonCheck,ButtonMenu:r.ButtonMenu,ButtonLink:r.ButtonLink,Input:h,Label:c,Message:d,Modal:l,RadioButton:n.RadioButton,Checkbox:n.Checkbox,Radio:n.Radio,Select:t,Hidden:u,Slider:i,Drilldown:s}}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(3),i(1))},,function(e,t,i){var n,s;(function(o,a,r){n=[i(4),i(7)],s=function(e,t){var i=o.View.extend({visible:!1,initialize:function(i){var n=this;this.model=i&&i.model||new o.Model({id:e.uid(),cls:"ui-portlet",title:"",icon:"",buttons:null,body:null,scrollable:!0,nopadding:!1,operations:null,collapsible:!1,collapsible_button:!1,collapsed:!1}).set(i),this.setElement(this._template()),this.$body=this.$(".portlet-body"),this.$title_text=this.$(".portlet-title-text"),this.$title_icon=this.$(".portlet-title-icon"),this.$header=this.$(".portlet-header"),this.$content=this.$(".portlet-content"),this.$backdrop=this.$(".portlet-backdrop"),this.$buttons=this.$(".portlet-buttons"),this.$operations=this.$(".portlet-operations"),this.model.get("body")&&this.append(this.model.get("body")),this.collapsible_button=new t.ButtonIcon({icon:"fa-eye",tooltip:"Collapse/Expand",cls:"ui-button-icon-plain",onclick:function(){n[n.collapsed?"expand":"collapse"]()}}),this.render()},render:function(){var e=this,t=this.model.attributes;return this.$el.removeClass().addClass(t.cls).attr("id",t.id),this.$header[t.title?"show":"hide"](),this.$title_text.html(t.title),a.each([this.$content,this.$body],function(e){e[t.nopadding?"addClass":"removeClass"]("no-padding")}),t.icon?this.$title_icon.removeClass().addClass("portlet-title-icon fa").addClass(t.icon).show():this.$title_icon.hide(),this.$title_text[t.collapsible?"addClass":"removeClass"]("no-highlight collapsible").off(),t.collapsible&&(this.$title_text.on("click",function(){e[e.collapsed?"expand":"collapse"]()}),t.collapsed?this.collapse():this.expand()),t.buttons?(this.$buttons.empty().show(),r.each(this.model.get("buttons"),function(t,i){i.$el.prop("id",t),e.$buttons.append(i.$el)})):this.$buttons.hide(),this.$operations.empty,t.collapsible_button&&this.$operations.append(this.collapsible_button.$el),t.operations&&r.each(t.operations,function(t,i){i.$el.prop("id",t),e.$operations.append(i.$el)}),this},append:function(e){this.$body.append(e)},empty:function(){this.$body.empty()},header:function(){return this.$header},body:function(){return this.$body},show:function(){this.visible=!0,this.$el.fadeIn("fast")},hide:function(){this.visible=!1,this.$el.hide()},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){this.$operations.find("#"+e).off("click").on("click",t)},title:function(e){return e&&this.$title_text.html(e),this.$title_text.html()},collapse:function(){this.collapsed=!0,this.$content.height("0%"),this.$body.hide(),this.collapsible_button.setIcon("fa-eye-slash")},expand:function(){this.collapsed=!1,this.$content.height("100%"),this.$body.fadeIn("fast"),this.collapsible_button.setIcon("fa-eye")},disable:function(){this.$backdrop.show()},enable:function(){this.$backdrop.hide()},_template:function(){return r("
").append(r("
").addClass("portlet-header").append(r("
").addClass("portlet-operations")).append(r("
").addClass("portlet-title").append(r("").addClass("portlet-title-icon")).append(r("").addClass("portlet-title-text")))).append(r("
").addClass("portlet-content").append(r("
").addClass("portlet-body")).append(r("
").addClass("portlet-buttons"))).append(r("
").addClass("portlet-backdrop"))}});return{View:i}}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(3),i(2),i(1))},,function(e,t,i){var n,s;(function(o,a,r){n=[i(8),i(23),i(14)],s=function(e,t,i){var n=o.Model.extend({}),s=o.Model.extend({defaults:{id:"",type:"",name:"",hda_ldda:"hda",metadata:null},initialize:function(){this.get("metadata")||this._set_metadata(),this.on("change",this._set_metadata,this)},_set_metadata:function(){var e=new n;a.each(a.keys(this.attributes),function(t){if(0===t.indexOf("metadata_")){var i=t.split("metadata_")[1];e.set(i,this.attributes[t]),delete this.attributes[t]}},this),this.set("metadata",e,{silent:!0})},get_metadata:function(e){return this.attributes.metadata.get(e)},urlRoot:Galaxy.root+"api/datasets"}),l=s.extend({defaults:a.extend({},s.prototype.defaults,{chunk_url:null,first_data_chunk:null,offset:0,at_eof:!1}),initialize:function(e){s.prototype.initialize.call(this),this.attributes.first_data_chunk&&(this.attributes.offset=this.attributes.first_data_chunk.offset),this.attributes.chunk_url=Galaxy.root+"dataset/display?dataset_id="+this.id,this.attributes.url_viz=Galaxy.root+"visualization"},get_next_chunk:function(){if(this.attributes.at_eof)return null;var e=this,t=r.Deferred();return r.getJSON(this.attributes.chunk_url,{offset:e.attributes.offset}).success(function(i){var n;""!==i.ck_data?(n=i,e.attributes.offset=i.offset):(e.attributes.at_eof=!0,n=null),t.resolve(n)}),t}}),c=o.Collection.extend({model:s}),d=o.View.extend({initialize:function(e){this.row_count=0,this.loading_chunk=!1,new p({model:e.model,$el:this.$el})},expand_to_container:function(){this.$el.height()").attr("id","loading_indicator"),this.$el.append(this.loading_indicator);var e=r("").attr({id:"content_table",cellpadding:0});this.$el.append(e);var t=this.model.get_metadata("column_names"),i=r("").appendTo(e),n=r("").appendTo(i);if(t)n.append("");else for(var s=1;s<=this.model.get_metadata("columns");s++)n.append("");var o=this,a=this.model.get("first_data_chunk");a?this._renderChunk(a):r.when(o.model.get_next_chunk()).then(function(e){o._renderChunk(e)}),this.scroll_elt.scroll(function(){o.attempt_to_fetch()})},scrolled_to_bottom:function(){return!1},_renderCell:function(e,t,i){var n=r("");t.append(e),this.row.append(t)},appendHeader:function(){this.$thead.append(this.row),this.row=a("")},add:function(e,t,i){var n=a("");t&&n.css("width",t),i&&n.css("text-align",i),n.append(e),this.row.append(n)},append:function(e,t){this._commit(e,t,!1)},prepend:function(e,t){this._commit(e,t,!0)},get:function(e){return this.$el.find("#"+e)},del:function(e){var t=this.$tbody.find("#"+e);t.length>0&&(t.remove(),this.row_count--,this._refresh())},delAll:function(){this.$tbody.empty(),this.row_count=0,this._refresh()},value:function(e){this.before=this.$tbody.find(".current").attr("id"),void 0!==e&&(this.$tbody.find("tr").removeClass("current"),e&&this.$tbody.find("#"+e).addClass("current"));var t=this.$tbody.find(".current").attr("id");return void 0===t?null:(t!=this.before&&this.options.onchange&&this.options.onchange(e),t)},size:function(){return this.$tbody.find("tr").length},_commit:function(e,t,i){this.del(e),this.row.attr("id",e),i?this.$tbody.prepend(this.row):this.$tbody.append(this.row),t&&(this.row.hide(),this.row.fadeIn()),this.row=this._row(),this.row_count++,this._refresh()},_row:function(){return a('')},_onclick:function(e){var t=this.value(),i=a(e.target).closest("tr").attr("id");""!=i&&i&&t!=i&&(this.options.onconfirm?this.options.onconfirm(i):this.value(i))},_ondblclick:function(e){var t=this.value();t&&this.options.ondblclick&&this.options.ondblclick(t)},_refresh:function(){0==this.row_count?this.$tmessage.show():this.$tmessage.hide()},_template:function(e){return'
"+t.join("")+""+s+"").text(e),s=this.model.get_metadata("column_types");return void 0!==i?n.attr("colspan",i).addClass("stringalign"):s&&t"),n=this.model.get_metadata("columns");return this.row_count%2!==0&&i.addClass("dark_row"),t.length===n?a.each(t,function(e,t){i.append(this._renderCell(e,t))},this):t.length>n?(a.each(t.slice(0,n-1),function(e,t){i.append(this._renderCell(e,t))},this),i.append(this._renderCell(t.slice(n-1).join(" "),n-1))):1===t.length?i.append(this._renderCell(e,0,n)):(a.each(t,function(e,t){i.append(this._renderCell(e,t))},this),a.each(a.range(n-t.length),function(){i.append(r(""))})),this.row_count++,i},_renderChunk:function(e){var t=this.$el.find("table");a.each(e.ck_data.split("\n"),function(e,i){""!==e&&t.append(this._renderRow(e))},this)}}),h=d.extend({initialize:function(e){d.prototype.initialize.call(this,e),scroll_elt=a.find(this.$el.parents(),function(e){return"auto"===r(e).css("overflow")}),scroll_elt||(scroll_elt=window),this.scroll_elt=r(scroll_elt)},scrolled_to_bottom:function(){return this.$el.height()-this.scroll_elt.scrollTop()-this.scroll_elt.height()<=0}}),u=d.extend({initialize:function(e){d.prototype.initialize.call(this,e),this.scroll_elt=this.$el.css({position:"relative",overflow:"scroll",height:e.height||"500px"})},scrolled_to_bottom:function(){return this.$el.scrollTop()+this.$el.innerHeight()>=this.el.scrollHeight}}),p=o.View.extend({col:{chrom:null,start:null,end:null},url_viz:null,dataset_id:null,genome_build:null,file_ext:null,initialize:function(e){function t(e,t){for(var i=0;i").attr("type","button").append(this.$icon=a("")).append(this.$title=a("")).append(this.$progress=a("
").append(this.$progress_bar=a("
")))),this.listenTo(this.model,"change",this.render,this),this.render()},render:function(){var e=this,t=this.model.attributes;this.$el.removeClass().addClass("ui-button-default").addClass(t.disabled&&"disabled").attr("id",t.id).attr("disabled",t.disabled).css("float",t.floating).off("click").on("click",function(){a(".tooltip").hide(),t.onclick&&!e.disabled&&t.onclick()}).tooltip({title:t.tooltip,placement:"bottom"}),this.$progress.addClass("progress").css("display",-1!==t.percentage?"block":"none"),this.$progress_bar.addClass("progress-bar").css({width:t.percentage+"%"}),this.$icon.removeClass().addClass("icon fa"),this.$title.removeClass().addClass("title"),t.wait?(this.$el.addClass(t.wait_cls).prop("disabled",!0),this.$icon.addClass("fa-spinner fa-spin ui-margin-right"),this.$title.html(t.wait_text)):(this.$el.addClass(t.cls),this.$icon.addClass(t.icon),this.$title.html(t.title),t.icon&&t.title&&this.$icon.addClass("ui-margin-right"))},show:function(){this.$el.show()},hide:function(){this.$el.hide()},disable:function(){this.model.set("disabled",!0)},enable:function(){this.model.set("disabled",!1)},wait:function(){this.model.set("wait",!0)},unwait:function(){this.model.set("wait",!1)},setIcon:function(e){this.model.set("icon",e)}}),i=t.extend({initialize:function(t){this.model=t&&t.model||new o.Model({id:e.uid(),title:"",icon:"",cls:""}).set(t),this.setElement(a("").append(this.$icon=a(""))),this.listenTo(this.model,"change",this.render,this),this.render()},render:function(){var e=this.model.attributes;this.$el.removeClass().addClass(e.cls).attr({id:e.id,href:e.href||"javascript:void(0)",title:e.title,target:e.target||"_top",disabled:e.disabled}).off("click").on("click",function(){e.onclick&&!e.disabled&&e.onclick()}),this.$icon.removeClass().addClass(e.icon)}}),n=o.View.extend({initialize:function(t){this.model=t&&t.model||new o.Model({id:e.uid(),title:"Select/Unselect all",icons:["fa-square-o","fa-minus-square-o","fa-check-square-o"],value:0,onchange:function(){}}).set(t),this.setElement(a("
").append(this.$icon=a("")).append(this.$title=a(""))),this.listenTo(this.model,"change",this.render,this),this.render()},render:function(e){var t=this,e=this.model.attributes;this.$el.addClass("ui-button-check").off("click").on("click",function(){t.model.set("value",0===t.model.get("value")&&2||0),e.onclick&&e.onclick()}),this.$title.html(e.title),this.$icon.removeClass().addClass("icon fa ui-margin-right").addClass(e.icons[e.value])},value:function(e,t){return void 0!==e&&(t&&0!==e&&(e=e!==t&&1||2),this.model.set("value",e),this.model.get("onchange")(this.model.get("value"))),this.model.get("value")}}),s=t.extend({initialize:function(t){this.model=t&&t.model||new o.Model({id:e.uid(),title:"",floating:"right",icon:"",cls:"ui-button-icon",disabled:!1}).set(t),this.setElement(a("
").append(this.$button=a("
").append(this.$icon=a("")).append(this.$title=a("")))),this.listenTo(this.model,"change",this.render,this),this.render()},render:function(e){var e=this.model.attributes;this.$el.removeClass().addClass(e.cls).addClass(e.disabled&&"disabled").attr("disabled",e.disabled).attr("id",e.id).css("float",e.floating).off("click").on("click",function(){a(".tooltip").hide(),!e.disabled&&e.onclick&&e.onclick()}),this.$button.addClass("button").tooltip({title:e.tooltip,placement:"bottom"}),this.$icon.removeClass().addClass("icon fa").addClass(e.icon),this.$title.addClass("title").html(e.title),e.icon&&e.title&&this.$icon.addClass("ui-margin-right")}}),r=t.extend({$menu:null,initialize:function(e){this.model=e&&e.model||new o.Model({id:"",title:"",floating:"right",pull:"right",icon:null,onclick:null,cls:"ui-button-icon ui-button-menu",tooltip:"",target:"",href:"",onunload:null,visible:!0,tag:""}).set(e),this.setElement(a("
").append(this.$root=a("
").append(this.$icon=a("")).append(this.$title=a("")))),this.listenTo(this.model,"change",this.render,this),this.render()},render:function(){var e=this.model.attributes;this.$el.removeClass().addClass("dropdown").addClass(e.cls).attr("id",e.id).css({"float":e.floating,display:e.visible?"block":"none"}),this.$root.addClass("root button dropdown-toggle").attr("data-toggle","dropdown").tooltip({title:e.tooltip,placement:"bottom"}).off("click").on("click",function(t){a(".tooltip").hide(),t.preventDefault(),e.onclick&&e.onclick()}),this.$icon.removeClass().addClass("icon fa").addClass(e.icon),this.$title.removeClass().addClass("title").html(e.title),e.icon&&e.title&&this.$icon.addClass("ui-margin-right")},addMenu:function(t){var t=e.merge(t,{title:"",target:"",href:"",onclick:null,divider:!1,icon:null,cls:"button-menu btn-group"});this.$menu||(this.$menu=a("
","
"].join("")}});return{CitationView:n,CitationListView:s}}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(3))},function(e,t,i){var n,s;(function(o,a,r){n=[i(42),i(32),i(6),i(5)],s=function(e,t,i,n){"use strict";var s=e.FoldoutListItemView,l=e.ListItemView,c=s.extend({className:s.prototype.className+" dataset-collection",id:function(){return["dataset_collection",this.model.get("id")].join("-")},initialize:function(e){this.linkTarget=e.linkTarget||"_blank",this.hasUser=e.hasUser,s.prototype.initialize.call(this,e)},_setUpListeners:function(){s.prototype._setUpListeners.call(this),this.listenTo(this.model,"change",function(e,t){o.has(e.changed,"deleted")?this.render():o.has(e.changed,"element_count")&&this.$("> .title-bar .subtitle").replaceWith(this._renderSubtitle())})},_renderSubtitle:function(){return a(this.templates.subtitle(this.model.toJSON(),this))},_getFoldoutPanelOptions:function(){var e=s.prototype._getFoldoutPanelOptions.call(this);return o.extend(e,{linkTarget:this.linkTarget,hasUser:this.hasUser})},$selector:function(){return this.$("> .selector")},toString:function(){var e=this.model?this.model+"":"(no model)";return"DCListItemView("+e+")"}});c.prototype.templates=function(){var e=o.extend({},s.prototype.templates.warnings,{error:i.wrapTemplate(["<% if( model.error ){ %>",'
',n("There was an error getting the data for this collection"),": <%- model.error %>","
","<% } %>"]),purged:i.wrapTemplate(["<% if( model.purged ){ %>",'
',n("This collection has been deleted and removed from disk"),"
","<% } %>"]),deleted:i.wrapTemplate(["<% if( model.deleted && !model.purged ){ %>",'
',n("This collection has been deleted"),"
","<% } %>"])}),t=i.wrapTemplate(['
','
','<%- collection.element_identifier || collection.name %>',"
",'
',"
"],"collection"),a=i.wrapTemplate(['
','<% var countText = collection.element_count? ( collection.element_count + " " ) : ""; %>','<% if( collection.collection_type === "list" ){ %>',n("a list of <%- countText %>datasets"),'<% } else if( collection.collection_type === "paired" ){ %>',n("a pair of datasets"),'<% } else if( collection.collection_type === "list:paired" ){ %>',n("a list of <%- countText %>dataset pairs"),'<% } else if( collection.collection_type === "list:list" ){ %>',n("a list of <%- countText %>dataset lists"),"<% } %>","
"],"collection");return o.extend({},s.prototype.templates,{warnings:e,titleBar:t,subtitle:a})}();var d=l.extend({className:l.prototype.className+" dataset-collection-element",initialize:function(e){e.logger&&(this.logger=this.model.logger=e.logger),this.log("DCEListItemView.initialize:",e),l.prototype.initialize.call(this,e)},toString:function(){var e=this.model?this.model+"":"(no model)";return"DCEListItemView("+e+")"}});d.prototype.templates=function(){var e=i.wrapTemplate(['
','
','<%- element.element_identifier %>',"
",'
',"
"],"element");return o.extend({},l.prototype.templates,{titleBar:e})}();var h=t.DatasetListItemView.extend({className:t.DatasetListItemView.prototype.className+" dataset-collection-element",initialize:function(e){e.logger&&(this.logger=this.model.logger=e.logger),this.log("DatasetDCEListItemView.initialize:",e),t.DatasetListItemView.prototype.initialize.call(this,e)},_fetchModelDetails:function(){var e=this;return e.model.inReadyState()&&!e.model.hasDetails()?e.model.fetch({silent:!0}):r.when()},toString:function(){var e=this.model?this.model+"":"(no model)";return"DatasetDCEListItemView("+e+")"}});h.prototype.templates=function(){var e=i.wrapTemplate(['
','','
','<%- element.element_identifier %>',"
","
"],"element");return o.extend({},t.DatasetListItemView.prototype.templates,{titleBar:e})}();var u=c.extend({className:c.prototype.className+" dataset-collection-element",_swapNewRender:function(e){c.prototype._swapNewRender.call(this,e);var t=this.model.get("state")||"ok";return this.$el.addClass("state-"+t),this.$el},toString:function(){var e=this.model?this.model+"":"(no model)";return"NestedDCDCEListItemView("+e+")"}});return{DCListItemView:c,DCEListItemView:d,DatasetDCEListItemView:h,NestedDCDCEListItemView:u}}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(2),i(1),i(1))},function(e,t,i){var n,s;(function(o,a,r){n=[i(70),i(6),i(5)],s=function(e,t,i){"use strict";var n={defaults:{model_class:"DatasetCollectionElement",element_identifier:null,element_index:null,element_type:null},_mergeObject:function(e){return o.extend(e,e.object,{element_id:e.id}),delete e.object,e},constructor:function(e,t){e=this._mergeObject(e),this.idAttribute="element_id",a.Model.apply(this,arguments)},parse:function(e,t){var i=e;return i=this._mergeObject(i)}},s=a.Model.extend(t.LoggableMixin).extend(n).extend({_logNamespace:"collections"}),l=a.Collection.extend(t.LoggableMixin).extend({_logNamespace:"collections",model:s,toString:function(){return["DatasetCollectionElementCollection(",this.length,")"].join("")}}),c=e.DatasetAssociation.extend(t.mixin(n,{url:function(){return this.has("history_id")?Galaxy.root+"api/histories/"+this.get("history_id")+"/contents/"+this.get("id"):(console.warn("no endpoint for non-hdas within a collection yet"),Galaxy.root+"api/datasets")},defaults:o.extend({},e.DatasetAssociation.prototype.defaults,n.defaults),constructor:function(e,t){this.debug(" DatasetDCE.constructor:",e,t),n.constructor.call(this,e,t)},hasDetails:function(){return this.elements&&this.elements.length},toString:function(){var e=this.get("element_identifier");return["DatasetDCE(",e,")"].join("")}})),d=l.extend({model:c,toString:function(){return["DatasetDCECollection(",this.length,")"].join("")}}),h=a.Model.extend(t.LoggableMixin).extend(t.SearchableModelMixin).extend({_logNamespace:"collections",defaults:{collection_type:null,deleted:!1},collectionClass:l,initialize:function(e,t){this.debug(this+"(DatasetCollection).initialize:",e,t,this),this.elements=this._createElementsModel(),this.on("change:elements",function(){this.log("change:elements"),this.elements=this._createElementsModel()})},_createElementsModel:function(){this.debug(this+"._createElementsModel",this.collectionClass,this.get("elements"),this.elements);var e=this.get("elements")||[];return this.unset("elements",{silent:!0}),this.elements=new this.collectionClass(e),this.elements},toJSON:function(){var e=a.Model.prototype.toJSON.call(this);return this.elements&&(e.elements=this.elements.toJSON()),e},inReadyState:function(){var e=this.get("populated");return this.isDeletedOrPurged()||e},hasDetails:function(){return 0!==this.elements.length},getVisibleContents:function(e){return this.elements},parse:function(e,t){var i=a.Model.prototype.parse.call(this,e,t);return i.create_time&&(i.create_time=new Date(i.create_time)),i.update_time&&(i.update_time=new Date(i.update_time)),i},"delete":function(e){return this.get("deleted")?r.when():this.save({deleted:!0},e)},undelete:function(e){return!this.get("deleted")||this.get("purged")?r.when():this.save({deleted:!1},e)},isDeletedOrPurged:function(){return this.get("deleted")||this.get("purged")},searchAttributes:["name"],toString:function(){var e=[this.get("id"),this.get("name")||this.get("element_identifier")];return"DatasetCollection("+e.join(",")+")"}}),u=h.extend({collectionClass:d,toString:function(){return"List"+h.prototype.toString.call(this)}}),p=u.extend({toString:function(){return"Pair"+h.prototype.toString.call(this)}}),g=h.extend(t.mixin(n,{constructor:function(e,t){this.debug(" NestedDCDCE.constructor:",e,t),n.constructor.call(this,e,t)},toString:function(){var e=this.object?""+this.object:this.get("element_identifier");return["NestedDCDCE(",e,")"].join("")}})),f=l.extend({model:g,toString:function(){return["NestedDCDCECollection(",this.length,")"].join("")}}),m=p.extend(t.mixin(n,{constructor:function(e,t){this.debug(" NestedPairDCDCE.constructor:",e,t),n.constructor.call(this,e,t)},toString:function(){var e=this.object?""+this.object:this.get("element_identifier");return["NestedPairDCDCE(",e,")"].join("")}})),v=f.extend({model:m,toString:function(){return["NestedPairDCDCECollection(",this.length,")"].join("")}}),_=h.extend({collectionClass:v,toString:function(){return["ListPairedDatasetCollection(",this.get("name"),")"].join("")}}),y=u.extend(t.mixin(n,{constructor:function(e,t){this.debug(" NestedListDCDCE.constructor:",e,t),n.constructor.call(this,e,t)},toString:function(){var e=this.object?""+this.object:this.get("element_identifier");return["NestedListDCDCE(",e,")"].join("")}})),w=f.extend({model:y,toString:function(){return["NestedListDCDCECollection(",this.length,")"].join("")}}),b=h.extend({collectionClass:w,toString:function(){return["ListOfListsDatasetCollection(",this.get("name"),")"].join("")}});return{ListDatasetCollection:u,PairDatasetCollection:p,ListPairedDatasetCollection:_,ListOfListsDatasetCollection:b}}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(2),i(3),i(1))},function(e,t,i){var n,s;(function(o,a,r,l){n=[i(39),i(12),i(6),i(8),i(87),i(5),i(84)],s=function(e,t,i,n,s,c){"use strict";function d(e){var t=e.toJSON(),i=f(t,{creationFn:function(t,i){return t=t.map(function(e){return{id:e.id,name:e.name,src:"dataset"===e.history_content_type?"hda":"hdca"}}),e.createHDCA(t,"list",i)}});return i}var h="collections",u=o.View.extend(i.LoggableMixin).extend({_logNamespace:h,tagName:"li",className:"collection-element",initialize:function(e){this.element=e.element||{},this.selected=e.selected||!1},render:function(){return this.$el.attr("data-element-id",this.element.id).attr("draggable",!0).html(this.template({element:this.element})),this.selected&&this.$el.addClass("selected"),this},template:a.template(['
',"<%- element.name %>","",'"].join("")),select:function(e){this.$el.toggleClass("selected",e),this.trigger("select",{source:this,selected:this.$el.hasClass("selected")})},discard:function(){var e=this,t=this.$el.parent().width();this.$el.animate({"margin-right":t},"fast",function(){e.trigger("discard",{source:e}),e.destroy()})},destroy:function(){this.off(),this.$el.remove()},events:{click:"_click","click .name":"_clickName","click .discard":"_clickDiscard",dragstart:"_dragstart",dragend:"_dragend",dragover:"_sendToParent",drop:"_sendToParent"},_click:function(e){e.stopPropagation(),this.select(e)},_clickName:function(e){e.stopPropagation(),e.preventDefault();var t=([c("Enter a new name for the element"),":\n(",c("Note that changing the name here will not rename the dataset"),")"].join(""),prompt(c("Enter a new name for the element")+":",this.element.name));t&&(this.element.name=t,this.render())},_clickDiscard:function(e){e.stopPropagation(),this.discard()},_dragstart:function(e){e.originalEvent&&(e=e.originalEvent),e.dataTransfer.effectAllowed="move",e.dataTransfer.setData("text/plain",JSON.stringify(this.element)),this.$el.addClass("dragging"),this.$el.parent().trigger("collection-element.dragstart",[this])},_dragend:function(e){this.$el.removeClass("dragging"),this.$el.parent().trigger("collection-element.dragend",[this])},_sendToParent:function(e){this.$el.parent().trigger(e)},toString:function(){return"DatasetCollectionElementView()"}}),p=o.View.extend(i.LoggableMixin).extend({_logNamespace:h,elementViewClass:u,collectionClass:e.HistoryListDatasetCollection,className:"list-collection-creator collection-creator flex-row-container",minElements:1,defaultAttributes:{creationFn:function(){throw new TypeError("no creation fn for creator")},oncreate:function(){},oncancel:function(){},autoscrollDist:24,highlightClr:"rgba( 64, 255, 255, 1.0 )"},initialize:function(e){this.metric("ListCollectionCreator.initialize",e);var t=this;a.each(this.defaultAttributes,function(i,n){i=e[n]||i,t[n]=i}),t.initialElements=e.elements||[],this._instanceSetUp(),this._elementsSetUp(),this._setUpBehaviors()},_instanceSetUp:function(){this.selectedIds={},this.$dragging=null,this.blocking=!1},_elementsSetUp:function(){this.invalidElements=[],this.workingElements=[],this.elementViews=[],this.workingElements=this.initialElements.slice(0),this._ensureElementIds(),this._validateElements(),this._mangleDuplicateNames(),this._sortElements()},_ensureElementIds:function(){return this.workingElements.forEach(function(e){e.hasOwnProperty("id")||(e.id=a.uniqueId())}),this.workingElements},_validateElements:function(){var e=this;return e.invalidElements=[],this.workingElements=this.workingElements.filter(function(t){var i=e._isElementInvalid(t);return i&&e.invalidElements.push({element:t,text:i}),!i}),this.workingElements},_isElementInvalid:function(e){return"dataset"!==e.history_content_type?c("is not a dataset"):e.state!==t.OK?c(a.contains(t.NOT_READY_STATES,e.state)?"hasn't finished running yet":"has errored, is paused, or is not accessible"):e.deleted||e.purged?c("has been deleted or purged"):null},_mangleDuplicateNames:function(){var e=900,t=1,i={};this.workingElements.forEach(function(n){for(var s=n.name;i.hasOwnProperty(s);)if(s=n.name+" ("+t+")",t+=1,t>=e)throw new Error("Safety hit in while loop - thats impressive");n.name=s,i[n.name]=!0})},_sortElements:function(e){},render:function(e,t){return this.workingElements.length .clear-selected").show():this.$(".collection-elements-controls > .clear-selected").hide()},_renderList:function(e,t){var i=this,n=l("
"),s=i.$list();a.each(this.elementViews,function(e){e.destroy(),i.removeElementView(e)}),i.workingElements.forEach(function(e){var t=i._createElementView(e);n.append(t.$el)}),i._renderClearSelected(),s.empty().append(n.children()),a.invoke(i.elementViews,"render"),s.height()>s.css("max-height")?s.css("border-width","1px 0px 1px 0px"):s.css("border-width","0px")},_createElementView:function(e){var t=new this.elementViewClass({element:e,selected:a.has(this.selectedIds,e.id)});return this.elementViews.push(t),this._listenToElementView(t),t},_listenToElementView:function(e){var t=this;t.listenTo(e,{select:function(e){var i=e.source.element;e.selected?t.selectedIds[i.id]=!0:delete t.selectedIds[i.id],t.trigger("elements:select",e)},discard:function(e){t.trigger("elements:discard",e)}})},addElementView:function(e){},removeElementView:function(e){delete this.selectedIds[e.element.id],this._renderClearSelected(),this.elementViews=a.without(this.elementViews,e),this.stopListening(e)},_renderNoElementsLeft:function(){this._disableNameAndCreate(!0),this.$(".collection-elements").append(this.templates.noElementsLeft())},_elementToJSON:function(e){return e},createList:function(e){if(!this.workingElements.length){var t=c("No valid elements for final list")+". ";return t+=''+c("Cancel")+" ",t+=c("or"),t+=' '+c("start over")+".",void this._showAlert(t)}var i=this,n=this.workingElements.map(function(e){return i._elementToJSON(e)});return i.blocking=!0,i.creationFn(n,e).always(function(){i.blocking=!1}).fail(function(e,t,n){i.trigger("error",{xhr:e,status:t,message:c("An error occurred while creating this collection")})}).done(function(e,t,n){i.trigger("collection:created",e,t,n),i.metric("collection:created",e),"function"==typeof i.oncreate&&i.oncreate.call(this,e,t,n)})},_setUpBehaviors:function(){return this.on("error",this._errorHandler),this.once("rendered",function(){this.trigger("rendered:initial",this)}),this.on("elements:select",function(e){this._renderClearSelected()}),this.on("elements:discard",function(e){var t=e.source.element;this.removeElementView(e.source),this.workingElements=a.without(this.workingElements,t),this.workingElements.length||this._renderNoElementsLeft()}),this},_errorHandler:function(e){this.error(e);var t=this;if(content=e.message||c("An error occurred"),e.xhr){var i=e.xhr,n=e.message;0===i.readyState&&0===i.status?content+=": "+c("Galaxy could not be reached and may be updating.")+c(" Try again in a few minutes."):i.responseJSON?content+=":
"+JSON.stringify(i.responseJSON)+"
":content+=": "+n}t._showAlert(content,"alert-danger")},events:{"click .more-help":"_clickMoreHelp","click .less-help":"_clickLessHelp","click .main-help":"_toggleHelp","click .header .alert button":"_hideAlert","click .reset":"reset","click .clear-selected":"clearSelectedElements","click .collection-elements":"clearSelectedElements","dragover .collection-elements":"_dragoverElements","drop .collection-elements":"_dropElements","collection-element.dragstart .collection-elements":"_elementDragstart","collection-element.dragend .collection-elements":"_elementDragend","change .collection-name":"_changeName","keydown .collection-name":"_nameCheckForEnter","click .cancel-create":function(e){"function"==typeof this.oncancel&&this.oncancel.call(this)},"click .create-collection":"_clickCreate"},_clickMoreHelp:function(e){e.stopPropagation(),this.$(".main-help").addClass("expanded"),this.$(".more-help").hide()},_clickLessHelp:function(e){e.stopPropagation(),this.$(".main-help").removeClass("expanded"),this.$(".more-help").show()},_toggleHelp:function(e){e.stopPropagation(),this.$(".main-help").toggleClass("expanded"),this.$(".more-help").toggle()},_showAlert:function(e,t){t=t||"alert-danger",this.$(".main-help").hide(),this.$(".header .alert").attr("class","alert alert-dismissable").addClass(t).show().find(".alert-message").html(e)},_hideAlert:function(e){this.$(".main-help").show(),this.$(".header .alert").hide()},reset:function(){this._instanceSetUp(),this._elementsSetUp(),this.render()},clearSelectedElements:function(e){this.$(".collection-elements .collection-element").removeClass("selected"),this.$(".collection-elements-controls > .clear-selected").hide()},_dragoverElements:function(e){e.preventDefault();var t=this.$list();this._checkForAutoscroll(t,e.originalEvent.clientY);var i=this._getNearestElement(e.originalEvent.clientY);this.$(".element-drop-placeholder").remove();var n=r('
');i.length?i.before(n):t.append(n)},_checkForAutoscroll:function(e,t){var i=2,n=e.offset(),s=e.scrollTop(),o=t-n.top,a=n.top+e.outerHeight()-t;o>=0&&o=0&&ae&&e>o-a)return s}return r()},_dropElements:function(e){e.originalEvent&&(e=e.originalEvent),e.preventDefault(),e.dataTransfer.dropEffect="move";var t=this._getNearestElement(e.clientY);return t.length?this.$dragging.insertBefore(t):this.$dragging.insertAfter(this.$(".collection-elements .collection-element").last()),this._syncOrderToDom(),!1},_syncOrderToDom:function(){var e=this,t=[];this.$(".collection-elements .collection-element").each(function(){var i=r(this).attr("data-element-id"),n=a.findWhere(e.workingElements,{id:i});n?t.push(n):console.error("missing element: ",i)}),this.workingElements=t,this._renderList()},_elementDragstart:function(e,t){t.select(!0),this.$dragging=this.$(".collection-elements .collection-element.selected")},_elementDragend:function(e,t){r(".element-drop-placeholder").remove(),this.$dragging=null},_changeName:function(e){this._validationWarning("name",!!this._getName())},_nameCheckForEnter:function(e){13!==e.keyCode||this.blocking||this._clickCreate()},_getName:function(){return a.escape(this.$(".collection-name").val())},_clickCreate:function(e){var t=this._getName();t?this.blocking||this.createList(t):this._validationWarning("name")},templates:{main:a.template(['
','
',''].join("")),header:a.template(['",'
','','',"
"].join("")),middle:a.template(['",'
',"
"].join("")),footer:a.template(['
','
','','
',c("Name"),":
","
","
",'
','
','",'
','",'","
","
",'
','","
","
"].join("")),helpContent:a.template(["

",c(["Collections of datasets are permanent, ordered lists of datasets that can be passed to tools and ","workflows in order to have analyses done on each member of the entire group. This interface allows ","you to create a collection and re-order the final collection."].join("")),"

","
    ","
  • ",c(["Rename elements in the list by clicking on ",'the existing name.'].join("")),"
  • ","
  • ",c(["Discard elements from the final created list by clicking on the ",'"Discard" button.'].join("")),"
  • ","
  • ",c(["Reorder the list by clicking and dragging elements. Select multiple elements by clicking on ",'them and you can then move those selected by dragging the ',"entire group. Deselect them by clicking them again or by clicking the ",'the "Clear selected" link.'].join("")),"
  • ","
  • ",c(['Click the "Start over" link to begin again as if you had just opened ',"the interface."].join("")),"
  • ","
  • ",c(['Click the "Cancel" button to exit the interface.'].join("")),"
  • ","

","

",c(['Once your collection is complete, enter a name and ','click "Create list".'].join("")),"

"].join("")),invalidElements:a.template([c("The following selections could not be included due to problems:"),"
    <% _.each( problems, function( problem ){ %>","
  • <%- problem.element.name %>: <%- problem.text %>
  • ","<% }); %>
"].join("")),noElementsLeft:a.template(['
  • ',c("No elements left! "),c("Would you like to "),'',c("start over"),"?","
  • "].join("")),invalidInitial:a.template(['
    ','
    ','',"<% if( _.size( problems ) ){ %>",c("The following selections could not be included due to problems"),":","
      <% _.each( problems, function( problem ){ %>","
    • <%- problem.element.name %>: <%- problem.text %>
    • ","<% }); %>
    ","<% } else if( _.size( elements ) < 1 ){ %>",c("No datasets were selected"),".","<% } %>","
    ",c("At least one element is needed for the collection"),". ",c("You may need to "),'',c("cancel")," ",c("and reselect new elements"),".","
    ","
    ","
    ",'"].join(""))},toString:function(){return"ListCollectionCreator"}}),g=function(e,t,i){var s,o=l.Deferred(),r=Galaxy.modal||new n.View;return t=a.defaults(t||{},{elements:e,oncancel:function(){r.hide(),o.reject("cancelled")},oncreate:function(e,t){r.hide(),o.resolve(t)}}),s=new i(t),r.show({title:t.title||c("Create a collection"),body:s.$el,width:"80%",height:"100%",closing_events:!0}),s.render(),window._collectionCreator=s,o},f=function(e,t){return t=t||{},t.title=c("Create a collection from a list of datasets"),g(e,t,p)};return{DatasetCollectionElementView:u,ListCollectionCreator:p,collectionCreatorModal:g,listCollectionCreatorModal:f,createListCollection:d}}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(3),i(2),i(1),i(1))},function(e,t,i){var n,s;(function(o,a,r,l){n=[i(42),i(12),i(22),i(6),i(5)],s=function(e,t,i,n,s){"use strict";var c="dataset",d=e.ListItemView,h=d.extend({_logNamespace:c,className:d.prototype.className+" dataset",id:function(){return["dataset",this.model.get("id")].join("-")},initialize:function(e){e.logger&&(this.logger=this.model.logger=e.logger), -this.log(this+".initialize:",e),d.prototype.initialize.call(this,e),this.linkTarget=e.linkTarget||"_blank"},_setUpListeners:function(){d.prototype._setUpListeners.call(this);var e=this;return e.listenTo(e.model,{change:function(t,i){e.model.changedAttributes().state&&e.model.inReadyState()&&e.expanded&&!e.model.hasDetails()?e.model.fetch({silent:!0}).done(function(){e.render()}):e.render()}})},_fetchModelDetails:function(){var e=this;return e.model.inReadyState()&&!e.model.hasDetails()?e.model.fetch({silent:!0}):o.when()},remove:function(e,t){var i=this;e=e||this.fxSpeed,this.$el.fadeOut(e,function(){a.View.prototype.remove.call(i),t&&t.call(i)})},_swapNewRender:function(e){return d.prototype._swapNewRender.call(this,e),this.model.has("state")&&this.$el.addClass("state-"+this.model.get("state")),this.$el},_renderPrimaryActions:function(){return[this._renderDisplayButton()]},_renderDisplayButton:function(){var e=this.model.get("state");if(e===t.NOT_VIEWABLE||e===t.DISCARDED||!this.model.get("accessible"))return null;var n={target:this.linkTarget,classes:"display-btn"};if(this.model.get("purged"))n.disabled=!0,n.title=s("Cannot display datasets removed from disk");else if(e===t.UPLOAD)n.disabled=!0,n.title=s("This dataset must finish uploading before it can be viewed");else if(e===t.NEW)n.disabled=!0,n.title=s("This dataset is not yet viewable");else{n.title=s("View data"),n.href=this.model.urls.display;var o=this;n.onclick=function(e){Galaxy.frame&&Galaxy.frame.active&&(Galaxy.frame.addDataset(o.model.get("id")),e.preventDefault())}}return n.faIcon="fa-eye",i(n)},_renderDetails:function(){if(this.model.get("state")===t.NOT_VIEWABLE)return r(this.templates.noAccess(this.model.toJSON(),this));var e=d.prototype._renderDetails.call(this);return e.find(".actions .left").empty().append(this._renderSecondaryActions()),e.find(".summary").html(this._renderSummary()).prepend(this._renderDetailMessages()),e.find(".display-applications").html(this._renderDisplayApplications()),this._setUpBehaviors(e),e},_renderSummary:function(){var e=this.model.toJSON(),t=this.templates.summaries[e.state];return(t=t||this.templates.summaries.unknown)(e,this)},_renderDetailMessages:function(){var e=this,t=r('
    '),i=e.model.toJSON();return l.each(e.templates.detailMessages,function(n){t.append(r(n(i,e)))}),t},_renderDisplayApplications:function(){return this.model.isDeletedOrPurged()?"":[this.templates.displayApplications(this.model.get("display_apps"),this),this.templates.displayApplications(this.model.get("display_types"),this)].join("")},_renderSecondaryActions:function(){switch(this.debug("_renderSecondaryActions"),this.model.get("state")){case t.NOT_VIEWABLE:return[];case t.OK:case t.FAILED_METADATA:case t.ERROR:return[this._renderDownloadButton(),this._renderShowParamsButton()]}return[this._renderShowParamsButton()]},_renderShowParamsButton:function(){return i({title:s("View details"),classes:"params-btn",href:this.model.urls.show_params,target:this.linkTarget,faIcon:"fa-info-circle",onclick:function(e){Galaxy.frame&&Galaxy.frame.active&&(Galaxy.frame.add({title:"Dataset details",url:this.href}),e.preventDefault(),e.stopPropagation())}})},_renderDownloadButton:function(){return this.model.get("purged")||!this.model.hasData()?null:l.isEmpty(this.model.get("meta_files"))?r(['','',""].join("")):this._renderMetaFileDownloadButton()},_renderMetaFileDownloadButton:function(){var e=this.model.urls;return r(['"].join("\n"))},events:l.extend(l.clone(d.prototype.events),{"click .display-btn":function(e){this.trigger("display",this,e)},"click .params-btn":function(e){this.trigger("params",this,e)},"click .download-btn":function(e){this.trigger("download",this,e)}}),toString:function(){var e=this.model?this.model+"":"(no model)";return"DatasetListItemView("+e+")"}});return h.prototype.templates=function(){var e=l.extend({},d.prototype.templates.warnings,{failed_metadata:n.wrapTemplate(['<% if( model.state === "failed_metadata" ){ %>','
    ',s("An error occurred setting the metadata for this dataset"),"
    ","<% } %>"]),error:n.wrapTemplate(["<% if( model.error ){ %>",'
    ',s("There was an error getting the data for this dataset"),": <%- model.error %>","
    ","<% } %>"]),purged:n.wrapTemplate(["<% if( model.purged ){ %>",'
    ',s("This dataset has been deleted and removed from disk"),"
    ","<% } %>"]),deleted:n.wrapTemplate(["<% if( model.deleted && !model.purged ){ %>",'
    ',s("This dataset has been deleted"),"
    ","<% } %>"])}),i=n.wrapTemplate(['
    ','
    ','
    ','
    ','
    ',"
    ","<% if( !dataset.deleted && !dataset.purged ){ %>",'
    ','
    ','
    ',"<% if( dataset.peek ){ %>",'
    <%= dataset.peek %>
    ',"<% } %>","<% } %>","
    "],"dataset"),o=n.wrapTemplate(['
    ','
    ',s("You do not have permission to view this dataset"),"
    ","
    "],"dataset"),a={};a[t.OK]=a[t.FAILED_METADATA]=n.wrapTemplate(["<% if( dataset.misc_blurb ){ %>",'
    ','<%- dataset.misc_blurb %>',"
    ","<% } %>","<% if( dataset.file_ext ){ %>",'
    ','",'<%- dataset.file_ext %>',"
    ","<% } %>","<% if( dataset.metadata_dbkey ){ %>",'
    ','",'',"<%- dataset.metadata_dbkey %>","","
    ","<% } %>","<% if( dataset.misc_info ){ %>",'
    ','<%- dataset.misc_info %>',"
    ","<% } %>"],"dataset"),a[t.NEW]=n.wrapTemplate(["
    ",s("This is a new dataset and not all of its data are available yet"),"
    "],"dataset"),a[t.NOT_VIEWABLE]=n.wrapTemplate(["
    ",s("You do not have permission to view this dataset"),"
    "],"dataset"),a[t.DISCARDED]=n.wrapTemplate(["
    ",s("The job creating this dataset was cancelled before completion"),"
    "],"dataset"),a[t.QUEUED]=n.wrapTemplate(["
    ",s("This job is waiting to run"),"
    "],"dataset"),a[t.RUNNING]=n.wrapTemplate(["
    ",s("This job is currently running"),"
    "],"dataset"),a[t.UPLOAD]=n.wrapTemplate(["
    ",s("This dataset is currently uploading"),"
    "],"dataset"),a[t.SETTING_METADATA]=n.wrapTemplate(["
    ",s("Metadata is being auto-detected"),"
    "],"dataset"),a[t.PAUSED]=n.wrapTemplate(["
    ",s('This job is paused. Use the "Resume Paused Jobs" in the history menu to resume'),"
    "],"dataset"),a[t.ERROR]=n.wrapTemplate(["<% if( !dataset.purged ){ %>","
    <%- dataset.misc_blurb %>
    ","<% } %>",'',s("An error occurred with this dataset"),":",'
    <%- dataset.misc_info %>
    '],"dataset"),a[t.EMPTY]=n.wrapTemplate(["
    ",s("No data"),": <%- dataset.misc_blurb %>
    "],"dataset"),a.unknown=n.wrapTemplate(['
    Error: unknown dataset state: "<%- dataset.state %>"
    '],"dataset");var r={resubmitted:n.wrapTemplate(["<% if( model.resubmitted ){ %>",'
    ',s("The job creating this dataset has been resubmitted"),"
    ","<% } %>"])},c=n.wrapTemplate(["<% _.each( apps, function( app ){ %>",'
    ','<%- app.label %> ','',"<% _.each( app.links, function( link ){ %>",'',"<% print( _l( link.text ) ); %>"," ","<% }); %>","","
    ","<% }); %>"],"apps");return l.extend({},d.prototype.templates,{warnings:e,details:i,noAccess:o,summaries:a,detailMessages:r,displayApplications:c})}(),{DatasetListItemView:h}}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(1),i(3),i(1),i(2))},function(e,t,i){var n,s;(function(o,a,r){n=[i(4)],s=function(e){var t=o.Model.extend({initialize:function(e){this.app=e},checksum:function(){var e="",t=this;return this.app.section.$el.find(".section-row").each(function(){var i=a(this).attr("id"),n=t.app.field_list[i];n&&(e+=i+":"+JSON.stringify(n.value&&n.value())+":"+n.collapsed+";")}),e},create:function(){function e(e,t,i){n.flat_dict[e]=t,o[e]=i,n.app.element_list[t]&&n.app.element_list[t].$el.attr("tour_id",e)}function t(s,o){for(var a in o){var r=o[a];if(r.input){var l=r.input,c=s;switch(""!=s&&(c+="|"),c+=l.name,l.type){case"repeat":var d="section-",h=[],u=null;for(var p in r){var g=p.indexOf(d);-1!=g&&(g+=d.length,h.push(parseInt(p.substr(g))),u||(u=p.substr(0,g)))}h.sort(function(e,t){return e-t});var a=0;for(var f in h)t(c+"_"+a++,r[u+h[f]]);break;case"conditional":var m=n.app.field_list[l.id].value();e(c+"|"+l.test_param.name,l.id,m);var v=i(l,m);-1!=v&&t(c,o[l.id+"-section-"+v]);break;case"section":t(!l.flat&&c||"",r);break;default:var _=n.app.field_list[l.id];if(_&&_.value){var m=_.value();if((void 0===l.ignore||l.ignore!=m)&&(_.collapsed&&l.collapsible_value&&(m=l.collapsible_value),e(c,l.id,m),l.payload))for(var y in l.payload)e(y,l.id,l.payload[y])}}}}}var n=this,s={};this._iterate(this.app.section.$el,s);var o={};return this.flat_dict={},t("",s),o},match:function(e){return this.flat_dict&&this.flat_dict[e]},matchCase:function(e,t){return i(e,t)},matchModel:function(e,t){var i=this;n(e.inputs,function(e,n){i.flat_dict[n]&&t(e,i.flat_dict[n])})},matchResponse:function(e){function t(e,s){if("string"==typeof s){var o=n.flat_dict[e];o&&(i[o]=s)}else for(var a in s){var r=a;if(""!==e){var l="|";s instanceof Array&&(l="_"),r=e+l+r}t(r,s[a])}}var i={},n=this;return t("",e),i},_iterate:function(e,t){var i=this,n=a(e).children();n.each(function(){var e=this,n=a(e).attr("id");if(a(e).hasClass("section-row")){var s=i.app.input_list[n];t[n]=s&&{input:s}||{},i._iterate(e,t[n])}else i._iterate(e,t)})}}),i=function(e,t){"boolean"==e.test_param.type&&(t="true"==t?e.test_param.truevalue||"true":e.test_param.falsevalue||"false");for(var i in e.cases)if(e.cases[i].value==t)return i;return-1},n=function(e,t,s,o){o=a.extend(!0,{},o),r.each(e,function(e){e&&e.type&&e.name&&(o[e.name]=e)});for(var l in e){var c=e[l];c.name=c.name||l;var d=s?s+"|"+c.name:c.name;switch(c.type){case"repeat":r.each(c.cache,function(e,i){n(e,t,d+"_"+i,o)});break;case"conditional":if(c.test_param){t(c.test_param,d+"|"+c.test_param.name,o);var h=i(c,c.test_param.value);-1!=h?n(c.cases[h].inputs,t,d,o):Galaxy.emit.debug("form-data::visitInputs() - Invalid case for "+d+".")}else Galaxy.emit.debug("form-data::visitInputs() - Conditional test parameter missing for "+d+".");break;case"section":n(c.inputs,t,d,o);break;default:t(c,d,o)}}};return{Manager:t,visitInputs:n}}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(3),i(1),i(2))},function(e,t,i){var n,s;(function(i,o,a){n=[],s=function(){return i.View.extend({initialize:function(e,t){this.app=e,this.app_options=e.options||{},this.field=t&&t.field||new i.View,this.model=t&&t.model||new i.Model({text_enable:this.app_options.text_enable||"Enable",text_disable:this.app_options.text_disable||"Disable",cls_enable:this.app_options.cls_enable||"fa fa-caret-square-o-down",cls_disable:this.app_options.cls_disable||"fa fa-caret-square-o-up"}).set(t),this.setElement(this._template()),this.$field=this.$(".ui-form-field"),this.$info=this.$(".ui-form-info"),this.$preview=this.$(".ui-form-preview"),this.$collapsible=this.$(".ui-form-collapsible"),this.$collapsible_text=this.$(".ui-form-collapsible-text"),this.$collapsible_icon=this.$(".ui-form-collapsible-icon"),this.$title=this.$(".ui-form-title"),this.$title_text=this.$(".ui-form-title-text"),this.$error_text=this.$(".ui-form-error-text"),this.$error=this.$(".ui-form-error"),this.$backdrop=this.$(".ui-form-backdrop"),this.$field.prepend(this.field.$el);var n=this.model.get("collapsible_value");this.field.collapsed=void 0!==n&&JSON.stringify(this.model.get("value"))==JSON.stringify(n),this.listenTo(this.model,"change",this.render,this),this.render();var s=this;this.$collapsible.on("click",function(){s.field.collapsed=!s.field.collapsed,e.trigger&&e.trigger("change"),s.render()})},backdrop:function(){this.model.set("backdrop",!0)},error:function(e){this.model.set("error_text",e)},reset:function(){this.model.set("error_text",null)},render:function(){o(".tooltip").hide();var e=this.model.get("help",""),t=this.model.get("argument");t&&-1==e.indexOf("("+t+")")&&(e+=" ("+t+")"),this.$info.html(e),this.$el[this.model.get("hidden")?"hide":"show"](),this.$preview[this.field.collapsed&&this.model.get("collapsible_preview")||this.model.get("disabled")?"show":"hide"]().html(a.escape(this.model.get("text_value")));var i=this.model.get("error_text");if(this.$error[i?"show":"hide"](),this.$el[i?"addClass":"removeClass"]("ui-error"),this.$error_text.html(i),this.$backdrop[this.model.get("backdrop")?"show":"hide"](),this.field.collapsed||this.model.get("disabled")?this.$field.hide():this.$field.show(),this.field.model&&this.field.model.set({color:this.model.get("color"),style:this.model.get("style")}),this.model.get("disabled")||void 0===this.model.get("collapsible_value"))this.$title_text.show().text(this.model.get("label")),this.$collapsible.hide();else{var n=this.field.collapsed?"enable":"disable";this.$title_text.hide(),this.$collapsible.show(),this.$collapsible_text.text(this.model.get("label")),this.$collapsible_icon.removeClass().addClass("icon").addClass(this.model.get("cls_"+n)).attr("data-original-title",this.model.get("text_"+n)).tooltip({placement:"bottom"})}},_template:function(){return o("
    ").addClass("ui-form-element").append(o("
    ").addClass("ui-form-error ui-error").append(o("").addClass("fa fa-arrow-down")).append(o("").addClass("ui-form-error-text"))).append(o("
    ").addClass("ui-form-title").append(o("
    ").addClass("ui-form-collapsible").append(o("").addClass("ui-form-collapsible-icon")).append(o("").addClass("ui-form-collapsible-text"))).append(o("").addClass("ui-form-title-text"))).append(o("
    ").addClass("ui-form-field").append(o("").addClass("ui-form-info")).append(o("
    ").addClass("ui-form-backdrop"))).append(o("
    ").addClass("ui-form-preview"))}})}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(3),i(1),i(2))},function(e,t,i){var n,s;(function(o,a,r){n=[i(4),i(7),i(48),i(50),i(49),i(46)],s=function(e,t,i,n,s,l){return o.Model.extend({types:{text:"_fieldText",select:"_fieldSelect",data_column:"_fieldSelect",genomebuild:"_fieldSelect",data:"_fieldData",data_collection:"_fieldData",integer:"_fieldSlider","float":"_fieldSlider","boolean":"_fieldBoolean",drill_down:"_fieldDrilldown",color:"_fieldColor",hidden:"_fieldHidden",hidden_data:"_fieldHidden",baseurl:"_fieldHidden",library_data:"_fieldLibrary",ftpfile:"_fieldFtp"},create:function(e){var t=this.types[e.type],i="function"==typeof this[t]?this[t].call(this,e):null;return i||(i=e.options?this._fieldSelect(e):this._fieldText(e),Galaxy.emit.debug("form-parameters::_addRow()","Auto matched field type ("+e.type+").")),void 0===e.value&&(e.value=null),i.value(e.value),i},_fieldData:function(e){return new i.View({id:"field-"+e.id,extensions:e.extensions,optional:e.optional,multiple:e.multiple,type:e.type,flavor:e.flavor,data:e.options,onchange:e.onchange})},_fieldSelect:function(e){if(e.is_workflow)return this._fieldText(e);"data_column"==e.type&&(e.error_text="Missing columns in referenced dataset.");var i=e.data;i||(i=[],a.each(e.options,function(e){i.push({label:e[0],value:e[1]})}));var n=t.Select;switch(e.display){case"checkboxes":n=t.Checkbox;break;case"radio":n=t.Radio;break;case"radiobutton":n=t.RadioButton}return new n.View({id:"field-"+e.id,data:i,error_text:e.error_text||"No options available",multiple:e.multiple,optional:e.optional,onchange:e.onchange,searchable:"workflow"!==e.flavor})},_fieldDrilldown:function(e){return e.is_workflow?this._fieldText(e):new t.Drilldown.View({id:"field-"+e.id,data:e.options,display:e.display,optional:e.optional,onchange:e.onchange})},_fieldText:function(i){if(i.options&&i.data)if(i.area=i.multiple,e.isEmpty(i.value))i.value=null;else if(r.isArray(i.value)){var n="";for(var s in i.value){if(n+=String(i.value[s]),!i.multiple)break;n+="\n"}i.value=n}return new t.Input({id:"field-"+i.id,area:i.area,placeholder:i.placeholder,onchange:i.onchange})},_fieldSlider:function(e){return new t.Slider.View({id:"field-"+e.id,precise:"float"==e.type,is_workflow:e.is_workflow,min:e.min,max:e.max,onchange:e.onchange})},_fieldHidden:function(e){return new t.Hidden({id:"field-"+e.id,info:e.info})},_fieldBoolean:function(e){return new t.RadioButton.View({id:"field-"+e.id,data:[{label:"Yes",value:"true"},{label:"No",value:"false"}],onchange:e.onchange})},_fieldColor:function(e){return new l({id:"field-"+e.id,onchange:e.onchange})},_fieldLibrary:function(e){return new n.View({id:"field-"+e.id,optional:e.optional,multiple:e.multiple,onchange:e.onchange})},_fieldFtp:function(e){return new s.View({id:"field-"+e.id,optional:e.optional,multiple:e.multiple,onchange:e.onchange})}})}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(3),i(2),i(1))},function(e,t,i){var n,s;(function(o,a,r){n=[i(4),i(9),i(7)],s=function(e,t,i){var n=o.View.extend({initialize:function(t){this.list={},this.options=e.merge(t,{title:"Repeat",empty_text:"Not available.",max:null,min:null}),this.button_new=new i.ButtonIcon({icon:"fa-plus",title:"Insert "+this.options.title,tooltip:"Add new "+this.options.title+" block",floating:"clear",cls:"ui-button-icon form-repeat-add",onclick:function(){t.onnew&&t.onnew()}}),this.setElement(a("
    ").append(this.$list=a("
    ")).append(a("
    ").append(this.button_new.$el)))},size:function(){return r.size(this.list)},add:function(e){if(!e.id||this.list[e.id])return void Galaxy.emit.debug("form-repeat::add()","Duplicate or invalid repeat block id.");var n=new i.ButtonIcon({icon:"fa-trash-o",tooltip:"Delete this repeat block",cls:"ui-button-icon-plain form-repeat-delete",onclick:function(){e.ondel&&e.ondel()}}),s=new t.View({id:e.id,title:"placeholder",cls:e.cls||"ui-portlet-repeat",operations:{button_delete:n}});s.append(e.$el),s.$el.addClass("section-row").hide(),this.list[e.id]=s,this.$list.append(s.$el.fadeIn("fast")),this.options.max>0&&this.size()>=this.options.max&&this.button_new.disable(),this._refresh()},del:function(e){return this.list[e]?(this.$list.find("#"+e).remove(),delete this.list[e],this.button_new.enable(),void this._refresh()):void Galaxy.emit.debug("form-repeat::del()","Invalid repeat block id.")},delAll:function(){for(var e in this.list)this.del(e)},hideOptions:function(){this.button_new.$el.hide(),r.each(this.list,function(e){e.hideOperation("button_delete")}),r.isEmpty(this.list)&&this.$el.append(a("
    ").addClass("ui-form-info").html(this.options.empty_text))},_refresh:function(){var e=0;for(var t in this.list){var i=this.list[t];i.title(++e+": "+this.options.title),i[this.size()>this.options.min?"showOperation":"hideOperation"]("button_delete")}}});return{View:n}}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(3),i(1),i(2))},function(e,t,i){var n,s;(function(o,a,r,l){n=[i(4),i(7),i(9),i(36),i(34),i(35)],s=function(e,t,i,n,s,c){var d=o.View.extend({initialize:function(e,t){this.app=e,this.inputs=t.inputs,this.parameters=new c,this.setElement(a("
    ")),this.render()},render:function(){var e=this;this.$el.empty(),r.each(this.inputs,function(t){e.add(t)})},add:function(t){var i=l.extend(!0,{},t);switch(i.id=t.id=e.uid(),this.app.input_list[i.id]=i,i.type){case"conditional":this._addConditional(i);break;case"repeat":this._addRepeat(i);break;case"section":this._addSection(i);break;default:this._addRow(i)}},_addConditional:function(e){var t=this;e.test_param.id=e.id,this.app.options.sustain_conditionals&&(e.test_param.disabled=!0);var i=this._addRow(e.test_param);i.model&&i.model.set("onchange",function(i){var n=t.app.data.matchCase(e,i);for(var s in e.cases){var o=e.cases[s],a=t.$("#"+e.id+"-section-"+s),r=!1;for(var l in o.inputs)if(!o.inputs[l].hidden){r=!0;break}s==n&&r?a.fadeIn("fast"):a.hide()}t.app.trigger("change")});for(var n in e.cases){var s=new d(this.app,{inputs:e.cases[n].inputs});this._append(s.$el.addClass("ui-form-section"),e.id+"-section-"+n)}i.trigger("change")},_addRepeat:function(e){function t(t){var n=e.id+"-section-"+o++,s=new d(i.app,{inputs:t});a.add({id:n,$el:s.$el,ondel:function(){a.del(n),i.app.trigger("change")}})}for(var i=this,o=0,a=new n.View({title:e.title||"Repeat",min:e.min,max:e.max,onnew:function(){t(e.inputs),i.app.trigger("change")}}),l=r.size(e.cache),c=0;cc?e.cache[c]:e.inputs);this.app.options.sustain_repeats&&a.hideOptions();var h=new s(this.app,{label:e.title||e.name,help:e.help,field:a});this._append(h.$el,e.id)},_addSection:function(e){var t=new i.View({title:e.title||e.name,cls:"ui-portlet-section",collapsible:!0,collapsible_button:!0,collapsed:!e.expanded});t.append(new d(this.app,{inputs:e.inputs}).$el),t.append(a("
    ").addClass("ui-form-info").html(e.help)),this.app.on("expand",function(e){t.$("#"+e).length>0&&t.expand()}),this._append(t.$el,e.id)},_addRow:function(e){var t=this,i=e.id;e.onchange=function(){t.app.trigger("change",i)};var n=this.parameters.create(e);this.app.field_list[i]=n;var o=new s(this.app,{name:e.name,label:e.label||e.name,value:e.value,text_value:e.text_value,collapsible_value:e.collapsible_value,collapsible_preview:e.collapsible_preview,help:e.help,argument:e.argument,disabled:e.disabled,color:e.color,style:e.style,backdrop:e.backdrop,hidden:e.hidden,field:n});return this.app.element_list[i]=o,this._append(o.$el,e.id),n},_append:function(e,t){this.$el.append(e.addClass("section-row").attr("id",t))}});return{View:d}}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(3),i(1),i(2),i(1))},function(e,t,i){var n,s;(function(o,a,r){n=[i(4),i(9),i(7),i(37),i(33)],s=function(e,t,i,n,s){return o.View.extend({initialize:function(t){this.options=e.merge(t,{initial_errors:!1,cls:"ui-portlet-limited",icon:null,always_refresh:!0}),this.setElement("
    "),this.render()},update:function(e){var t=this;this.data.matchModel(e,function(e,i){var n=t.input_list[i];if(n&&n.options&&!a.isEqual(n.options,e.options)){n.options=e.options;var s=t.field_list[i];if(s.update){var o=[];if(-1!=["data","data_collection","drill_down"].indexOf(n.type))o=n.options;else for(var r in e.options){var l=e.options[r];l.length>2&&o.push({label:l[0],value:l[1]})}s.update(o),s.trigger("change"),Galaxy.emit.debug("form-view::update()","Updating options for "+i)}}})},wait:function(e){for(var t in this.input_list){var i=this.field_list[t],n=this.input_list[t];n.is_dynamic&&i.wait&&i.unwait&&i[e?"wait":"unwait"]()}},highlight:function(e,t,i){var n=this.element_list[e];if(n&&(n.error(t||"Please verify this parameter."),this.portlet.expand(),this.trigger("expand",e),!i)){var s=this.$el.parents().filter(function(){return-1!=["auto","scroll"].indexOf(r(this).css("overflow"))}).first();s.animate({scrollTop:s.scrollTop()+n.$el.offset().top-120},500)}},errors:function(e){if(this.trigger("reset"),e&&e.errors){var t=this.data.matchResponse(e.errors);for(var i in this.element_list){this.element_list[i];t[i]&&this.highlight(i,t[i],!0)}}},render:function(){var e=this;this.off("change"),this.off("reset"),this.field_list={},this.input_list={},this.element_list={},this.data=new s.Manager(this),this._renderForm(),this.data.create(),this.options.initial_errors&&this.errors(this.options);var t=this.data.checksum();return this.on("change",function(i){var n=e.input_list[i];if(!n||n.refresh_on_change||e.options.always_refresh){var s=e.data.checksum();s!=t&&(t=s,e.options.onchange&&e.options.onchange())}}),this.on("reset",function(){a.each(e.element_list,function(e){e.reset()})}),this},_renderForm:function(){r(".tooltip").remove(),this.message=new i.Message,this.section=new n.View(this,{inputs:this.options.inputs}),this.portlet=new t.View({icon:this.options.icon,title:this.options.title,cls:this.options.cls,operations:this.options.operations,buttons:this.options.buttons,collapsible:this.options.collapsible,collapsed:this.options.collapsed}),this.portlet.append(this.message.$el),this.portlet.append(this.section.$el),this.$el.empty(),this.options.inputs&&this.$el.append(this.portlet.$el),this.options.message&&this.message.update({persistent:!0,status:"warning",message:this.options.message}),Galaxy.emit.debug("form-view::initialize()","Completed")}})}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(3),i(2),i(1))},function(e,t,i){var n,s;(function(o){n=[i(30),i(74),i(5)],s=function(e,t,i){"use strict";function n(e){return function(t,i){return this.isNew()&&(i=i||{},i.url=this.urlRoot+this.get("history_id")+"/contents",t=t||{},t.type="dataset_collection"),e.call(this,t,i)}}var s=t.HistoryContentMixin,a=e.ListDatasetCollection,r=e.PairDatasetCollection,l=e.ListPairedDatasetCollection,c=e.ListOfListsDatasetCollection,d=a.extend(s).extend({defaults:o.extend(o.clone(a.prototype.defaults),{history_content_type:"dataset_collection",collection_type:"list",model_class:"HistoryDatasetCollectionAssociation"}),save:n(a.prototype.save),toString:function(){return"History"+a.prototype.toString.call(this)}}),h=r.extend(s).extend({defaults:o.extend(o.clone(r.prototype.defaults),{history_content_type:"dataset_collection",collection_type:"paired",model_class:"HistoryDatasetCollectionAssociation"}),save:n(r.prototype.save),toString:function(){return"History"+r.prototype.toString.call(this)}}),u=l.extend(s).extend({defaults:o.extend(o.clone(l.prototype.defaults),{history_content_type:"dataset_collection",collection_type:"list:paired",model_class:"HistoryDatasetCollectionAssociation"}),save:n(l.prototype.save),toString:function(){return"History"+l.prototype.toString.call(this)}}),p=c.extend(s).extend({defaults:o.extend(o.clone(c.prototype.defaults),{history_content_type:"dataset_collection",collection_type:"list:list",model_class:"HistoryDatasetCollectionAssociation"}),save:n(c.prototype.save),toString:function(){return["HistoryListOfListsDatasetCollection(",this.get("name"),")"].join("")}});return{HistoryListDatasetCollection:d,HistoryPairDatasetCollection:h,HistoryListPairedDatasetCollection:u,HistoryListOfListsDatasetCollection:p}}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(2))},function(e,t,i){var n,s;(function(o,a,r){n=[i(67),i(72),i(39),i(41),i(6),i(129)],s=function(e,t,i,n,s,l){"use strict";var c=e.PaginatedCollection,d=c.extend(s.LoggableMixin).extend({_logNamespace:"history",model:function(e,n){if("dataset"===e.history_content_type)return new t.HistoryDatasetAssociation(e,n);if("dataset_collection"===e.history_content_type){switch(e.collection_type){case"list":return new i.HistoryListDatasetCollection(e,n);case"paired":return new i.HistoryPairDatasetCollection(e,n);case"list:paired":return new i.HistoryListPairedDatasetCollection(e,n);case"list:list":return new i.HistoryListOfListsDatasetCollection(e,n)}var s="Unknown collection_type: "+e.collection_type;return console.warn(s,e),{validationError:s}}return{validationError:"Unknown history_content_type: "+e.history_content_type}},limitPerPage:500,limitPerProgressiveFetch:500,order:"hid",urlRoot:Galaxy.root+"api/histories",url:function(){return this.urlRoot+"/"+this.historyId+"/contents"},initialize:function(e,t){t=t||{},c.prototype.initialize.call(this,e,t),this.history=t.history||null,this.setHistoryId(t.historyId||null),this.includeDeleted=t.includeDeleted||this.includeDeleted,this.includeHidden=t.includeHidden||this.includeHidden,this.model.prototype.idAttribute="type_id"},setHistoryId:function(e){this.historyId=e,this._setUpWebStorage()},_setUpWebStorage:function(e){return this.historyId?(this.storage=new n.HistoryPrefs({id:n.HistoryPrefs.historyStorageKey(this.historyId)}),this.trigger("new-storage",this.storage,this),this.on({"include-deleted":function(e){this.storage.includeDeleted(e)},"include-hidden":function(e){this.storage.includeHidden(e)}}),this.includeDeleted=this.storage.includeDeleted()||!1,this.includeHidden=this.storage.includeHidden()||!1,this):void 0},comparators:o.extend(o.clone(c.prototype.comparators),{name:s.buildComparator("name",{ascending:!0}),"name-dsc":s.buildComparator("name",{ascending:!1}),hid:s.buildComparator("hid",{ascending:!1}),"hid-asc":s.buildComparator("hid",{ascending:!0})}),running:function(){return this.filter(function(e){return!e.inReadyState()})},runningAndActive:function(){return this.filter(function(e){return!e.inReadyState()&&e.get("visible")&&!e.get("deleted")})},getByHid:function(e){return this.findWhere({hid:e})},haveDetails:function(){return this.all(function(e){return e.hasDetails()})},hidden:function(){return this.filter(function(e){return e.hidden()})},deleted:function(){return this.filter(function(e){return e.get("deleted")})},visibleAndUndeleted:function(){return this.filter(function(e){return e.get("visible")&&!e.get("deleted")})},setIncludeDeleted:function(e,t){if(o.isBoolean(e)&&e!==this.includeDeleted){if(this.includeDeleted=e,o.result(t,"silent"))return;this.trigger("include-deleted",e,this)}},setIncludeHidden:function(e,t){if(o.isBoolean(e)&&e!==this.includeHidden){if(this.includeHidden=e,t=t||{},o.result(t,"silent"))return;this.trigger("include-hidden",e,this)}},fetch:function(e){if(e=e||{},this.historyId&&!e.details){var t=n.HistoryPrefs.get(this.historyId).toJSON();o.isEmpty(t.expandedIds)||(e.details=o.values(t.expandedIds).join(","))}return c.prototype.fetch.call(this,e)},_buildFetchData:function(e){return o.extend(c.prototype._buildFetchData.call(this,e),{v:"dev"})},_fetchParams:c.prototype._fetchParams.concat(["v","details"]),_buildFetchFilters:function(e){var t=c.prototype._buildFetchFilters.call(this,e)||{},i={};return this.includeDeleted||(i.deleted=!1,i.purged=!1),this.includeHidden||(i.visible=!0),o.defaults(t,i)},getTotalItemCount:function(){return this.history.contentsShown()},fetchUpdated:function(e,t){return e&&(t=t||{filters:{}},t.remove=!1,t.filters={"update_time-ge":e.toISOString(),visible:""}),this.fetch(t)},fetchDeleted:function(e){e=e||{};var t=this;return e.filters=o.extend(e.filters,{deleted:!0,purged:void 0}),e.remove=!1,t.trigger("fetching-deleted",t),t.fetch(e).always(function(){t.trigger("fetching-deleted-done",t)})},fetchHidden:function(e){e=e||{};var t=this;return e.filters=o.extend(e.filters,{visible:!1}),e.remove=!1,t.trigger("fetching-hidden",t),t.fetch(e).always(function(){t.trigger("fetching-hidden-done",t)})},fetchAllDetails:function(e){e=e||{};var t={details:"all"};return e.data=o.extend(e.data||{},t),this.fetch(e)},fetchCollectionCounts:function(e){return e=e||{},e.keys=["type_id","element_count"].join(","),e.filters=o.extend(e.filters||{},{history_content_type:"dataset_collection"}),e.remove=!1,this.fetch(e)},_filterAndUpdate:function(e,t){var i=this,n=i.model.prototype.idAttribute,s=[t];return i.fetch({filters:e,remove:!1}).then(function(e){return e=e.reduce(function(e,t,s){var o=i.get(t[n]);return o?e.concat(o):e},[]),i.ajaxQueue("save",s,e)})},ajaxQueue:function(e,t,i){ -return i=i||this.models,new l.AjaxQueue(i.slice().reverse().map(function(i,n){var s=o.isString(e)?i[e]:e;return function(){return s.apply(i,t)}})).deferred},progressivelyFetchDetails:function(e){function i(t){t=t||0;var a=o.extend(o.clone(e),{view:"summary",keys:c,limit:r,offset:t,reset:0===t,remove:!1});o.defer(function(){s.fetch.call(s,a).fail(n.reject).done(function(e){n.notify(e,r,t),e.length!==r?(s.allFetched=!0,n.resolve(e,r,t)):i(t+r)})})}e=e||{};var n=a.Deferred(),s=this,r=e.limitPerCall||s.limitPerProgressiveFetch,l=t.HistoryDatasetAssociation.prototype.searchAttributes,c=l.join(",");return i(),n},isCopyable:function(e){var t=["HistoryDatasetAssociation","HistoryDatasetCollectionAssociation"];return o.isObject(e)&&e.id&&o.contains(t,e.model_class)},copy:function(e){var t,i,n;o.isString(e)?(t=e,n="hda",i="dataset"):(t=e.id,n={HistoryDatasetAssociation:"hda",LibraryDatasetDatasetAssociation:"ldda",HistoryDatasetCollectionAssociation:"hdca"}[e.model_class]||"hda",i="hdca"===n?"dataset_collection":"dataset");var s=this,r=a.ajax(this.url(),{method:"POST",contentType:"application/json",data:JSON.stringify({content:t,source:n,type:i})}).done(function(e){s.add([e],{parse:!0})}).fail(function(e,o,a){s.trigger("error",s,r,{},"Error copying contents",{type:i,id:t,source:n})});return r},createHDCA:function(e,t,i,n){var s=this.model({history_content_type:"dataset_collection",collection_type:t,history_id:this.historyId,name:i,element_identifiers:e});return s.save(n)},haveSearchDetails:function(){return this.allFetched&&this.all(function(e){return o.has(e.attributes,"annotation")})},matches:function(e){return this.filter(function(t){return t.matches(e)})},clone:function(){var e=r.Collection.prototype.clone.call(this);return e.historyId=this.historyId,e},toString:function(){return["HistoryContents(",[this.historyId,this.length].join(),")"].join("")}});return{HistoryContents:d}}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(2),i(1),i(3))},function(e,t,i){var n,s;(function(o){n=[i(6)],s=function(e){"use strict";var t=e.SessionStorageModel.extend({defaults:{expandedIds:{},show_deleted:!1,show_hidden:!1},addExpanded:function(e){var t=this.get("expandedIds");t[e.id]=e.get("id"),this.save("expandedIds",t)},removeExpanded:function(e){var t=this.get("expandedIds");delete t[e.id],this.save("expandedIds",t)},isExpanded:function(e){return o.result(this.get("expandedIds"),e,!1)},allExpanded:function(){return o.values(this.get("expandedIds"))},clearExpanded:function(){this.set("expandedIds",{})},includeDeleted:function(e){return o.isUndefined(e)||this.set("show_deleted",e),this.get("show_deleted")},includeHidden:function(e){return o.isUndefined(e)||this.set("show_hidden",e),this.get("show_hidden")},toString:function(){return"HistoryPrefs("+this.id+")"}},{storageKeyPrefix:"history:",historyStorageKey:function(e){if(!e)throw new Error("HistoryPrefs.historyStorageKey needs valid id: "+e);return t.storageKeyPrefix+e},get:function(e){return new t({id:t.historyStorageKey(e)})},clearAll:function(e){for(var i in sessionStorage)0===i.indexOf(t.storageKeyPrefix)&&sessionStorage.removeItem(i)}});return{HistoryPrefs:t}}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(2))},function(e,t,i){var n,s;(function(o,a,r,l){n=[i(6),i(5)],s=function(e,t){"use strict";var i="list",n=o.View.extend(e.LoggableMixin).extend({_logNamespace:i,initialize:function(e){this.expanded=e.expanded||!1,this.log(" expanded:",this.expanded),this.fxSpeed=void 0!==e.fxSpeed?e.fxSpeed:this.fxSpeed},fxSpeed:"fast",render:function(e){var t=this._buildNewRender();return this._setUpBehaviors(t),this._queueNewRender(t,e),this},_buildNewRender:function(){var e=a(this.templates.el(this.model.toJSON(),this));return this.expanded&&this.$details(e).replaceWith(this._renderDetails().show()),e},_queueNewRender:function(e,t){t=void 0===t?this.fxSpeed:t;var i=this;0===t?(i._swapNewRender(e),i.trigger("rendered",i)):a(i).queue("fx",[function(e){i.$el.fadeOut(t,e)},function(t){i._swapNewRender(e),t()},function(e){i.$el.fadeIn(t,e)},function(e){i.trigger("rendered",i),e()}])},_swapNewRender:function(e){return this.$el.empty().attr("class",r.isFunction(this.className)?this.className():this.className).append(e.children())},_setUpBehaviors:function(e){e=e||this.$el,e.find("[title]").tooltip({placement:"bottom"})},$details:function(e){return e=e||this.$el,e.find("> .details")},_renderDetails:function(){var e=a(this.templates.details(this.model.toJSON(),this));return this._setUpBehaviors(e),e},toggleExpanded:function(e){return e=void 0===e?!this.expanded:e,e?this.expand():this.collapse(),this},expand:function(){var e=this;return e._fetchModelDetails().always(function(){e._expand()})},_fetchModelDetails:function(){return this.model.hasDetails()?l.when():this.model.fetch()},_expand:function(){var e=this,t=e._renderDetails();e.$details().replaceWith(t),e.expanded=!0,e.$details().slideDown(e.fxSpeed,function(){e.trigger("expanded",e)})},collapse:function(){this.debug(this+"(ExpandableView).collapse");var e=this;e.expanded=!1,this.$details().slideUp(e.fxSpeed,function(){e.trigger("collapsed",e)})}}),s=n.extend(e.mixin(e.SelectableViewMixin,e.DraggableViewMixin,{tagName:"div",className:"list-item",initialize:function(t){n.prototype.initialize.call(this,t),e.SelectableViewMixin.initialize.call(this,t),e.DraggableViewMixin.initialize.call(this,t),this._setUpListeners()},_setUpListeners:function(){return this.on("selectable",function(e){e?this.$(".primary-actions").hide():this.$(".primary-actions").show()},this),this},_buildNewRender:function(){var e=n.prototype._buildNewRender.call(this);return e.children(".warnings").replaceWith(this._renderWarnings()),e.children(".title-bar").replaceWith(this._renderTitleBar()),e.children(".primary-actions").append(this._renderPrimaryActions()),e.find("> .title-bar .subtitle").replaceWith(this._renderSubtitle()),e},_swapNewRender:function(e){return n.prototype._swapNewRender.call(this,e),this.selectable&&this.showSelector(0),this.draggable&&this.draggableOn(),this.$el},_renderWarnings:function(){var e=this,t=a('
    '),i=e.model.toJSON();return r.each(e.templates.warnings,function(n){t.append(a(n(i,e)))}),t},_renderTitleBar:function(){return a(this.templates.titleBar(this.model.toJSON(),this))},_renderPrimaryActions:function(){return[]},_renderSubtitle:function(){return a(this.templates.subtitle(this.model.toJSON(),this))},events:{"click .title-bar":"_clickTitleBar","keydown .title-bar":"_keyDownTitleBar","click .selector":"toggleSelect"},_clickTitleBar:function(e){e.stopPropagation(),e.altKey?(this.toggleSelect(e),this.selectable||this.showSelector()):this.toggleExpanded()},_keyDownTitleBar:function(e){var t=32,i=13;return!e||"keydown"!==e.type||e.keyCode!==t&&e.keyCode!==i?!0:(this.toggleExpanded(),e.stopPropagation(),!1)},toString:function(){var e=this.model?this.model+"":"(no model)";return"ListItemView("+e+")"}}));s.prototype.templates=function(){var t=e.wrapTemplate(['
    ','
    ','
    ','',"
    ",'
    ','
    ','
    ',"
    "]),i={},n=e.wrapTemplate(['
    ','','
    ','<%- element.name %>',"
    ",'
    ',"
    "],"element"),s=e.wrapTemplate(['
    ']),o=e.wrapTemplate(['
    ']);return{el:t,warnings:i,titleBar:n,subtitle:s,details:o}}();var c=s.extend({foldoutStyle:"foldout",foldoutPanelClass:null,initialize:function(e){"drilldown"===this.foldoutStyle&&(this.expanded=!1),this.foldoutStyle=e.foldoutStyle||this.foldoutStyle,this.foldoutPanelClass=e.foldoutPanelClass||this.foldoutPanelClass,s.prototype.initialize.call(this,e),this.foldout=this._createFoldoutPanel()},_renderDetails:function(){if("drilldown"===this.foldoutStyle)return a();var e=s.prototype._renderDetails.call(this);return this._attachFoldout(this.foldout,e)},_createFoldoutPanel:function(){var e=this.model,t=this._getFoldoutPanelClass(e),i=this._getFoldoutPanelOptions(e),n=new t(r.extend(i,{model:e}));return n},_getFoldoutPanelClass:function(){return this.foldoutPanelClass},_getFoldoutPanelOptions:function(){return{foldoutStyle:this.foldoutStyle,fxSpeed:this.fxSpeed}},_attachFoldout:function(e,t){return t=t||this.$("> .details"),this.foldout=e.render(0),e.$("> .controls").hide(),t.append(e.$el)},expand:function(){var e=this;return e._fetchModelDetails().always(function(){"foldout"===e.foldoutStyle?e._expand():"drilldown"===e.foldoutStyle&&e._expandByDrilldown()})},_expandByDrilldown:function(){var e=this;e.listenTo(e.foldout,"close",function(){e.trigger("collapsed:drilldown",e,e.foldout)}),e.trigger("expanded:drilldown",e,e.foldout)}});return c.prototype.templates=function(){var t=e.wrapTemplate(['
    ',"
    "],"collection");return r.extend({},s.prototype.templates,{details:t})}(),{ExpandableView:n,ListItemView:s,FoldoutListItemView:c}}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(3),i(1),i(2),i(1))},function(e,t,i){var n,s;(function(o,a){n=[i(4),i(55),i(7),i(38),i(17),i(28)],s=function(e,t,i,n,s,r){return n.extend({initialize:function(e){var i=this;n.prototype.initialize.call(this,e),this.deferred=new t,e.inputs?this._buildForm(e):this.deferred.execute(function(t){i._buildModel(t,e,!0)}),e.listen_to_history&&parent.Galaxy&&parent.Galaxy.currHistoryPanel&&this.listenTo(parent.Galaxy.currHistoryPanel.collection,"change",function(){this.refresh()}),this.$el.on("remove",function(){i.remove()})},refresh:function(){var e=this;e.deferred.reset(),this.deferred.execute(function(t){e._updateModel(t)})},remove:function(){var e=this;this.$el.hide(),this.deferred.execute(function(){n.prototype.remove.call(e),Galaxy.emit.debug("tool-form-base::remove()","Destroy view.")})},_buildForm:function(t){var i=this;this.options=e.merge(t,this.options),this.options=e.merge({icon:t.icon,title:""+t.name+" "+t.description+" (Galaxy Version "+t.version+")",operations:!this.options.hide_operations&&this._operations(),onchange:function(){i.refresh()}},this.options),this.options.customize&&this.options.customize(this.options),this.render(),this.options.collapsible||this.$el.append(o("
    ").addClass("ui-margin-top-large").append(this._footer()))},_buildModel:function(t,n,s){var a=this;this.options.id=n.id,this.options.version=n.version;var r="",l={};n.job_id?r=Galaxy.root+"api/jobs/"+n.job_id+"/build_for_rerun":(r=Galaxy.root+"api/tools/"+n.id+"/build",Galaxy.params&&Galaxy.params.tool_id==n.id&&(l=o.extend({},Galaxy.params),n.version&&(l.tool_version=n.version))),e.get({url:r,data:l,success:function(e){return e=e.tool_model||e,e.display?(a._buildForm(e),!s&&a.message.update({status:"success",message:"Now you are using '"+a.options.name+"' version "+a.options.version+", id '"+a.options.id+"'.",persistent:!1}),Galaxy.emit.debug("tool-form-base::initialize()","Initial tool model ready.",e),void t.resolve()):void(window.location=Galaxy.root)},error:function(e,n){var s=e&&e.err_msg||"Uncaught error.";401==n?window.location=Galaxy.root+"user/login?"+o.param({redirect:Galaxy.root+"?tool_id="+a.options.id}):a.$el.is(":empty")?a.$el.prepend(new i.Message({message:s,status:"danger",persistent:!0,large:!0}).$el):Galaxy.modal&&Galaxy.modal.show({title:"Tool request failed",body:s,buttons:{Close:function(){Galaxy.modal.hide()}}}),Galaxy.emit.debug("tool-form::initialize()","Initial tool model request failed.",e),t.reject()}})},_updateModel:function(t){var i=this,n=this.options.update_url||Galaxy.root+"api/tools/"+this.options.id+"/build",s={tool_id:this.options.id,tool_version:this.options.version,inputs:o.extend(!0,{},i.data.create())};this.wait(!0),Galaxy.emit.debug("tool-form-base::_updateModel()","Sending current state.",s),e.request({type:"POST",url:n,data:s,success:function(e){i.update(e.tool_model||e),i.options.update&&i.options.update(e),i.wait(!1),Galaxy.emit.debug("tool-form-base::_updateModel()","Received new model.",e),t.resolve()},error:function(e){Galaxy.emit.debug("tool-form-base::_updateModel()","Refresh request failed.",e),t.reject()}})},_operations:function(){var e=this,t=this.options,n=new i.ButtonMenu({icon:"fa-cubes",title:!t.narrow&&"Versions"||null,tooltip:"Select another tool version"});if(!t.sustain_version&&t.versions&&t.versions.length>1)for(var s in t.versions){var o=t.versions[s];o!=t.version&&n.addMenu({title:"Switch to "+o,version:o,icon:"fa-cube",onclick:function(){var i=t.id.replace(t.version,this.version),n=this.version;e.deferred.reset(),e.deferred.execute(function(t){e._buildModel(t,{id:i,version:n})})}})}else n.$el.hide();var a=new i.ButtonMenu({icon:"fa-caret-down",title:!t.narrow&&"Options"||null,tooltip:"View available options"});return t.biostar_url&&(a.addMenu({icon:"fa-question-circle",title:"Question?",tooltip:"Ask a question about this tool (Biostar)",onclick:function(){window.open(t.biostar_url+"/p/new/post/")}}),a.addMenu({icon:"fa-search",title:"Search",tooltip:"Search help for this tool (Biostar)",onclick:function(){window.open(t.biostar_url+"/local/search/page/?q="+t.name)}})),a.addMenu({icon:"fa-share",title:"Share",tooltip:"Share this tool",onclick:function(){prompt("Copy to clipboard: Ctrl+C, Enter",window.location.origin+Galaxy.root+"root?tool_id="+t.id)}}),Galaxy.user&&Galaxy.user.get("is_admin")&&a.addMenu({icon:"fa-download",title:"Download",tooltip:"Download this tool",onclick:function(){window.location.href=Galaxy.root+"api/tools/"+t.id+"/download"}}),t.requirements&&t.requirements.length>0&&a.addMenu({icon:"fa-info-circle",title:"Requirements",tooltip:"Display tool requirements",onclick:function(){!this.visible||e.portlet.collapsed?(this.visible=!0,e.portlet.expand(),e.message.update({persistent:!0,message:e._templateRequirements(t),status:"info"})):(this.visible=!1,e.message.update({message:""}))}}),t.sharable_url&&a.addMenu({icon:"fa-external-link",title:"See in Tool Shed",tooltip:"Access the repository",onclick:function(){window.open(t.sharable_url)}}),{menu:a,versions:n}},_footer:function(){var e=this.options,t=o("
    ").append(this._templateHelp(e));if(e.citations){var i=o("
    "),n=new s.ToolCitationCollection;n.tool_id=e.id;var a=new r.CitationListView({el:i,collection:n});a.render(),n.fetch(),t.append(i)}return t},_templateHelp:function(e){var t=o("
    ").addClass("ui-form-help").append(e.help);return t.find("a").attr("target","_blank"),t},_templateRequirements:function(e){var t=e.requirements.length;if(t>0){var i="This tool requires ";a.each(e.requirements,function(e,n){i+=e.name+(e.version?" (Version "+e.version+")":"")+(t-2>n?", ":n==t-2?" and ":"")});var n=o("").attr("target","_blank").attr("href","https://wiki.galaxyproject.org/Tools/Requirements").text("here");return o("").append(i+". Click ").append(n).append(" for more information.")}return"No requirements found."}})}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(1),i(2))},function(e,t,i){var n,s;(function(o,a){n=[i(2),i(16),i(11),i(18)],s=function(e,t,i,n){"use strict";var s={hidden:!1,show:function(){this.set("hidden",!1)},hide:function(){this.set("hidden",!0)},toggle:function(){this.set("hidden",!this.get("hidden"))},is_visible:function(){return!this.attributes.hidden}},r=o.Model.extend({defaults:{name:null,label:null,type:null,value:null,html:null,num_samples:5},initialize:function(e){this.attributes.html=unescape(this.attributes.html)},copy:function(){return new r(this.toJSON())},set_value:function(e){this.set("value",e||"")}}),l=o.Collection.extend({model:r}),c=r.extend({}),d=r.extend({set_value:function(e){this.set("value",parseInt(e,10))},get_samples:function(){return d3.scale.linear().domain([this.get("min"),this.get("max")]).ticks(this.get("num_samples"))}}),h=d.extend({set_value:function(e){this.set("value",parseFloat(e))}}),u=r.extend({get_samples:function(){return e.map(this.get("options"),function(e){return e[0]})}});r.subModelTypes={integer:d,"float":h,data:c,select:u};var p=o.Model.extend({defaults:{id:null,name:null,description:null,target:null,inputs:[],outputs:[]},urlRoot:Galaxy.root+"api/tools",initialize:function(t){this.set("inputs",new l(e.map(t.inputs,function(e){var t=r.subModelTypes[e.type]||r;return new t(e)})))},toJSON:function(){var e=o.Model.prototype.toJSON.call(this);return e.inputs=this.get("inputs").map(function(e){return e.toJSON()}),e},remove_inputs:function(e){var t=this,i=t.get("inputs").filter(function(t){return-1!==e.indexOf(t.get("type"))});t.get("inputs").remove(i)},copy:function(e){var t=new p(this.toJSON());if(e){var i=new o.Collection;t.get("inputs").each(function(e){e.get_samples()&&i.push(e)}),t.set("inputs",i)}return t},apply_search_results:function(t){return-1!==e.indexOf(t,this.attributes.id)?this.show():this.hide(),this.is_visible()},set_input_value:function(e,t){this.get("inputs").find(function(t){return t.get("name")===e}).set("value",t)},set_input_values:function(t){var i=this;e.each(e.keys(t),function(e){i.set_input_value(e,t[e])})},run:function(){return this._run()},rerun:function(e,t){return this._run({action:"rerun",target_dataset_id:e.id,regions:t})},get_inputs_dict:function(){var e={};return this.get("inputs").each(function(t){e[t.get("name")]=t.get("value")}),e},_run:function(n){var s=e.extend({tool_id:this.id,inputs:this.get_inputs_dict()},n),o=a.Deferred(),r=new t.ServerStateDeferred({ajax_settings:{url:this.urlRoot,data:JSON.stringify(s),dataType:"json",contentType:"application/json",type:"POST"},interval:2e3,success_fn:function(e){return"pending"!==e}});return a.when(r.go()).then(function(e){o.resolve(new i.DatasetCollection(e))}),o}});e.extend(p.prototype,s);var g=(o.View.extend({}),o.Collection.extend({model:p})),f=o.Model.extend(s),m=o.Model.extend({defaults:{elems:[],open:!1},clear_search_results:function(){e.each(this.attributes.elems,function(e){e.show()}),this.show(),this.set("open",!1)},apply_search_results:function(t){var i,n=!0;e.each(this.attributes.elems,function(e){e instanceof f?(i=e,i.hide()):e instanceof p&&e.apply_search_results(t)&&(n=!1,i&&i.show())}),n?this.hide():(this.show(),this.set("open",!0))}});e.extend(m.prototype,s);var v=o.Model.extend({defaults:{search_hint_string:"search tools",min_chars_for_search:3,clear_btn_url:"",search_url:"",visible:!0,query:"",results:null,clear_key:27},urlRoot:Galaxy.root+"api/tools",initialize:function(){this.on("change:query",this.do_search)},do_search:function(){var e=this.attributes.query;if(e.length");e.append(S.tool_link(this.model.toJSON()));var t=this.model.get("form_style",null);if("upload1"===this.model.id)e.find("a").on("click",function(e){e.preventDefault(),Galaxy.upload.show()});else if("regular"===t){var i=this;e.find("a").on("click",function(e){e.preventDefault();var t=new n.View({id:i.model.id,version:i.model.get("version")});t.deferred.execute(function(){Galaxy.app.display(t)})})}return this.$el.append(e),this}}),b=y.extend({tagName:"div",className:"toolPanelLabel",render:function(){return this.$el.append(a("").text(this.model.attributes.text)),this}}),x=y.extend({tagName:"div",className:"toolSectionWrapper",initialize:function(){y.prototype.initialize.call(this),this.model.on("change:open",this.update_open,this)},render:function(){this.$el.append(S.panel_section(this.model.toJSON()));var t=this.$el.find(".toolSectionBody");return e.each(this.model.attributes.elems,function(e){if(e instanceof p){var i=new w({model:e,className:"toolTitle"});i.render(),t.append(i.$el)}else if(e instanceof f){var n=new b({model:e});n.render(),t.append(n.$el)}}),this},events:{"click .toolSectionTitle > a":"toggle"},toggle:function(){this.model.set("open",!this.model.attributes.open)},update_open:function(){this.model.attributes.open?this.$el.children(".toolSectionBody").slideDown("fast"):this.$el.children(".toolSectionBody").slideUp("fast")}}),C=o.View.extend({tagName:"div",id:"tool-search",className:"bar",events:{click:"focus_and_select","keyup :input":"query_changed","click #search-clear-btn":"clear"},render:function(){return this.$el.append(S.tool_search(this.model.toJSON())),this.model.is_visible()||this.$el.hide(),this.$el.find("[title]").tooltip(),this},focus_and_select:function(){this.$el.find(":input").focus().select()},clear:function(){return this.model.clear_search(),this.$el.find(":input").val(""),this.focus_and_select(),!1},query_changed:function(e){return this.model.attributes.clear_key&&this.model.attributes.clear_key===e.which?(this.clear(),!1):void this.model.set("query",this.$el.find(":input").val())}}),$=o.View.extend({tagName:"div",className:"toolMenu",initialize:function(){this.model.get("tool_search").on("change:results",this.handle_search_results,this)},render:function(){var e=this,t=new C({model:this.model.get("tool_search")});return t.render(),e.$el.append(t.$el),this.model.get("layout").each(function(t){if(t instanceof m){var i=new x({model:t});i.render(),e.$el.append(i.$el)}else if(t instanceof p){var n=new w({model:t,className:"toolTitleNoSection"});n.render(),e.$el.append(n.$el)}else if(t instanceof f){var s=new b({model:t});s.render(),e.$el.append(s.$el)}}),e.$el.find("a.tool-link").click(function(t){var i=a(this).attr("class").split(/\s+/)[0],n=e.model.get("tools").get(i);e.trigger("tool_link_click",t,n)}),this},handle_search_results:function(){var e=this.model.get("tool_search").get("results");e&&0===e.length?a("#search-no-results").show():a("#search-no-results").hide()}}),E=o.View.extend({className:"toolForm",render:function(){this.$el.children().remove(),this.$el.append(S.tool_form(this.model.toJSON()))}}),S=(o.View.extend({className:"toolMenuAndView",initialize:function(){this.tool_panel_view=new $({collection:this.collection}),this.tool_form_view=new E},render:function(){this.tool_panel_view.render(),this.tool_panel_view.$el.css("float","left"),this.$el.append(this.tool_panel_view.$el),this.tool_form_view.$el.hide(),this.$el.append(this.tool_form_view.$el);var e=this;this.tool_panel_view.on("tool_link_click",function(t,i){t.preventDefault(),e.show_tool(i)})},show_tool:function(e){var t=this;e.fetch().done(function(){t.tool_form_view.model=e,t.tool_form_view.render(),t.tool_form_view.$el.show(),a("#left").width("650px")})}}),{tool_search:e.template(['',' ',''].join("")),panel_section:e.template(['",'
    '+e.content+"
    "}});return{View:t}}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(3),i(1))},function(e,t,i){var n,s;(function(i){n=[],s=function(){var e=i.Model.extend({defaults:{extension:"auto",genome:"?",url_paste:"",status:"init",info:null,file_name:"",file_mode:"",file_size:0,file_type:null,file_path:"",file_data:null,percentage:0,space_to_tab:!1,to_posix_lines:!0,enabled:!0},reset:function(e){this.clear().set(this.defaults).set(e)}}),t=i.Collection.extend({model:e});return{Model:e,Collection:t}}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(3))},,function(e,t,i){var n,s;(function(o,a){n=[i(4)],s=function(e){return o.Model.extend({initialize:function(){this.active={},this.last=null},execute:function(t){var i=this,n=e.uid(),s=t.length>0;this.active[n]=!0;var o=a.Deferred();o.promise().always(function(){delete i.active[n],s&&Galaxy.emit.debug("deferred::execute()",this.state().charAt(0).toUpperCase()+this.state().slice(1)+" "+n)}),a.when(this.last).always(function(){i.active[n]?(s&&Galaxy.emit.debug("deferred::execute()","Running "+n),t(o),!s&&o.resolve()):o.reject()}),this.last=o.promise()},reset:function(){Galaxy.emit.debug("deferred::execute()","Reset");for(var e in this.active)this.active[e]=!1},ready:function(){return a.isEmptyObject(this.active)}})}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(3),i(1))},,,,,,,,,,,function(e,t,i){var n,s;(function(o,a){n=[i(6),i(5),i(15)],s=function(e,t){var i=o.View.extend(e.LoggableMixin).extend(e.HiddenUntilActivatedViewMixin).extend({tagName:"div",className:"annotation-display",initialize:function(e){e=e||{},this.tooltipConfig=e.tooltipConfig||{placement:"bottom"},this.listenTo(this.model,"change:annotation",function(){this.render()}),this.hiddenUntilActivated(e.$activator,e)},render:function(){var e=this;return this.$el.html(this._template()),this.$annotation().make_text_editable({use_textarea:!0,on_finish:function(t){e.$annotation().text(t),e.model.save({annotation:t},{silent:!0}).fail(function(){e.$annotation().text(e.model.previous("annotation"))})}}),this},_template:function(){var e=this.model.get("annotation");return['",'
    ',a.escape(e),"
    "].join("")},$annotation:function(){return this.$el.find(".annotation")},remove:function(){this.$annotation.off(),this.stopListening(this.model),o.View.prototype.remove.call(this)},toString:function(){return["AnnotationEditor(",this.model+"",")"].join("")}});return{AnnotationEditor:i}}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(3),i(2))},function(e,t,i){var n,s;(function(o){n=[i(2),i(3),i(6)],s=function(e,t,i){"use strict";var n=t.Collection.extend({initialize:function(e,i){t.Collection.prototype.initialize.call(this,e,i),this.setOrder(i.order||this.order,{silent:!0})},_setUpListeners:function(){return this.on({"changed-order":this.sort})},fetch:function(e){return e=this._buildFetchOptions(e),t.Collection.prototype.fetch.call(this,e)},_buildFetchOptions:function(t){t=e.clone(t)||{};var i=this;t.traditional=!0,t.data=t.data||i._buildFetchData(t);var n=this._buildFetchFilters(t);return e.isEmpty(n)||e.extend(t.data,this._fetchFiltersToAjaxData(n)),t},_buildFetchData:function(t){var i={};return this.order&&(i.order=this.order),e.defaults(e.pick(t,this._fetchParams),i)},_fetchParams:["order","limit","offset","view","keys"],_buildFetchFilters:function(t){return e.clone(t.filters||{})},_fetchFiltersToAjaxData:function(t){var i={q:[],qv:[]};return e.each(t,function(e,t){void 0!==e&&""!==e&&(e===!0&&(e="True"),e===!1&&(e="False"),null===e&&(e="None"),i.q.push(t),i.qv.push(e))}),i},reset:function(e,i){return this.allFetched=!1,t.Collection.prototype.reset.call(this,e,i)},order:null,comparators:{update_time:i.buildComparator("update_time",{ascending:!1}),"update_time-asc":i.buildComparator("update_time",{ascending:!0}),create_time:i.buildComparator("create_time",{ascending:!1}),"create_time-asc":i.buildComparator("create_time",{ascending:!0})},setOrder:function(t,i){i=i||{};var n=this,s=n.comparators[t];if(e.isUndefined(s))throw new Error("unknown order: "+t);if(s!==n.comparator){n.order;return n.order=t,n.comparator=s,i.silent||n.trigger("changed-order",i),n}}}),s=n.extend({limitPerPage:500,initialize:function(e,t){n.prototype.initialize.call(this,e,t),this.currentPage=t.currentPage||0},getTotalItemCount:function(){return this.length},shouldPaginate:function(){return this.getTotalItemCount()>=this.limitPerPage},getLastPage:function(){return Math.floor(this.getTotalItemCount()/this.limitPerPage)},getPageCount:function(){return this.getLastPage()+1},getPageLimitOffset:function(e){return e=this.constrainPageNum(e),{limit:this.limitPerPage,offset:e*this.limitPerPage}},constrainPageNum:function(e){return Math.max(0,Math.min(e,this.getLastPage()))},fetchPage:function(t,i){var n=this;return t=n.constrainPageNum(t),n.currentPage=t,i=e.defaults(i||{},n.getPageLimitOffset(t)),n.trigger("fetching-more"),n.fetch(i).always(function(){n.trigger("fetching-more-done")})},fetchCurrentPage:function(e){return this.fetchPage(this.currentPage,e)},fetchPrevPage:function(e){return this.fetchPage(this.currentPage-1,e)},fetchNextPage:function(e){return this.fetchPage(this.currentPage+1,e)}}),a=n.extend({limitOnFirstFetch:null,limitPerFetch:100,initialize:function(e,t){n.prototype.initialize.call(this,e,t),this.limitOnFirstFetch=t.limitOnFirstFetch||this.limitOnFirstFetch,this.limitPerFetch=t.limitPerFetch||this.limitPerFetch,this.allFetched=!1,this.lastFetched=t.lastFetched||0},_buildFetchOptions:function(e){return e.remove=e.remove||!1,n.prototype._buildFetchOptions.call(this,e)},fetchFirst:function(t){return t=t?e.clone(t):{},this.allFetched=!1,this.lastFetched=0,this.fetchMore(e.defaults(t,{reset:!0,limit:this.limitOnFirstFetch}))},fetchMore:function(t){t=e.clone(t||{});var i=this;if(!t.reset&&i.allFetched)return o.when();t.offset=t.reset?0:t.offset||i.lastFetched;var n=t.limit=t.limit||i.limitPerFetch||null;return i.trigger("fetching-more"),i.fetch(t).always(function(){i.trigger("fetching-more-done")}).done(function(t){var s=e.isArray(t)?t.length:0;i.lastFetched+=s,(!n||n>s)&&(i.allFetched=!0,i.trigger("all-fetched",this))})},fetchAll:function(t){t=t||{};var i=this;return t=e.pick(t,"silent"),t.filters={},i.fetch(t).done(function(e){i.allFetched=!0,i.trigger("all-fetched",i)})}});return{ControlledFetchCollection:n,PaginatedCollection:s,InfinitelyScrollingCollection:a}}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(1))},function(e,t,i){var n,s;(function(o){n=[i(76),i(30),i(29),i(6),i(5)],s=function(e,t,i,n,s){"use strict";var a="collections",r=e.ModelListPanel,l=r.extend({_logNamespace:a,className:r.prototype.className+" dataset-collection-panel",DatasetDCEViewClass:i.DatasetDCEListItemView,NestedDCDCEViewClass:i.NestedDCDCEListItemView,modelCollectionKey:"elements",initialize:function(e){r.prototype.initialize.call(this,e),this.linkTarget=e.linkTarget||"_blank",this.hasUser=e.hasUser,this.panelStack=[],this.parentName=e.parentName,this.foldoutStyle=e.foldoutStyle||"foldout"},_queueNewRender:function(e,t){t=void 0===t?this.fxSpeed:t;var i=this;i.log("_queueNewRender:",e,t),i._swapNewRender(e),i.trigger("rendered",i)},_filterCollection:function(){return this.model.getVisibleContents()},_getItemViewClass:function(e){switch(e.get("element_type")){case"hda":return this.DatasetDCEViewClass;case"dataset_collection":return this.NestedDCDCEViewClass}throw new TypeError("Unknown element type:",e.get("element_type"))},_getItemViewOptions:function(e){var t=r.prototype._getItemViewOptions.call(this,e);return o.extend(t,{linkTarget:this.linkTarget,hasUser:this.hasUser,foldoutStyle:this.foldoutStyle})},_setUpItemViewListeners:function(e){var t=this;return r.prototype._setUpItemViewListeners.call(t,e),t.listenTo(e,{"expanded:drilldown":function(e,t){this._expandDrilldownPanel(t)},"collapsed:drilldown":function(e,t){this._collapseDrilldownPanel(t)}}),this},_expandDrilldownPanel:function(e){this.panelStack.push(e),this.$("> .controls").add(this.$list()).hide(),e.parentName=this.model.get("name"),this.$el.append(e.render().$el)},_collapseDrilldownPanel:function(e){this.panelStack.pop(),this.render()},events:{"click .navigation .back":"close"},close:function(e){this.remove(),this.trigger("close")},toString:function(){return"CollectionView("+(this.model?this.model.get("name"):"")+")"}});l.prototype.templates=function(){var e=n.wrapTemplate(['
    ','",'
    ','
    <%- collection.name || collection.element_identifier %>
    ','
    ','<% if( collection.collection_type === "list" ){ %>',s("a list of datasets"),'<% } else if( collection.collection_type === "paired" ){ %>',s("a pair of datasets"),'<% } else if( collection.collection_type === "list:paired" ){ %>',s("a list of paired datasets"),'<% } else if( collection.collection_type === "list:list" ){ %>',s("a list of dataset lists"),"<% } %>","
    ","
    ","
    "],"collection");return o.extend(o.clone(r.prototype.templates),{controls:e})}();var c=l.extend({DatasetDCEViewClass:i.DatasetDCEListItemView,toString:function(){return"ListCollectionView("+(this.model?this.model.get("name"):"")+")"}}),d=c.extend({toString:function(){return"PairCollectionView("+(this.model?this.model.get("name"):"")+")"}}),h=l.extend({NestedDCDCEViewClass:i.NestedDCDCEListItemView.extend({foldoutPanelClass:d}),toString:function(){return"ListOfPairsCollectionView("+(this.model?this.model.get("name"):"")+")"}}),u=l.extend({NestedDCDCEViewClass:i.NestedDCDCEListItemView.extend({foldoutPanelClass:d}),toString:function(){return"ListOfListsCollectionView("+(this.model?this.model.get("name"):"")+")"}});return{CollectionView:l,ListCollectionView:c,PairCollectionView:d,ListOfPairsCollectionView:h,ListOfListsCollectionView:u}}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(2))},function(e,t,i){var n,s;(function(o,a){n=[i(12),i(32),i(77),i(66),i(22),i(6),i(5)],s=function(e,t,n,s,r,l,c){"use strict";var d=t.DatasetListItemView,h=d.extend({initialize:function(e){d.prototype.initialize.call(this,e),this.hasUser=e.hasUser,this.purgeAllowed=e.purgeAllowed||!1,this.tagsEditorShown=e.tagsEditorShown||!1,this.annotationEditorShown=e.annotationEditorShown||!1},_renderPrimaryActions:function(){var t=d.prototype._renderPrimaryActions.call(this);return this.model.get("state")===e.NOT_VIEWABLE?t:d.prototype._renderPrimaryActions.call(this).concat([this._renderEditButton(),this._renderDeleteButton()])},_renderEditButton:function(){if(this.model.get("state")===e.DISCARDED||!this.model.get("accessible"))return null;var t=this.model.get("purged"),i=this.model.get("deleted"),n={title:c("Edit attributes"),href:this.model.urls.edit,target:this.linkTarget,faIcon:"fa-pencil",classes:"edit-btn"};return i||t?(n.disabled=!0,t?n.title=c("Cannot edit attributes of datasets removed from disk"):i&&(n.title=c("Undelete dataset to edit attributes"))):o.contains([e.UPLOAD,e.NEW],this.model.get("state"))&&(n.disabled=!0,n.title=c("This dataset is not yet editable")),r(n)},_renderDeleteButton:function(){if(!this.model.get("accessible"))return null;var e=this,t=this.model.isDeletedOrPurged();return r({title:c(t?"Dataset is already deleted":"Delete"),disabled:t,faIcon:"fa-times",classes:"delete-btn",onclick:function(){e.$el.find(".icon-btn.delete-btn").trigger("mouseout"),e.model["delete"]()}})},_renderDetails:function(){var t=d.prototype._renderDetails.call(this),i=this.model.get("state");return!this.model.isDeletedOrPurged()&&o.contains([e.OK,e.FAILED_METADATA],i)&&(this._renderTags(t),this._renderAnnotation(t),this._makeDbkeyEditLink(t)),this._setUpBehaviors(t),t},_renderSecondaryActions:function(){var t=d.prototype._renderSecondaryActions.call(this);switch(this.model.get("state")){case e.UPLOAD:case e.NOT_VIEWABLE:return t;case e.ERROR:return t.unshift(this._renderErrButton()),t.concat([this._renderRerunButton()]);case e.OK:case e.FAILED_METADATA:return t.concat([this._renderRerunButton(),this._renderVisualizationsButton()])}return t.concat([this._renderRerunButton()])},_renderErrButton:function(){return r({title:c("View or report this error"),href:this.model.urls.report_error,classes:"report-error-btn",target:this.linkTarget,faIcon:"fa-bug"})},_renderRerunButton:function(){var e=this.model.get("creating_job");return this.model.get("rerunnable")?r({title:c("Run this job again"),href:this.model.urls.rerun,classes:"rerun-btn",target:this.linkTarget,faIcon:"fa-refresh",onclick:function(t){t.preventDefault(),!function(){var t=[i(18)];(function(t){var i=new t.View({job_id:e});i.deferred.execute(function(){Galaxy.app.display(i)})}).apply(null,t)}()}}):void 0},_renderVisualizationsButton:function(){var e=this.model.get("visualizations");if(this.model.isDeletedOrPurged()||!this.hasUser||!this.model.hasData()||o.isEmpty(e))return null;if(!o.isObject(e[0]))return this.warn("Visualizations have been switched off"),null;var t=a(this.templates.visualizations(e,this));return t.find('[target="galaxy_main"]').attr("target",this.linkTarget),this._addScratchBookFn(t.find(".visualization-link").addBack(".visualization-link")),t},_addScratchBookFn:function(e){e.click(function(e){Galaxy.frame&&Galaxy.frame.active&&(Galaxy.frame.add({title:"Visualization",url:a(this).attr("href")}),e.preventDefault(),e.stopPropagation())})},_renderTags:function(e){if(this.hasUser){var t=this;this.tagsEditor=new n.TagsEditor({model:this.model,el:e.find(".tags-display"),onshowFirstTime:function(){this.render()},onshow:function(){t.tagsEditorShown=!0},onhide:function(){t.tagsEditorShown=!1},$activator:r({title:c("Edit dataset tags"),classes:"tag-btn",faIcon:"fa-tags"}).appendTo(e.find(".actions .right"))}),this.tagsEditorShown&&this.tagsEditor.toggle(!0)}},_renderAnnotation:function(e){if(this.hasUser){var t=this;this.annotationEditor=new s.AnnotationEditor({model:this.model,el:e.find(".annotation-display"),onshowFirstTime:function(){this.render()},onshow:function(){t.annotationEditorShown=!0},onhide:function(){t.annotationEditorShown=!1},$activator:r({title:c("Edit dataset annotation"),classes:"annotate-btn",faIcon:"fa-comment"}).appendTo(e.find(".actions .right"))}),this.annotationEditorShown&&this.annotationEditor.toggle(!0)}},_makeDbkeyEditLink:function(e){if("?"===this.model.get("metadata_dbkey")&&!this.model.isDeletedOrPurged()){var t=a('?').attr("href",this.model.urls.edit).attr("target",this.linkTarget);e.find(".dbkey .value").replaceWith(t)}},events:o.extend(o.clone(d.prototype.events),{"click .undelete-link":"_clickUndeleteLink","click .purge-link":"_clickPurgeLink","click .edit-btn":function(e){this.trigger("edit",this,e)},"click .delete-btn":function(e){this.trigger("delete",this,e)},"click .rerun-btn":function(e){this.trigger("rerun",this,e)},"click .report-err-btn":function(e){this.trigger("report-err",this,e)},"click .visualization-btn":function(e){this.trigger("visualize",this,e)},"click .dbkey a":function(e){this.trigger("edit",this,e)}}),_clickUndeleteLink:function(e){return this.model.undelete(),!1},_clickPurgeLink:function(e){return confirm(c("This will permanently remove the data in your dataset. Are you sure?"))&&this.model.purge(),!1},toString:function(){var e=this.model?this.model+"":"(no model)";return"HDAEditView("+e+")"}});return h.prototype.templates=function(){var e=o.extend({},d.prototype.templates.warnings,{failed_metadata:l.wrapTemplate(['<% if( dataset.state === "failed_metadata" ){ %>','","<% } %>"],"dataset"),deleted:l.wrapTemplate(["<% if( dataset.deleted && !dataset.purged ){ %>",'
    ',c("This dataset has been deleted"),'
    ',c("Undelete it"),"","<% if( view.purgeAllowed ){ %>",'
    ',c("Permanently remove it from disk"),"","<% } %>","
    ","<% } %>"],"dataset")}),t=l.wrapTemplate(["<% if( visualizations.length === 1 ){ %>",'">','',"","<% } else { %>",'","<% } %>"],"visualizations");return o.extend({},d.prototype.templates,{warnings:e,visualizations:t})}(),{DatasetListItemEdit:h}}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(2),i(1))},function(e,t,i){var n,s;(function(o,a,r){n=[i(12),i(6),i(5)],s=function(e,t,i){"use strict";var n="dataset",s=t.SearchableModelMixin,l=o.Model.extend(t.LoggableMixin).extend(t.mixin(s,{_logNamespace:n,defaults:{state:e.NEW,deleted:!1,purged:!1,name:"(unnamed dataset)",accessible:!0,data_type:"",file_ext:"",file_size:0,meta_files:[],misc_blurb:"",misc_info:"",tags:[]},initialize:function(t,i){this.debug(this+"(Dataset).initialize",t,i),this.get("accessible")||this.set("state",e.NOT_VIEWABLE),this.urls=this._generateUrls(),this._setUpListeners()},_generateUrls:function(){var e=this.get("id");if(!e)return{};var t={purge:"datasets/"+e+"/purge_async",display:"datasets/"+e+"/display/?preview=True",edit:"datasets/"+e+"/edit",download:"datasets/"+e+"/display?to_ext="+this.get("file_ext"),report_error:"dataset/errors?id="+e,rerun:"tool_runner/rerun?id="+e,show_params:"datasets/"+e+"/show_params",visualization:"visualization",meta_download:"dataset/get_metadata_file?hda_id="+e+"&metadata_name="};return a.each(t,function(e,i){t[i]=Galaxy.root+e}),this.urls=t,t},_setUpListeners:function(){this.on("change:state",function(e,t){this.log(this+" has changed state:",e,t),this.inReadyState()&&this.trigger("state:ready",e,t,this.previous("state"))}),this.on("change:id change:file_ext",function(e){this._generateUrls()})},toJSON:function(){var e=o.Model.prototype.toJSON.call(this);return a.extend(e,{urls:this.urls})},isDeletedOrPurged:function(){return this.get("deleted")||this.get("purged")},inReadyState:function(){var t=a.contains(e.READY_STATES,this.get("state"));return this.isDeletedOrPurged()||t},hasDetails:function(){return this.get("accessible")?this.has("annotation"):!0},hasData:function(){return this.get("file_size")>0},fetch:function(e){var t=this;return o.Model.prototype.fetch.call(this,e).always(function(){t._generateUrls()})},parse:function(e,t){var i=o.Model.prototype.parse.call(this,e,t);return i.create_time&&(i.create_time=new Date(i.create_time)),i.update_time&&(i.update_time=new Date(i.update_time)),i},save:function(e,t){return t=t||{},t.wait=a.isUndefined(t.wait)?!0:t.wait,o.Model.prototype.save.call(this,e,t)},"delete":function(e){return this.get("deleted")?r.when():this.save({deleted:!0},e)},undelete:function(e){return!this.get("deleted")||this.get("purged")?r.when():this.save({deleted:!1},e)},purge:function(e){if(this.get("purged"))return r.when();e=e||{},e.url=this.urls.purge;var t=this,n=r.ajax(e);return n.done(function(e,i,n){t.set({deleted:!0,purged:!0})}),n.fail(function(n,s,o){var a=i("Unable to purge dataset"),r="Removal of datasets by users is not allowed in this Galaxy instance";n.responseJSON&&n.responseJSON.error?a=n.responseJSON.error:-1!==n.responseText.indexOf(r)&&(a=r), -n.responseText=a,t.trigger("error",t,n,e,i(a),{error:a})}),n},searchAttributes:["name","file_ext","genome_build","misc_blurb","misc_info","annotation","tags"],searchAliases:{title:"name",format:"file_ext",database:"genome_build",blurb:"misc_blurb",description:"misc_blurb",info:"misc_info",tag:"tags"},toString:function(){var e=this.get("id")||"";return this.get("name")&&(e='"'+this.get("name")+'",'+e),"Dataset("+e+")"}})),c=o.Collection.extend(t.LoggableMixin).extend({_logNamespace:n,model:l,urlRoot:Galaxy.root+"api/datasets",url:function(){return this.urlRoot},ids:function(){return this.map(function(e){return e.get("id")})},notReady:function(){return this.filter(function(e){return!e.inReadyState()})},haveDetails:function(){return this.all(function(e){return e.hasDetails()})},ajaxQueue:function(e,t){var i=r.Deferred(),n=this.length,s=[];if(!n)return i.resolve([]),i;var o=this.chain().reverse().map(function(a,r){return function(){var l=e.call(a,t);l.done(function(e){i.notify({curr:r,total:n,response:e,model:a})}),l.always(function(e){s.push(e),o.length?o.shift()():i.resolve(s)})}}).value();return o.shift()(),i},matches:function(e){return this.filter(function(t){return t.matches(e)})},toString:function(){return["DatasetAssociationCollection(",this.length,")"].join("")}});return{DatasetAssociation:l,DatasetAssociationCollection:c}}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(3),i(2),i(1))},function(e,t,i){var n,s;(function(o){n=[i(32),i(6),i(5)],s=function(e,t,i){"use strict";var n=e.DatasetListItemView,s=n.extend({className:n.prototype.className+" history-content",initialize:function(e,t){n.prototype.initialize.call(this,e,t)},toString:function(){var e=this.model?this.model+"":"(no model)";return"HDAListItemView("+e+")"}});return s.prototype.templates=function(){var e=t.wrapTemplate(['
    ','','
    ','<%- dataset.hid %> ','<%- dataset.name %>',"
    ","
    "],"dataset"),s=o.extend({},n.prototype.templates.warnings,{hidden:t.wrapTemplate(["<% if( !dataset.visible ){ %>",'
    ',i("This dataset has been hidden"),"
    ","<% } %>"],"dataset")});return o.extend({},n.prototype.templates,{titleBar:e,warnings:s})}(),{HDAListItemView:s}}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(2))},function(e,t,i){var n,s;(function(o){n=[i(70),i(74),i(6),i(5)],s=function(e,t,i,n){"use strict";var s=e.DatasetAssociation,a=t.HistoryContentMixin,r=s.extend(i.mixin(a,{defaults:o.extend({},s.prototype.defaults,a.defaults,{history_content_type:"dataset",model_class:"HistoryDatasetAssociation"})}));return{HistoryDatasetAssociation:r}}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(2))},function(e,t,i){var n,s;(function(o){n=[i(12),i(29),i(68),i(6),i(5)],s=function(e,t,i,n,s){"use strict";var a=t.DCListItemView,r=a.extend({className:a.prototype.className+" history-content",_setUpListeners:function(){a.prototype._setUpListeners.call(this),this.listenTo(this.model,{"change:populated change:visible":function(e,t){this.render()}})},_getFoldoutPanelClass:function(){switch(this.model.get("collection_type")){case"list":return i.ListCollectionView;case"paired":return i.PairCollectionView;case"list:paired":return i.ListOfPairsCollectionView;case"list:list":return i.ListOfListsCollectionView}throw new TypeError("Uknown collection_type: "+this.model.get("collection_type"))},_swapNewRender:function(t){a.prototype._swapNewRender.call(this,t);var i=this.model.get("populated")?e.OK:e.RUNNING;return this.$el.addClass("state-"+i),this.$el},toString:function(){var e=this.model?this.model+"":"(no model)";return"HDCAListItemView("+e+")"}});return r.prototype.templates=function(){var e=o.extend({},a.prototype.templates.warnings,{hidden:n.wrapTemplate(["<% if( !collection.visible ){ %>",'
    ',s("This collection has been hidden"),"
    ","<% } %>"],"collection")}),t=n.wrapTemplate(['
    ','','
    ','<%- collection.hid %> ','<%- collection.name %>',"
    ",'
    ',"
    "],"collection");return o.extend({},a.prototype.templates,{warnings:e,titleBar:t})}(),{HDCAListItemView:r}}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(2))},function(e,t,i){var n,s;(function(o){n=[i(12),i(6),i(5)],s=function(e,t,i){"use strict";var n={defaults:{history_id:null,history_content_type:null,hid:null,visible:!0},idAttribute:"type_id",hidden:function(){return!this.get("visible")},isVisible:function(e,t){var i=!0;return e||!this.get("deleted")&&!this.get("purged")||(i=!1),t||this.get("visible")||(i=!1),i},urlRoot:Galaxy.root+"api/histories/",url:function(){var e=this.urlRoot+this.get("history_id")+"/contents/"+this.get("history_content_type")+"s/"+this.get("id");return e},hide:function(e){return this.get("visible")?this.save({visible:!1},e):o.when()},unhide:function(e){return this.get("visible")?o.when():this.save({visible:!0},e)},toString:function(){return[this.get("type_id"),this.get("hid"),this.get("name")].join(":")}};return{HistoryContentMixin:n}}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(1))},function(e,t,i){var n,s;(function(o,a,r,l){n=[i(40),i(41),i(67),i(4),i(6),i(5)],s=function(e,t,i,n,s,c){"use strict";var d=o.Model.extend(s.LoggableMixin).extend(s.mixin(s.SearchableModelMixin,{_logNamespace:"history",UPDATE_DELAY:4e3,defaults:{model_class:"History",id:null,name:"Unnamed History",state:"new",deleted:!1,contents_active:{},contents_states:{}},urlRoot:Galaxy.root+"api/histories",contentsClass:e.HistoryContents,searchAttributes:["name","annotation","tags"],searchAliases:{title:"name",tag:"tags"},initialize:function(e,t){t=t||{},this.logger=t.logger||null,this.log(this+".initialize:",e,t),this.contents=new this.contentsClass([],{history:this,historyId:this.get("id"),order:t.order}),this._setUpListeners(),this._setUpCollectionListeners(),this.updateTimeoutId=null},_setUpListeners:function(){return this.on({error:function(e,t,i,n,s){this.clearUpdateTimeout()},"change:id":function(e,t){this.contents&&(this.contents.historyId=t)}})},_setUpCollectionListeners:function(){return this.contents?this.listenTo(this.contents,{error:function(){this.trigger.apply(this,a.makeArray(arguments))}}):this},contentsShown:function(){var e=this.get("contents_active"),t=e.active||0;return t+=this.contents.includeDeleted?e.deleted:0,t+=this.contents.includeHidden?e.hidden:0},nice_size:function(){var e=this.get("size");return e?n.bytesToString(e,!0,2):c("(empty)")},toJSON:function(){return r.extend(o.Model.prototype.toJSON.call(this),{nice_size:this.nice_size()})},get:function(e){return"nice_size"===e?this.nice_size():o.Model.prototype.get.apply(this,arguments)},ownedByCurrUser:function(){return Galaxy&&Galaxy.user?Galaxy.user.isAnonymous()||Galaxy.user.id!==this.get("user_id")?!1:!0:!1},numOfUnfinishedJobs:function(){var e=this.get("non_ready_jobs");return e?e.length:0},numOfUnfinishedShownContents:function(){return this.contents.runningAndActive().length||0},_fetchContentRelatedAttributes:function(){var e=["size","non_ready_jobs","contents_active","hid_counter"];return this.fetch({data:l.param({keys:e.join(",")})})},refresh:function(e){e=e||{};var t=this,i=t.lastUpdateTime;this.contents.allFetched=!1;var n=0!==t.contents.currentPage?function(){return t.contents.fetchPage(0)}:function(){return t.contents.fetchUpdated(i)};return n().done(function(i,n,s){var o;try{o=new Date(s.getResponseHeader("Date"))}catch(a){}t.lastUpdateTime=o||new Date,t.checkForUpdates(e)})},checkForUpdates:function(e){function t(){n.clearUpdateTimeout(),n.updateTimeoutId=setTimeout(function(){n.refresh(e)},i)}e=e||{};var i=this.UPDATE_DELAY,n=this;if(n.id){var s=this.numOfUnfinishedShownContents();s>0?t():n._fetchContentRelatedAttributes().done(function(e){n.numOfUnfinishedJobs()>0?t():n.trigger("ready")})}},clearUpdateTimeout:function(){this.updateTimeoutId&&(clearTimeout(this.updateTimeoutId),this.updateTimeoutId=null)},parse:function(e,t){var i=o.Model.prototype.parse.call(this,e,t);return i.create_time&&(i.create_time=new Date(i.create_time)),i.update_time&&(i.update_time=new Date(i.update_time)),i},fetchWithContents:function(e,t){e=e||{};var i=this;return e.view="dev-detailed",this.fetch(e).then(function(e){return i.contents.history=i,i.contents.setHistoryId(e.id),i.fetchContents(t)})},fetchContents:function(e){e=e||{};var t=this;return t.lastUpdateTime=new Date,t.contents.fetchCurrentPage(e)},_delete:function(e){return this.get("deleted")?a.when():this.save({deleted:!0},e)},purge:function(e){return this.get("purged")?a.when():this.save({deleted:!0,purged:!0},e)},undelete:function(e){return this.get("deleted")?this.save({deleted:!1},e):a.when()},copy:function(e,t,i){if(e=void 0!==e?e:!0,!this.id)throw new Error("You must set the history ID before copying it.");var n={history_id:this.id};e&&(n.current=!0),t&&(n.name=t),i||(n.all_datasets=!1),n.view="dev-detailed";var s=this,o=a.post(this.urlRoot,n);return e?o.then(function(e){var t=new d(e);return t.setAsCurrent().done(function(){s.trigger("copied",s,e)})}):o.done(function(e){s.trigger("copied",s,e)})},setAsCurrent:function(){var e=this,t=a.getJSON(Galaxy.root+"history/set_as_current?id="+this.id);return t.done(function(){e.trigger("set-as-current",e)}),t},toString:function(){return"History("+this.get("id")+","+this.get("name")+")"}})),h=i.InfinitelyScrollingCollection,u=h.extend(s.LoggableMixin).extend({_logNamespace:"history",model:d,order:"update_time",limitOnFirstFetch:10,limitPerFetch:10,initialize:function(e,t){t=t||{},this.log("HistoryCollection.initialize",e,t),h.prototype.initialize.call(this,e,t),this.includeDeleted=t.includeDeleted||!1,this.currentHistoryId=t.currentHistoryId,this.setUpListeners()},urlRoot:Galaxy.root+"api/histories",url:function(){return this.urlRoot},setUpListeners:function(){return this.on({"change:deleted":function(e){this.debug("change:deleted",this.includeDeleted,e.get("deleted")),!this.includeDeleted&&e.get("deleted")&&this.remove(e)},copied:function(e,t){this.setCurrent(new d(t,[]))},"set-as-current":function(e){var t=this.currentHistoryId;this.trigger("no-longer-current",t),this.currentHistoryId=e.id}})},_buildFetchData:function(e){return r.extend(h.prototype._buildFetchData.call(this,e),{view:"dev-detailed"})},_buildFetchFilters:function(e){var t=h.prototype._buildFetchFilters.call(this,e)||{},i={};return this.includeDeleted?i.deleted=null:(i.deleted=!1,i.purged=!1),r.defaults(t,i)},fetchFirst:function(e){var t=this,i=l.when();return this.currentHistoryId&&(i=h.prototype.fetchFirst.call(t,{silent:!0,limit:1,filters:{purged:"",deleted:"","encoded_id-in":this.currentHistoryId}})),i.then(function(){return e=e||{},e.offset=0,t.fetchMore(e)})},comparators:r.extend(r.clone(h.prototype.comparators),{name:s.buildComparator("name",{ascending:!0}),"name-dsc":s.buildComparator("name",{ascending:!1}),size:s.buildComparator("size",{ascending:!1}),"size-asc":s.buildComparator("size",{ascending:!0})}),sort:function(e){e=e||{};var t=e.silent,i=this.remove(this.get(this.currentHistoryId));return h.prototype.sort.call(this,r.defaults({silent:!0},e)),this.unshift(i,{silent:!0}),t||this.trigger("sort",this,e),this},create:function(e,t,i,n){var s=this,o=a.getJSON(Galaxy.root+"history/create_new_current");return o.done(function(e){s.setCurrent(new d(e,[],i||{}))})},setCurrent:function(e,t){return t=t||{},this.unshift(e,t),this.currentHistoryId=e.get("id"),t.silent||this.trigger("new-current",e,this),this},toString:function(){return"HistoryCollection("+this.length+",current:"+this.currentHistoryId+")"}});return{History:d,HistoryCollection:u}}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(3),i(1),i(2),i(1))},function(e,t,i){var n,s;(function(o,a,r){n=[i(42),i(127),i(6),i(5),i(85)],s=function(e,t,i,n){"use strict";var s="list",l=o.View.extend(i.LoggableMixin).extend({_logNamespace:s,viewClass:e.ListItemView,collectionClass:o.Collection,tagName:"div",className:"list-panel",fxSpeed:"fast",emptyMsg:n("This list is empty"),noneFoundMsg:n("No matching items found"),searchPlaceholder:n("search"),initialize:function(e,t){e=e||{},e.logger&&(this.logger=e.logger),this.log(this+".initialize:",e),this.fxSpeed=a.has(e,"fxSpeed")?e.fxSpeed:this.fxSpeed,this.filters=[],this.searchFor=e.searchFor||"",this.selecting=void 0!==e.selecting?e.selecting:!0,this.selected=e.selected||[],this.lastSelected=null,this.dragItems=e.dragItems||!1,this.viewClass=e.viewClass||this.viewClass,this.views=[],this.collection=e.collection||this._createDefaultCollection(),this.filters=e.filters||[],this.$scrollContainer=e.$scrollContainer||this.$scrollContainer,this.title=e.title||"",this.subtitle=e.subtitle||"",this._setUpListeners()},_setUpListeners:function(){return this.off(),this.on({error:function(e,t,i,n,s){console.error(e,t,i,n,s)},loading:function(){this._showLoadingIndicator("loading...",40)},"loading-done":function(){this._hideLoadingIndicator(40)}}),this.once("rendered",function(){this.trigger("rendered:initial",this)}),this._setUpCollectionListeners(),this._setUpViewListeners(),this},_createDefaultCollection:function(){return new this.collectionClass([])},_setUpCollectionListeners:function(){return this.log(this+"._setUpCollectionListeners",this.collection),this.stopListening(this.collection),this.listenTo(this.collection,{error:function(e,t,i,n,s){this.trigger("error",e,t,i,n,s)},update:function(e,t){var i=t.changes;return t.renderAll||i.added.length+i.removed.length>1?this.renderItems():1===i.added.length?this.addItemView(a.first(i.added),e,t):1===i.removed.length?this.removeItemView(a.first(i.removed),e,t):void 0}}),this},_setUpViewListeners:function(){this.log(this+"._setUpViewListeners"),this.on({"view:selected":function(e,t){if(t&&t.shiftKey&&this.lastSelected){var i=this.viewFromModelId(this.lastSelected);i&&this.selectRange(e,i)}else t&&t.altKey&&!this.selecting&&this.showSelectors();this.selected.push(e.model.id),this.lastSelected=e.model.id},"view:de-selected":function(e,t){this.selected=a.without(this.selected,e.model.id)}})},render:function(e){this.log(this+".render",e);var t=this._buildNewRender();return this._setUpBehaviors(t),this._queueNewRender(t,e),this},_buildNewRender:function(){this.debug(this+"(ListPanel)._buildNewRender");var e=r(this.templates.el({},this));return this._renderControls(e),this._renderTitle(e),this._renderSubtitle(e),this._renderSearch(e),this.renderItems(e),e},_renderControls:function(e){this.debug(this+"(ListPanel)._renderControls");var t=r(this.templates.controls({},this));return e.find(".controls").replaceWith(t),t},_renderTitle:function(e){},_renderSubtitle:function(e){},_queueNewRender:function(e,t){t=void 0===t?this.fxSpeed:t;var i=this;i.log("_queueNewRender:",e,t),r(i).queue("fx",[function(e){i.$el.fadeOut(t,e)},function(t){i._swapNewRender(e),t()},function(e){i.$el.fadeIn(t,e)},function(e){i.trigger("rendered",i),e()}])},_swapNewRender:function(e){return this.$el.empty().attr("class",this.className).append(e.children()),this.selecting&&this.showSelectors(0),this},_setUpBehaviors:function(e){return e=e||this.$el,this.$controls(e).find("[title]").tooltip(),this._renderMultiselectActionMenu(e),this},_renderMultiselectActionMenu:function(e){e=e||this.$el;var t=e.find(".list-action-menu"),i=this.multiselectActions();if(!i.length)return t.empty();var s=r(['
    ','",'","
    "].join("")),o=i.map(function(e){var t=['
  • ',e.html,"
  • "].join("");return r(t).click(function(t){return t.preventDefault(),e.func(t)})});return s.find("ul").append(o),t.replaceWith(s),s},multiselectActions:function(){return[]},$scrollContainer:function(e){return(e||this.$el).parent().parent()},$controls:function(e){return(e||this.$el).find("> .controls")},$list:function(e){return(e||this.$el).find("> .list-items")},$messages:function(e){return(e||this.$el).find("> .controls .messages")},$emptyMessage:function(e){return(e||this.$el).find("> .empty-message")},renderItems:function(e){e=e||this.$el;var t=this;t.log(this+".renderItems",e);var i=t.$list(e);t.freeViews();var n=t._filterCollection();return t.views=n.map(function(e){var i=t._createItemView(e);return i}),i.empty(),t.views.length&&t._attachItems(e),t._renderEmptyMessage(e).toggle(!t.views.length),t.trigger("views:ready",t.views),t.views},_filterCollection:function(){var e=this;return e.collection.filter(a.bind(e._filterItem,e))},_filterItem:function(e){var t=this;return a.every(t.filters.map(function(t){return t.call(e)}))&&(!t.searchFor||e.matchesAll(t.searchFor))},_createItemView:function(e){var t=this._getItemViewClass(e),i=a.extend(this._getItemViewOptions(e),{model:e}),n=new t(i);return this._setUpItemViewListeners(n),n},_destroyItemView:function(e){this.stopListening(e),this.views=a.without(this.views,e)},_destroyItemViews:function(e){var t=this;return t.views.forEach(function(e){t.stopListening(e)}),t.views=[],t},freeViews:function(){return this._destroyItemViews()},_getItemViewClass:function(e){return this.viewClass},_getItemViewOptions:function(e){return{fxSpeed:this.fxSpeed,expanded:!1,selectable:this.selecting,selected:a.contains(this.selected,e.id),draggable:this.dragItems}},_setUpItemViewListeners:function(e){var t=this;return this.listenTo(e,"all",function(){var e=Array.prototype.slice.call(arguments,0);e[0]="view:"+e[0],t.trigger.apply(t,e)}),this.listenTo(e,"draggable:dragstart",function(e,t){var i={},n=this.getSelectedModels();i=n.length?n.toJSON():[t.model.toJSON()],e.dataTransfer.setData("text",JSON.stringify(i))},this),t},_attachItems:function(e){var t=this;return this.$list(e).append(this.views.map(function(e){return t._renderItemView$el(e)})),this},_renderItemView$el:function(e){return e.render(0).$el},_renderEmptyMessage:function(e){this.debug("_renderEmptyMessage",e,this.searchFor);var t=this.searchFor?this.noneFoundMsg:this.emptyMsg;return this.$emptyMessage(e).text(t)},expandAll:function(){a.each(this.views,function(e){e.expand()})},collapseAll:function(){a.each(this.views,function(e){e.collapse()})},addItemView:function(e,t,i){var n=this,s=n._filterCollection().indexOf(e);if(-1!==s){var o=n._createItemView(e);return r(o).queue("fx",[function(e){n.$emptyMessage().is(":visible")?n.$emptyMessage().fadeOut(n.fxSpeed,e):e()},function(e){n._attachView(o,s),e()}]),o}},_attachView:function(e,t,i){i=a.isUndefined(i)?!0:i,t=t||0;var n=this;return n.views.splice(t,0,e),n._insertIntoListAt(t,n._renderItemView$el(e).hide()),n.trigger("view:attached",e),i?e.$el.slideDown(n.fxSpeed,function(){n.trigger("view:attached:rendered")}):(e.$el.show(),n.trigger("view:attached:rendered")),e},_insertIntoListAt:function(e,t){var i=this.$list();return 0===e?i.prepend(t):i.children().eq(e-1).after(t),t},removeItemView:function(e,t,i){var n=this,s=a.find(n.views,function(t){return t.model===e});return s?(n.views=a.without(n.views,s),n.trigger("view:removed",s),r({}).queue("fx",[function(e){s.$el.fadeOut(n.fxSpeed,e)},function(e){s.remove(),n.trigger("view:removed:rendered"),n.views.length?e():n._renderEmptyMessage().fadeIn(n.fxSpeed,e)}]),s):void 0},viewFromModelId:function(e){return a.find(this.views,function(t){return t.model.id===e})},viewFromModel:function(e){return e?this.viewFromModelId(e.id):void 0},viewsWhereModel:function(e){return this.views.filter(function(t){return a.isMatch(t.model.attributes,e)})},viewRange:function(e,t){if(e===t)return e?[e]:[];var i=this.views.indexOf(e),n=this.views.indexOf(t);return-1===i||-1===n?i===n?[]:-1===i?[t]:[e]:n>i?this.views.slice(i,n+1):this.views.slice(n,i+1)},_renderSearch:function(e){return e.find(".controls .search-input").searchInput({placeholder:this.searchPlaceholder,initialVal:this.searchFor,onfirstsearch:a.bind(this._firstSearch,this),onsearch:a.bind(this.searchItems,this),onclear:a.bind(this.clearSearch,this)}),e},_firstSearch:function(e){return this.log("onFirstSearch",e),this.searchItems(e)},searchItems:function(e,t){if(this.log("searchItems",e,this.searchFor,t),!t&&this.searchFor===e)return this;this.searchFor=e,this.renderItems(),this.trigger("search:searching",e,this);var i=this.$("> .controls .search-query");return i.val()!==e&&i.val(e),this},clearSearch:function(e){return this.searchFor="",this.trigger("search:clear",this),this.$("> .controls .search-query").val(""),this.renderItems(),this},THROTTLE_SELECTOR_FX_AT:20,showSelectors:function(e){e=void 0!==e?e:this.fxSpeed,this.selecting=!0,this.$(".list-actions").slideDown(e),e=this.views.length>=this.THROTTLE_SELECTOR_FX_AT?0:e,a.each(this.views,function(t){t.showSelector(e)})},hideSelectors:function(e){e=void 0!==e?e:this.fxSpeed,this.selecting=!1,this.$(".list-actions").slideUp(e),e=this.views.length>=this.THROTTLE_SELECTOR_FX_AT?0:e,a.each(this.views,function(t){t.hideSelector(e)}),this.selected=[],this.lastSelected=null},toggleSelectors:function(){this.selecting?this.hideSelectors():this.showSelectors()},selectAll:function(e){a.each(this.views,function(t){t.select(e)})},deselectAll:function(e){this.lastSelected=null,a.each(this.views,function(t){t.deselect(e)})},selectRange:function(e,t){var i=this.viewRange(e,t);return a.each(i,function(e){e.select()}),i},getSelectedViews:function(){return a.filter(this.views,function(e){return e.selected})},getSelectedModels:function(){return new this.collection.constructor(a.map(this.getSelectedViews(),function(e){return e.model}))},_showLoadingIndicator:function(e,i,n){this.debug("_showLoadingIndicator",this.indicator,e,i,n),i=void 0!==i?i:this.fxSpeed,this.indicator||(this.indicator=new t(this.$el),this.debug(" created",this.indicator)),this.$el.is(":visible")?(this.$el.fadeOut(i),this.indicator.show(e,i,n)):this.indicator.show(0,n)},_hideLoadingIndicator:function(e,t){this.debug("_hideLoadingIndicator",this.indicator,e,t),e=void 0!==e?e:this.fxSpeed,this.indicator&&this.indicator.hide(e,t)},scrollPosition:function(){return this.$scrollContainer().scrollTop()},scrollTo:function(e,t){return t=t||0,this.$scrollContainer().animate({scrollTop:e},t),this},scrollToTop:function(e){return this.scrollTo(0,e)},scrollToItem:function(e,t){return e?this:this},scrollToId:function(e,t){return this.scrollToItem(this.viewFromModelId(e),t)},events:{"click .select-all":"selectAll","click .deselect-all":"deselectAll"},toString:function(){return"ListPanel("+this.collection+")"}});l.prototype.templates=function(){var e=i.wrapTemplate(["
    ",'
    ','
    ','
    ',"
    "]),t=i.wrapTemplate(['
    ','
    ','
    <%- view.title %>
    ',"
    ",'
    <%- view.subtitle %>
    ','
    ','
    ','",'
    ','
    ','",'","
    ",'
    ',"
    ","
    ","
    "]);return{el:e,controls:t}}();var c=l.extend({modelCollectionKey:"contents",initialize:function(e){l.prototype.initialize.call(this,e),this.selecting=void 0!==e.selecting?e.selecting:!1,this.setModel(this.model,e)},setModel:function(e,t){if(t=t||{},this.debug(this+".setModel:",e,t),this.freeModel(),this.freeViews(),e){var i=this.model?this.model.get("id"):null;this.model=e,this.logger&&(this.model.logger=this.logger),this._setUpModelListeners(),this.stopListening(this.collection),this.collection=this.model[this.modelCollectionKey]||t.collection||this._createDefaultCollection(),this._setUpCollectionListeners(),i&&e.get("id")!==i&&this.trigger("new-model",this)}return this},freeModel:function(){return this.model&&this.stopListening(this.model),this},_setUpModelListeners:function(){return this.log(this+"._setUpModelListeners",this.model),this.listenTo(this.model,"error",function(){var e=Array.prototype.slice.call(arguments,0);e.unshift("error"),this.trigger.apply(this,e)},this),this.logger&&this.listenTo(this.model,"all",function(e){this.info(this+"(model)",e,arguments)}),this},_renderControls:function(e){this.debug(this+"(ModelListPanel)._renderControls");var t=this.model?this.model.toJSON():{},i=r(this.templates.controls(t,this));return e.find(".controls").replaceWith(i),i},toString:function(){return"ModelListPanel("+this.model+")"}});return c.prototype.templates=function(){var e=i.wrapTemplate(['
    ','
    ','
    <%- model.name %>
    ',"
    ",'
    <%- view.subtitle %>
    ','
    ','
    ','",'
    ','
    ','",'","
    ",'
    ',"
    ","
    ","
    "]);return a.extend(a.clone(l.prototype.templates),{controls:e})}(),{ListPanel:l,ModelListPanel:c}}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(3),i(2),i(1))},function(e,t,i){var n,s;(function(o,a){n=[i(6),i(5)],s=function(e,t){var i=o.View.extend(e.LoggableMixin).extend(e.HiddenUntilActivatedViewMixin).extend({tagName:"div",className:"tags-display",initialize:function(e){this.listenTo(this.model,"change:tags",function(){this.render()}),this.hiddenUntilActivated(e.$activator,e)},render:function(){var e=this;return this.$el.html(this._template()),this.$input().select2({placeholder:"Add tags",width:"100%",tags:function(){return e._getTagsUsed()}}),this._setUpBehaviors(),this},_template:function(){return['",''].join("")},tagsToCSV:function(){var e=this.model.get("tags");return!a.isArray(e)||a.isEmpty(e)?"":e.map(function(e){return a.escape(e)}).sort().join(",")},$input:function(){return this.$el.find("input.tags-input")},_getTagsUsed:function(){return Galaxy.user.get("tags_used")},_setUpBehaviors:function(){var e=this;this.$input().on("change",function(t){e.model.save({tags:t.val},{silent:!0}),t.added&&e._addNewTagToTagsUsed(t.added.text+"")})},_addNewTagToTagsUsed:function(e){var t=Galaxy.user.get("tags_used");a.contains(t,e)||(t.push(e),t.sort(),Galaxy.user.set("tags_used",t))},remove:function(){this.$input.off(),this.stopListening(this.model),o.View.prototype.remove.call(this)},toString:function(){return["TagsEditor(",this.model+"",")"].join("")}});return{TagsEditor:i}}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(3),i(2))},function(e,t,i){var n,s;(function(o,a){n=[i(5)],s=function(e){"use strict";function t(t,i,n){return Galaxy.modal.show({title:i,body:t,closing_events:!0,buttons:{Ok:function(){Galaxy.modal.hide()}}}),Galaxy.modal.$el.addClass("error-modal"),n&&(Galaxy.modal.$(".error-details").add(Galaxy.modal.$('button:contains("Details")')).remove(),o("
    ").addClass("error-details").hide().appendTo(Galaxy.modal.$(".modal-content")).append([o("

    ").text(h),o("

    ").text(JSON.stringify(n,null,"  "))]),o('").appendTo(Galaxy.modal.$(".buttons")).click(function(){Galaxy.modal.$(".error-details").toggle()})),Galaxy.modal}function i(i,n,s){if(i){if(i=e(i),n=e(n)||e("Error:"),window.Galaxy&&Galaxy.modal)return t(i,n,s);alert(n+"\n\n"+i),console.log("error details:",JSON.stringify(s))}}function n(){return i(e("You appear to be offline. Please check your connection and try again."),e("Offline?"))}function s(){return i(e("Galaxy is currently unreachable. Please try again in a few minutes.")+" "+c,e("Cannot connect to Galaxy"))}function r(t,n,s,o,a){o=o||d,o+=" "+c,a=a||e("An error occurred");var r=l(t,n,s);return i(o,a,r)}function l(e,t,i){return{raven:a.result(window.Raven,"lastEventId"),userAgent:navigator.userAgent,onLine:navigator.onLine,version:a.result(Galaxy.config,"version_major"),xhr:a.omit(t,a.functions(t)),options:a.omit(i,"xhr"),url:a.result(Galaxy.lastAjax,"url"),data:a.result(Galaxy.lastAjax,"data"),model:a.result(e,"toJSON",e+""),user:a.omit(a.result(Galaxy.user,"toJSON"),"email")}}var c=e("Please contact a Galaxy administrator if the problem persists."),d=e("An error occurred while updating information with the server."),h=e("The following information can assist the developers in finding the source of the error:");return{errorModal:i,offlineErrorModal:n,badGatewayErrorModal:s,ajaxErrorModal:r}}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(1),i(2))},function(e,t,i){var n,s;(function(i,o,a,r){n=[],s=function(){var e=i.View.extend({initialize:function(e,t){this.$button=e,this.$button.length||(this.$button=o("
    ")),this.options=t||[],this.$button.data("popupmenu",this);var i=this;this.$button.click(function(e){return o(".popmenu-wrapper").remove(),i._renderAndShow(e),!1})},_renderAndShow:function(e){this.render(),this.$el.appendTo("body").css(this._getShownPosition(e)).show(),this._setUpCloseBehavior()},render:function(){if(this.$el.addClass("popmenu-wrapper").hide().css({position:"absolute"}).html(this.template(this.$button.attr("id"),this.options)),this.options.length){var e=this;this.$el.find("li").each(function(t,i){var n=e.options[t];n.func&&o(this).children("a.popupmenu-option").click(function(t){n.func.call(e,t,n),t.preventDefault()})})}return this},template:function(e,t){return['"].join("")},_templateOptions:function(e){return e.length?a.map(e,function(e){if(e.divider)return'
  • ';if(e.header)return['
  • ',e.html,"
  • "].join("");var t=e.href||"javascript:void(0);",i=e.target?' target="'+e.target+'"':"",n=e.checked?'':"";return['
  • ",n,e.html,"
  • "].join("")}).join(""):"
  • (no options)
  • "},_getShownPosition:function(e){var t=this.$el.width(),i=e.pageX-t/2;return i=Math.min(i,o(document).scrollLeft()+o(window).width()-t-5),i=Math.max(i,o(document).scrollLeft()+5),{top:e.pageY,left:i}},_setUpCloseBehavior:function(){function e(e){if(o(document).off("click.close_popup"),window&&window.parent!==window)try{o(window.parent.document).off("click.close_popup")}catch(i){}else try{o("iframe#galaxy_main").contents().off("click.close_popup")}catch(i){}t.remove()}var t=this;if(o("html").one("click.close_popup",e),window&&window.parent!==window)try{o(window.parent.document).find("html").one("click.close_popup",e)}catch(i){}else try{o("iframe#galaxy_main").contents().one("click.close_popup",e)}catch(i){}},addItem:function(e,t){return t=t>=0?t:this.options.length,this.options.splice(t,0,e),this},removeItem:function(e){return e>=0&&this.options.splice(e,1),this},findIndexByHtml:function(e){for(var t=0;t0){this.$(".upload-ftp-content").html(a(this._templateTable()));var i=0;for(index in t)this.rows.push(this._add(t[index])),i+=t[index].size;if(this.$(".upload-ftp-number").html(t.length+" files"),this.$(".upload-ftp-disk").html(e.bytesToString(i,!0)),this.collection){var n=this;this.$("._has_collection").show(),this.$select_all=this.$(".upload-selectall").addClass(this.options.class_add),this.$select_all.on("click",function(){var e=n.$select_all.hasClass(n.options.class_add);for(index in t){var i=t[index],s=n._find(i);(!s&&e||s&&!e)&&n.rows[index].trigger("click")}}),this._refresh()}}else this.$(".upload-ftp-content").html(a(this._templateInfo()));this.$(".upload-ftp-wait").hide()},_add:function(e){var t=this,i=a(this._templateRow(e)),n=i.find(".icon");return this.$("tbody").append(i),this.collection?(n.addClass(this._find(e)?this.options.class_remove:this.options.class_add),i.on("click",function(){var i=t._find(e);n.removeClass(),i?(t.options.onremove(i),n.addClass(t.options.class_add)):(t.options.onadd(e),n.addClass(t.options.class_remove)),t._refresh()})):i.on("click",function(){t.options.onchange(e)}),i},_refresh:function(){var e=this.collection.where({file_mode:"ftp",enabled:!0});this.$select_all.removeClass(),0==e.length?this.$select_all.addClass(this.options.class_add):this.$select_all.addClass(e.length==this.rows.length?this.options.class_remove:this.options.class_partial)},_find:function(e){var t=this.collection.findWhere({file_path:e.path,file_mode:"ftp",enabled:!0});return t&&t.get("id")},_templateRow:function(t){return'
    '+t.path+''+e.bytesToString(t.size)+''+t.ctime+""},_templateTable:function(){return'Available files:   
    NameSizeCreated
    '},_templateInfo:function(){return'
    Your FTP directory does not contain any files.
    '},_template:function(){return'
    This Galaxy server allows you to upload files via FTP. To upload some files, log in to the FTP server at '+this.options.ftp_upload_site+' using your Galaxy credentials (email address and password).
    '}})}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(3),i(1))},function(e,t,i){var n,s;(function(o,a,r){n=[i(4)],s=function(e){return o.View.extend({options:{class_check:"fa-check-square-o",class_uncheck:"fa-square-o",parameters:[{id:"space_to_tab",title:"Convert spaces to tabs"},{id:"to_posix_lines",title:"Use POSIX standard"}]},initialize:function(e){this.model=e.model,this.setElement(a("
    ").addClass("upload-settings")),this.$el.append(a("
    ").addClass("upload-settings-cover")),this.$el.append(a("").addClass("upload-settings-table ui-table-striped").append("")),this.$cover=this.$(".upload-settings-cover"),this.$table=this.$(".upload-settings-table > tbody"),this.listenTo(this.model,"change",this.render,this),this.model.trigger("change")},render:function(){var e=this;this.$table.empty(),r.each(this.options.parameters,function(t){var i=a("
    ").addClass("upload-"+t.id+" upload-icon-button fa").addClass(e.model.get(t.id)&&e.options.class_check||e.options.class_uncheck).on("click",function(){e.model.get("enabled")&&e.model.set(t.id,!e.model.get(t.id))});e.$table.append(a("
    ").append(a("");t.append(e),this.row.append(t)},appendHeader:function(){this.$thead.append(this.row),this.row=a("")},add:function(e,t,i){var n=a("");t&&n.css("width",t),i&&n.css("text-align",i),n.append(e),this.row.append(n)},append:function(e,t){this._commit(e,t,!1)},prepend:function(e,t){this._commit(e,t,!0)},get:function(e){return this.$el.find("#"+e)},del:function(e){var t=this.$tbody.find("#"+e);t.length>0&&(t.remove(),this.row_count--,this._refresh())},delAll:function(){this.$tbody.empty(),this.row_count=0,this._refresh()},value:function(e){this.before=this.$tbody.find(".current").attr("id"),void 0!==e&&(this.$tbody.find("tr").removeClass("current"),e&&this.$tbody.find("#"+e).addClass("current"));var t=this.$tbody.find(".current").attr("id");return void 0===t?null:(t!=this.before&&this.options.onchange&&this.options.onchange(e),t)},size:function(){return this.$tbody.find("tr").length},_commit:function(e,t,i){this.del(e),this.row.attr("id",e),i?this.$tbody.prepend(this.row):this.$tbody.append(this.row),t&&(this.row.hide(),this.row.fadeIn()),this.row=this._row(),this.row_count++,this._refresh()},_row:function(){return a('')},_onclick:function(e){var t=this.value(),i=a(e.target).closest("tr").attr("id");""!=i&&i&&t!=i&&(this.options.onconfirm?this.options.onconfirm(i):this.value(i))},_ondblclick:function(e){var t=this.value();t&&this.options.ondblclick&&this.options.ondblclick(t)},_refresh:function(){0==this.row_count?this.$tmessage.show():this.$tmessage.hide()},_template:function(e){return'
    ").append(i)).append(a("").append(t.title)))}),this.$cover[this.model.get("enabled")&&"hide"||"show"]()}})}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(3),i(1),i(2))},,,function(e,t,i){var n,s,o;(function(i,a){!function(i){s=[],n=i,o="function"==typeof n?n.apply(t,s):n,!(void 0!==o&&(e.exports=o))}(function(){i.fn.extend({hoverhighlight:function(e,t){return e=e||"body",this.length?(a(this).each(function(){var i=a(this),n=i.data("target");n&&i.mouseover(function(i){a(n,e).css({background:t})}).mouseout(function(e){a(n).css({background:""})})}),this):this}})})}).call(t,i(1),i(1))},function(e,t,i){var n,s,o;(function(i,a){!function(i){s=[],n=i,o="function"==typeof n?n.apply(t,s):n,!(void 0!==o&&(e.exports=o))}(function(){function e(e,n){function s(e){var t=i(this).parent().children("input");t.val("").trigger("searchInput.clear").blur(),n.onclear()}function o(e,t){return t?(i(this).trigger("search.search",t),void("function"==typeof n.onfirstsearch&&f?(f=!1,n.onfirstsearch(t)):n.onsearch(t))):s()}function r(){return['"].join("")}function l(){return i(r()).focus(function(e){i(this).select()}).keyup(function(e){if(e.preventDefault(),e.stopPropagation(),e.which===u&&n.escWillClear)s.call(this,e);else{var t=i(this).val();(e.which===p||n.minSearchLen&&t.length>=n.minSearchLen)&&o.call(this,e,t)}}).val(n.initialVal)}function c(){return i([''].join("")).tooltip({placement:"bottom"}).click(function(e){s.call(this,e)})}function d(){return i([''].join("")).hide().tooltip({placement:"bottom"})}function h(){g.find(".search-loading").toggle(),g.find(".search-clear").toggle()}var u=27,p=13,g=i(e),f=!0,m={initialVal:"",name:"search",placeholder:"search",classes:"",onclear:function(){},onfirstsearch:null,onsearch:function(e){},minSearchLen:0,escWillClear:!0,oninit:function(){}};return"string"===a.type(n)?("toggle-loading"===n&&h(),g):("object"===a.type(n)&&(n=a.extend(!0,{},m,n)),g.addClass("search-input").prepend([l(),c(),d()]))}var t=window._l||function(e){return e};a.fn.extend({searchInput:function(t){return this.each(function(){return e(this,t)})}})})}).call(t,i(1),i(1))},,function(e,t,i){var n,s;n=[],s=function(){function e(e,t){var i=/(-?[0-9\.]+)/g,n=e.toString().toLowerCase()||"",s=t.toString().toLowerCase()||"",o=String.fromCharCode(0),a=n.replace(i,o+"$1"+o).split(o),r=s.replace(i,o+"$1"+o).split(o),l=new Date(n).getTime(),c=l?new Date(s).getTime():null;if(c){if(c>l)return-1;if(l>c)return 1}for(var d,h,u=0,p=Math.max(a.length,r.length);p>u;u++){if(d=parseFloat(a[u])||a[u],h=parseFloat(r[u])||r[u],h>d)return-1;if(d>h)return 1}return 0}return e}.apply(t,n),!(void 0!==s&&(e.exports=s))},function(e,t,i){(function(e,t){!function(i){e.event.props.push("dataTransfer"),i.uploadpost=function(t){var n=i.extend({},{data:{},success:function(){},error:function(){},progress:function(){},url:null,maxfilesize:2048,error_filesize:"File exceeds 2GB. Please use a FTP client.",error_default:"Please make sure the file is available.",error_server:"Upload request failed.",error_login:"Uploads require you to log in."},t),s=n.data;if(s.error_message)return void n.error(s.error_message);var o=new FormData;for(var a in s.payload)o.append(a,s.payload[a]);var r=0;for(var a in s.files){var l=s.files[a];o.append(l.name,l.file,l.file.name),r+=l.file.size}return r>1048576*n.maxfilesize?void n.error(n.error_filesize):(xhr=new XMLHttpRequest,xhr.open("POST",n.url,!0),xhr.setRequestHeader("Accept","application/json"),xhr.setRequestHeader("Cache-Control","no-cache"),xhr.setRequestHeader("X-Requested-With","XMLHttpRequest"),xhr.onreadystatechange=function(){if(xhr.readyState==xhr.DONE){var t=null;if(xhr.responseText)try{t=e.parseJSON(xhr.responseText)}catch(i){t=xhr.responseText}if(xhr.status<200||xhr.status>299){var s=xhr.statusText;403==xhr.status?s=n.error_login:0==xhr.status?s=n.error_server:s||(s=n.error_default),n.error(s+" ("+xhr.status+")")}else n.success(t)}},xhr.upload.addEventListener("progress",function(e){e.lengthComputable&&n.progress(Math.round(100*e.loaded/e.total))},!1),Galaxy.emit.debug("uploadbox::uploadpost()","Posting following data.",n),void xhr.send(o))},i.fn.uploadinput=function(e){var t=this,n=i.extend({},{ondragover:function(){},ondragleave:function(){},onchange:function(){},multiple:!1},e),s=i('");return t.append(s.change(function(e){n.onchange(e.target.files),i(this).val("")})),t.on("drop",function(e){n.ondragleave(e),e.dataTransfer&&(n.onchange(e.dataTransfer.files),e.preventDefault())}),t.on("dragover",function(e){e.preventDefault(),n.ondragover(e)}),t.on("dragleave",function(e){e.stopPropagation(),n.ondragleave(e)}),{dialog:function(){s.trigger("click")}}},i.fn.uploadbox=function(e){function n(e){if(e&&e.length&&!m){var i=g;return t.each(e,function(e,i){"new"!==e.mode&&t.filter(p,function(t){return t.name===e.name&&t.size===e.size}).length&&(e.duplicate=!0)}),t.each(e,function(e){if(!e.duplicate){var t=String(g++);p[t]=e,u.announce(t,p[t]),f++}}),i}}function s(e){p[e]&&(delete p[e],f--)}function o(){if(0==f||v)return v=!1,m=!1,void u.complete();m=!0;var e=-1;for(var t in p){e=t;break}p[e];s(e),i.uploadpost({url:u.url,data:u.initialize(e),success:function(t){u.success(e,t),o()},error:function(t){u.error(e,t),o()},progress:function(t){u.progress(e,t)}})}function a(){_.dialog()}function r(e){for(e in p)s(e)}function l(){m||(m=!0,o())}function c(){v=!0}function d(e){return u=i.extend({},u,e)}function h(){return window.File&&window.FormData&&window.XMLHttpRequest&&window.FileList}var u=i.extend({},{dragover:function(){},dragleave:function(){},announce:function(e){},initialize:function(e){},progress:function(e,t){},success:function(e,t){},error:function(e,t){alert(t)},complete:function(){}},e),p={},g=0,f=0,m=!1,v=!1,_=i(this).uploadinput({multiple:!0,onchange:function(e){n(e)},ondragover:e.ondragover,ondragleave:e.ondragleave});return{select:a,add:n,remove:s,start:l,stop:c,reset:r,configure:d,compatible:h}}}(e)}).call(t,i(1),i(2))},function(e,t,i){(function(t){var n=i(10).RightPanel,s=(i(7),i(116));CurrentHistoryView=i(113).CurrentHistoryView,_l=i(5);var o=n.extend({title:_l("History"),initialize:function(e){n.prototype.initialize.call(this,e),this.options=t.pick(e,"userIsAnonymous","allow_user_dataset_purge","galaxyRoot"),this.historyView=new CurrentHistoryView({className:CurrentHistoryView.prototype.className+" middle",purgeAllowed:e.allow_user_dataset_purge,linkTarget:"galaxy_main"})},$toggleButton:function(){return this.$(".footer > .panel-collapse")},render:function(){n.prototype.render.call(this),this.optionsMenu=s(this.$("#history-options-button"),{anonymous:this.options.userIsAnonymous,purgeAllowed:this.options.allow_user_dataset_purge,root:this.options.galaxyRoot}),this.$("> .header .buttons [title]").tooltip({placement:"bottom"}),this.historyView.setElement(this.$(".history-panel")),this.$el.attr("class","history-right-panel")},_templateHeader:function(e){var i=this.options.galaxyRoot+"history",n=this.options.galaxyRoot+"history/view_multiple";return['
    ','
    ','','',this.options.userIsAnonymous?"":[''].join(""),"
    ",'
    ',t.escape(this.title),"
    ","
    "].join("")},_templateBody:function(e){return['
    '].join("")},_templateFooter:function(e){return['
    You can tell Galaxy to download data from web by entering URL in this box (one per line). You can also directly paste the contents of a file.
    ',"
    ","
    "].join("")}});return{CitationView:n,CitationListView:s}}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(3))},function(e,t,i){var n,s;(function(o,a,r){n=[i(42),i(32),i(6),i(5)],s=function(e,t,i,n){"use strict";var s=e.FoldoutListItemView,l=e.ListItemView,c=s.extend({className:s.prototype.className+" dataset-collection",id:function(){return["dataset_collection",this.model.get("id")].join("-")},initialize:function(e){this.linkTarget=e.linkTarget||"_blank",this.hasUser=e.hasUser,s.prototype.initialize.call(this,e)},_setUpListeners:function(){s.prototype._setUpListeners.call(this),this.listenTo(this.model,"change",function(e,t){o.has(e.changed,"deleted")?this.render():o.has(e.changed,"element_count")&&this.$("> .title-bar .subtitle").replaceWith(this._renderSubtitle())})},_renderSubtitle:function(){return a(this.templates.subtitle(this.model.toJSON(),this))},_getFoldoutPanelOptions:function(){var e=s.prototype._getFoldoutPanelOptions.call(this);return o.extend(e,{linkTarget:this.linkTarget,hasUser:this.hasUser})},$selector:function(){return this.$("> .selector")},toString:function(){var e=this.model?this.model+"":"(no model)";return"DCListItemView("+e+")"}});c.prototype.templates=function(){var e=o.extend({},s.prototype.templates.warnings,{error:i.wrapTemplate(["<% if( model.error ){ %>",'
    ',n("There was an error getting the data for this collection"),": <%- model.error %>","
    ","<% } %>"]),purged:i.wrapTemplate(["<% if( model.purged ){ %>",'
    ',n("This collection has been deleted and removed from disk"),"
    ","<% } %>"]),deleted:i.wrapTemplate(["<% if( model.deleted && !model.purged ){ %>",'
    ',n("This collection has been deleted"),"
    ","<% } %>"])}),t=i.wrapTemplate(['
    ','
    ','<%- collection.element_identifier || collection.name %>',"
    ",'
    ',"
    "],"collection"),a=i.wrapTemplate(['
    ','<% var countText = collection.element_count? ( collection.element_count + " " ) : ""; %>','<% if( collection.collection_type === "list" ){ %>',n("a list of <%- countText %>datasets"),'<% } else if( collection.collection_type === "paired" ){ %>',n("a pair of datasets"),'<% } else if( collection.collection_type === "list:paired" ){ %>',n("a list of <%- countText %>dataset pairs"),'<% } else if( collection.collection_type === "list:list" ){ %>',n("a list of <%- countText %>dataset lists"),"<% } %>","
    "],"collection");return o.extend({},s.prototype.templates,{warnings:e,titleBar:t,subtitle:a})}();var d=l.extend({className:l.prototype.className+" dataset-collection-element",initialize:function(e){e.logger&&(this.logger=this.model.logger=e.logger),this.log("DCEListItemView.initialize:",e),l.prototype.initialize.call(this,e)},toString:function(){var e=this.model?this.model+"":"(no model)";return"DCEListItemView("+e+")"}});d.prototype.templates=function(){var e=i.wrapTemplate(['
    ','
    ','<%- element.element_identifier %>',"
    ",'
    ',"
    "],"element");return o.extend({},l.prototype.templates,{titleBar:e})}();var h=t.DatasetListItemView.extend({className:t.DatasetListItemView.prototype.className+" dataset-collection-element",initialize:function(e){e.logger&&(this.logger=this.model.logger=e.logger),this.log("DatasetDCEListItemView.initialize:",e),t.DatasetListItemView.prototype.initialize.call(this,e)},_fetchModelDetails:function(){var e=this;return e.model.inReadyState()&&!e.model.hasDetails()?e.model.fetch({silent:!0}):r.when()},toString:function(){var e=this.model?this.model+"":"(no model)";return"DatasetDCEListItemView("+e+")"}});h.prototype.templates=function(){var e=i.wrapTemplate(['
    ','','
    ','<%- element.element_identifier %>',"
    ","
    "],"element");return o.extend({},t.DatasetListItemView.prototype.templates,{titleBar:e})}();var u=c.extend({className:c.prototype.className+" dataset-collection-element",_swapNewRender:function(e){c.prototype._swapNewRender.call(this,e);var t=this.model.get("state")||"ok";return this.$el.addClass("state-"+t),this.$el},toString:function(){var e=this.model?this.model+"":"(no model)";return"NestedDCDCEListItemView("+e+")"}});return{DCListItemView:c,DCEListItemView:d,DatasetDCEListItemView:h,NestedDCDCEListItemView:u}}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(2),i(1),i(1))},function(e,t,i){var n,s;(function(o,a,r){n=[i(70),i(6),i(5)],s=function(e,t,i){"use strict";var n={defaults:{model_class:"DatasetCollectionElement",element_identifier:null,element_index:null,element_type:null},_mergeObject:function(e){return o.extend(e,e.object,{element_id:e.id}),delete e.object,e},constructor:function(e,t){e=this._mergeObject(e),this.idAttribute="element_id",a.Model.apply(this,arguments)},parse:function(e,t){var i=e;return i=this._mergeObject(i)}},s=a.Model.extend(t.LoggableMixin).extend(n).extend({_logNamespace:"collections"}),l=a.Collection.extend(t.LoggableMixin).extend({_logNamespace:"collections",model:s,toString:function(){return["DatasetCollectionElementCollection(",this.length,")"].join("")}}),c=e.DatasetAssociation.extend(t.mixin(n,{url:function(){return this.has("history_id")?Galaxy.root+"api/histories/"+this.get("history_id")+"/contents/"+this.get("id"):(console.warn("no endpoint for non-hdas within a collection yet"),Galaxy.root+"api/datasets")},defaults:o.extend({},e.DatasetAssociation.prototype.defaults,n.defaults),constructor:function(e,t){this.debug("\t DatasetDCE.constructor:",e,t),n.constructor.call(this,e,t)},hasDetails:function(){return this.elements&&this.elements.length},toString:function(){var e=this.get("element_identifier");return["DatasetDCE(",e,")"].join("")}})),d=l.extend({model:c,toString:function(){return["DatasetDCECollection(",this.length,")"].join("")}}),h=a.Model.extend(t.LoggableMixin).extend(t.SearchableModelMixin).extend({_logNamespace:"collections",defaults:{collection_type:null,deleted:!1},collectionClass:l,initialize:function(e,t){this.debug(this+"(DatasetCollection).initialize:",e,t,this),this.elements=this._createElementsModel(),this.on("change:elements",function(){this.log("change:elements"),this.elements=this._createElementsModel()})},_createElementsModel:function(){this.debug(this+"._createElementsModel",this.collectionClass,this.get("elements"),this.elements);var e=this.get("elements")||[];return this.unset("elements",{silent:!0}),this.elements=new this.collectionClass(e),this.elements},toJSON:function(){var e=a.Model.prototype.toJSON.call(this);return this.elements&&(e.elements=this.elements.toJSON()),e},inReadyState:function(){var e=this.get("populated");return this.isDeletedOrPurged()||e},hasDetails:function(){return 0!==this.elements.length},getVisibleContents:function(e){return this.elements},parse:function(e,t){var i=a.Model.prototype.parse.call(this,e,t);return i.create_time&&(i.create_time=new Date(i.create_time)),i.update_time&&(i.update_time=new Date(i.update_time)),i},"delete":function(e){return this.get("deleted")?r.when():this.save({deleted:!0},e)},undelete:function(e){return!this.get("deleted")||this.get("purged")?r.when():this.save({deleted:!1},e)},isDeletedOrPurged:function(){return this.get("deleted")||this.get("purged")},searchAttributes:["name"],toString:function(){var e=[this.get("id"),this.get("name")||this.get("element_identifier")];return"DatasetCollection("+e.join(",")+")"}}),u=h.extend({collectionClass:d,toString:function(){return"List"+h.prototype.toString.call(this)}}),p=u.extend({toString:function(){return"Pair"+h.prototype.toString.call(this)}}),g=h.extend(t.mixin(n,{constructor:function(e,t){this.debug("\t NestedDCDCE.constructor:",e,t),n.constructor.call(this,e,t)},toString:function(){var e=this.object?""+this.object:this.get("element_identifier");return["NestedDCDCE(",e,")"].join("")}})),f=l.extend({model:g,toString:function(){return["NestedDCDCECollection(",this.length,")"].join("")}}),m=p.extend(t.mixin(n,{constructor:function(e,t){this.debug("\t NestedPairDCDCE.constructor:",e,t),n.constructor.call(this,e,t)},toString:function(){var e=this.object?""+this.object:this.get("element_identifier");return["NestedPairDCDCE(",e,")"].join("")}})),v=f.extend({model:m,toString:function(){return["NestedPairDCDCECollection(",this.length,")"].join("")}}),_=h.extend({collectionClass:v,toString:function(){return["ListPairedDatasetCollection(",this.get("name"),")"].join("")}}),y=u.extend(t.mixin(n,{constructor:function(e,t){this.debug("\t NestedListDCDCE.constructor:",e,t),n.constructor.call(this,e,t)},toString:function(){var e=this.object?""+this.object:this.get("element_identifier");return["NestedListDCDCE(",e,")"].join("")}})),w=f.extend({model:y,toString:function(){return["NestedListDCDCECollection(",this.length,")"].join("")}}),b=h.extend({collectionClass:w,toString:function(){return["ListOfListsDatasetCollection(",this.get("name"),")"].join("")}});return{ListDatasetCollection:u,PairDatasetCollection:p,ListPairedDatasetCollection:_,ListOfListsDatasetCollection:b}}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(2),i(3),i(1))},function(e,t,i){var n,s;(function(o,a,r,l){n=[i(39),i(12),i(6),i(8),i(87),i(5),i(84)],s=function(e,t,i,n,s,c){"use strict";function d(e){var t=e.toJSON(),i=f(t,{creationFn:function(t,i){return t=t.map(function(e){return{id:e.id,name:e.name,src:"dataset"===e.history_content_type?"hda":"hdca"}}),e.createHDCA(t,"list",i)}});return i}var h="collections",u=o.View.extend(i.LoggableMixin).extend({_logNamespace:h,tagName:"li",className:"collection-element",initialize:function(e){this.element=e.element||{},this.selected=e.selected||!1},render:function(){return this.$el.attr("data-element-id",this.element.id).attr("draggable",!0).html(this.template({element:this.element})),this.selected&&this.$el.addClass("selected"),this},template:a.template(['',"<%- element.name %>","",'"].join("")),select:function(e){this.$el.toggleClass("selected",e),this.trigger("select",{source:this,selected:this.$el.hasClass("selected")})},discard:function(){var e=this,t=this.$el.parent().width();this.$el.animate({"margin-right":t},"fast",function(){e.trigger("discard",{source:e}),e.destroy()})},destroy:function(){this.off(),this.$el.remove()},events:{click:"_click","click .name":"_clickName","click .discard":"_clickDiscard",dragstart:"_dragstart",dragend:"_dragend",dragover:"_sendToParent",drop:"_sendToParent"},_click:function(e){e.stopPropagation(),this.select(e)},_clickName:function(e){e.stopPropagation(),e.preventDefault();var t=([c("Enter a new name for the element"),":\n(",c("Note that changing the name here will not rename the dataset"),")"].join(""),prompt(c("Enter a new name for the element")+":",this.element.name));t&&(this.element.name=t,this.render())},_clickDiscard:function(e){e.stopPropagation(),this.discard()},_dragstart:function(e){e.originalEvent&&(e=e.originalEvent),e.dataTransfer.effectAllowed="move",e.dataTransfer.setData("text/plain",JSON.stringify(this.element)),this.$el.addClass("dragging"),this.$el.parent().trigger("collection-element.dragstart",[this])},_dragend:function(e){this.$el.removeClass("dragging"),this.$el.parent().trigger("collection-element.dragend",[this])},_sendToParent:function(e){this.$el.parent().trigger(e)},toString:function(){return"DatasetCollectionElementView()"}}),p=o.View.extend(i.LoggableMixin).extend({_logNamespace:h,elementViewClass:u,collectionClass:e.HistoryListDatasetCollection,className:"list-collection-creator collection-creator flex-row-container",minElements:1,defaultAttributes:{creationFn:function(){throw new TypeError("no creation fn for creator")},oncreate:function(){},oncancel:function(){},autoscrollDist:24,highlightClr:"rgba( 64, 255, 255, 1.0 )"},initialize:function(e){this.metric("ListCollectionCreator.initialize",e);var t=this;a.each(this.defaultAttributes,function(i,n){i=e[n]||i,t[n]=i}),t.initialElements=e.elements||[],this._instanceSetUp(),this._elementsSetUp(),this._setUpBehaviors()},_instanceSetUp:function(){this.selectedIds={},this.$dragging=null,this.blocking=!1},_elementsSetUp:function(){this.invalidElements=[],this.workingElements=[],this.elementViews=[],this.workingElements=this.initialElements.slice(0),this._ensureElementIds(),this._validateElements(),this._mangleDuplicateNames(),this._sortElements()},_ensureElementIds:function(){return this.workingElements.forEach(function(e){e.hasOwnProperty("id")||(e.id=a.uniqueId())}),this.workingElements},_validateElements:function(){var e=this;return e.invalidElements=[],this.workingElements=this.workingElements.filter(function(t){var i=e._isElementInvalid(t);return i&&e.invalidElements.push({element:t,text:i}),!i}),this.workingElements},_isElementInvalid:function(e){return"dataset"!==e.history_content_type?c("is not a dataset"):e.state!==t.OK?c(a.contains(t.NOT_READY_STATES,e.state)?"hasn't finished running yet":"has errored, is paused, or is not accessible"):e.deleted||e.purged?c("has been deleted or purged"):null},_mangleDuplicateNames:function(){var e=900,t=1,i={};this.workingElements.forEach(function(n){for(var s=n.name;i.hasOwnProperty(s);)if(s=n.name+" ("+t+")",t+=1,t>=e)throw new Error("Safety hit in while loop - thats impressive");n.name=s,i[n.name]=!0})},_sortElements:function(e){},render:function(e,t){return this.workingElements.length .clear-selected").show():this.$(".collection-elements-controls > .clear-selected").hide()},_renderList:function(e,t){var i=this,n=l("
    "),s=i.$list();a.each(this.elementViews,function(e){e.destroy(),i.removeElementView(e)}),i.workingElements.forEach(function(e){var t=i._createElementView(e);n.append(t.$el)}),i._renderClearSelected(),s.empty().append(n.children()),a.invoke(i.elementViews,"render"),s.height()>s.css("max-height")?s.css("border-width","1px 0px 1px 0px"):s.css("border-width","0px")},_createElementView:function(e){var t=new this.elementViewClass({element:e,selected:a.has(this.selectedIds,e.id)});return this.elementViews.push(t),this._listenToElementView(t),t},_listenToElementView:function(e){var t=this;t.listenTo(e,{select:function(e){var i=e.source.element;e.selected?t.selectedIds[i.id]=!0:delete t.selectedIds[i.id],t.trigger("elements:select",e)},discard:function(e){t.trigger("elements:discard",e)}})},addElementView:function(e){},removeElementView:function(e){delete this.selectedIds[e.element.id],this._renderClearSelected(),this.elementViews=a.without(this.elementViews,e),this.stopListening(e)},_renderNoElementsLeft:function(){this._disableNameAndCreate(!0),this.$(".collection-elements").append(this.templates.noElementsLeft())},_elementToJSON:function(e){return e},createList:function(e){if(!this.workingElements.length){var t=c("No valid elements for final list")+". ";return t+=''+c("Cancel")+" ",t+=c("or"),t+=' '+c("start over")+".",void this._showAlert(t)}var i=this,n=this.workingElements.map(function(e){return i._elementToJSON(e)});return i.blocking=!0,i.creationFn(n,e).always(function(){i.blocking=!1}).fail(function(e,t,n){i.trigger("error",{xhr:e,status:t,message:c("An error occurred while creating this collection")})}).done(function(e,t,n){i.trigger("collection:created",e,t,n),i.metric("collection:created",e),"function"==typeof i.oncreate&&i.oncreate.call(this,e,t,n)})},_setUpBehaviors:function(){return this.on("error",this._errorHandler),this.once("rendered",function(){this.trigger("rendered:initial",this)}),this.on("elements:select",function(e){this._renderClearSelected()}),this.on("elements:discard",function(e){var t=e.source.element;this.removeElementView(e.source),this.workingElements=a.without(this.workingElements,t),this.workingElements.length||this._renderNoElementsLeft()}),this},_errorHandler:function(e){this.error(e);var t=this;if(content=e.message||c("An error occurred"),e.xhr){var i=e.xhr,n=e.message;0===i.readyState&&0===i.status?content+=": "+c("Galaxy could not be reached and may be updating.")+c(" Try again in a few minutes."):i.responseJSON?content+=":
    "+JSON.stringify(i.responseJSON)+"
    ":content+=": "+n}t._showAlert(content,"alert-danger")},events:{"click .more-help":"_clickMoreHelp","click .less-help":"_clickLessHelp","click .main-help":"_toggleHelp","click .header .alert button":"_hideAlert","click .reset":"reset","click .clear-selected":"clearSelectedElements","click .collection-elements":"clearSelectedElements","dragover .collection-elements":"_dragoverElements","drop .collection-elements":"_dropElements","collection-element.dragstart .collection-elements":"_elementDragstart","collection-element.dragend .collection-elements":"_elementDragend","change .collection-name":"_changeName","keydown .collection-name":"_nameCheckForEnter","click .cancel-create":function(e){"function"==typeof this.oncancel&&this.oncancel.call(this)},"click .create-collection":"_clickCreate"},_clickMoreHelp:function(e){e.stopPropagation(),this.$(".main-help").addClass("expanded"),this.$(".more-help").hide()},_clickLessHelp:function(e){e.stopPropagation(),this.$(".main-help").removeClass("expanded"),this.$(".more-help").show()},_toggleHelp:function(e){e.stopPropagation(),this.$(".main-help").toggleClass("expanded"),this.$(".more-help").toggle()},_showAlert:function(e,t){t=t||"alert-danger",this.$(".main-help").hide(),this.$(".header .alert").attr("class","alert alert-dismissable").addClass(t).show().find(".alert-message").html(e)},_hideAlert:function(e){this.$(".main-help").show(),this.$(".header .alert").hide()},reset:function(){this._instanceSetUp(),this._elementsSetUp(),this.render()},clearSelectedElements:function(e){this.$(".collection-elements .collection-element").removeClass("selected"),this.$(".collection-elements-controls > .clear-selected").hide()},_dragoverElements:function(e){e.preventDefault();var t=this.$list();this._checkForAutoscroll(t,e.originalEvent.clientY);var i=this._getNearestElement(e.originalEvent.clientY);this.$(".element-drop-placeholder").remove();var n=r('
    ');i.length?i.before(n):t.append(n)},_checkForAutoscroll:function(e,t){var i=2,n=e.offset(),s=e.scrollTop(),o=t-n.top,a=n.top+e.outerHeight()-t;o>=0&&o=0&&ae&&o-a
    ','
    ',''].join("")),header:a.template(['",'
    ','','',"
    "].join("")),middle:a.template(['",'
    ',"
    "].join("")),footer:a.template(['
    ','
    ','','
    ',c("Name"),":
    ","
    ","
    ",'
    ','
    ','",'
    ','",'","
    ","
    ",'
    ','","
    ","
    "].join("")),helpContent:a.template(["

    ",c(["Collections of datasets are permanent, ordered lists of datasets that can be passed to tools and ","workflows in order to have analyses done on each member of the entire group. This interface allows ","you to create a collection and re-order the final collection."].join("")),"

    ","
      ","
    • ",c(["Rename elements in the list by clicking on ",'the existing name.'].join("")),"
    • ","
    • ",c(["Discard elements from the final created list by clicking on the ",'"Discard" button.'].join("")),"
    • ","
    • ",c(["Reorder the list by clicking and dragging elements. Select multiple elements by clicking on ",'them and you can then move those selected by dragging the ',"entire group. Deselect them by clicking them again or by clicking the ",'the "Clear selected" link.'].join("")),"
    • ","
    • ",c(['Click the "Start over" link to begin again as if you had just opened ',"the interface."].join("")),"
    • ","
    • ",c(['Click the "Cancel" button to exit the interface.'].join("")),"
    • ","

    ","

    ",c(['Once your collection is complete, enter a name and ','click "Create list".'].join("")),"

    "].join("")),invalidElements:a.template([c("The following selections could not be included due to problems:"),"
      <% _.each( problems, function( problem ){ %>","
    • <%- problem.element.name %>: <%- problem.text %>
    • ","<% }); %>
    "].join("")),noElementsLeft:a.template(['
  • ',c("No elements left! "),c("Would you like to "),'',c("start over"),"?","
  • "].join("")),invalidInitial:a.template(['
    ','
    ','',"<% if( _.size( problems ) ){ %>",c("The following selections could not be included due to problems"),":","
      <% _.each( problems, function( problem ){ %>","
    • <%- problem.element.name %>: <%- problem.text %>
    • ","<% }); %>
    ","<% } else if( _.size( elements ) < 1 ){ %>",c("No datasets were selected"),".","<% } %>","
    ",c("At least one element is needed for the collection"),". ",c("You may need to "),'',c("cancel")," ",c("and reselect new elements"),".","
    ","
    ","
    ",'"].join(""))},toString:function(){return"ListCollectionCreator"}}),g=function(e,t,i){var s,o=l.Deferred(),r=Galaxy.modal||new n.View;return t=a.defaults(t||{},{elements:e,oncancel:function(){r.hide(),o.reject("cancelled")},oncreate:function(e,t){r.hide(),o.resolve(t)}}),s=new i(t),r.show({title:t.title||c("Create a collection"),body:s.$el,width:"80%",height:"100%",closing_events:!0}),s.render(),window._collectionCreator=s,o},f=function(e,t){return t=t||{},t.title=c("Create a collection from a list of datasets"),g(e,t,p)};return{DatasetCollectionElementView:u,ListCollectionCreator:p,collectionCreatorModal:g,listCollectionCreatorModal:f,createListCollection:d}}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(3),i(2),i(1),i(1))},function(e,t,i){var n,s;(function(o,a,r,l){n=[i(42),i(12),i(22),i(6),i(5)],s=function(e,t,i,n,s){"use strict";var c="dataset",d=e.ListItemView,h=d.extend({_logNamespace:c,className:d.prototype.className+" dataset",id:function(){return["dataset",this.model.get("id")].join("-")},initialize:function(e){e.logger&&(this.logger=this.model.logger=e.logger), +this.log(this+".initialize:",e),d.prototype.initialize.call(this,e),this.linkTarget=e.linkTarget||"_blank"},_setUpListeners:function(){d.prototype._setUpListeners.call(this);var e=this;return e.listenTo(e.model,{change:function(t,i){e.model.changedAttributes().state&&e.model.inReadyState()&&e.expanded&&!e.model.hasDetails()?e.model.fetch({silent:!0}).done(function(){e.render()}):e.render()}})},_fetchModelDetails:function(){var e=this;return e.model.inReadyState()&&!e.model.hasDetails()?e.model.fetch({silent:!0}):o.when()},remove:function(e,t){var i=this;e=e||this.fxSpeed,this.$el.fadeOut(e,function(){a.View.prototype.remove.call(i),t&&t.call(i)})},_swapNewRender:function(e){return d.prototype._swapNewRender.call(this,e),this.model.has("state")&&this.$el.addClass("state-"+this.model.get("state")),this.$el},_renderPrimaryActions:function(){return[this._renderDisplayButton()]},_renderDisplayButton:function(){var e=this.model.get("state");if(e===t.NOT_VIEWABLE||e===t.DISCARDED||!this.model.get("accessible"))return null;var n={target:this.linkTarget,classes:"display-btn"};if(this.model.get("purged"))n.disabled=!0,n.title=s("Cannot display datasets removed from disk");else if(e===t.UPLOAD)n.disabled=!0,n.title=s("This dataset must finish uploading before it can be viewed");else if(e===t.NEW)n.disabled=!0,n.title=s("This dataset is not yet viewable");else{n.title=s("View data"),n.href=this.model.urls.display;var o=this;n.onclick=function(e){Galaxy.frame&&Galaxy.frame.active&&(Galaxy.frame.addDataset(o.model.get("id")),e.preventDefault())}}return n.faIcon="fa-eye",i(n)},_renderDetails:function(){if(this.model.get("state")===t.NOT_VIEWABLE)return r(this.templates.noAccess(this.model.toJSON(),this));var e=d.prototype._renderDetails.call(this);return e.find(".actions .left").empty().append(this._renderSecondaryActions()),e.find(".summary").html(this._renderSummary()).prepend(this._renderDetailMessages()),e.find(".display-applications").html(this._renderDisplayApplications()),this._setUpBehaviors(e),e},_renderSummary:function(){var e=this.model.toJSON(),t=this.templates.summaries[e.state];return(t=t||this.templates.summaries.unknown)(e,this)},_renderDetailMessages:function(){var e=this,t=r('
    '),i=e.model.toJSON();return l.each(e.templates.detailMessages,function(n){t.append(r(n(i,e)))}),t},_renderDisplayApplications:function(){return this.model.isDeletedOrPurged()?"":[this.templates.displayApplications(this.model.get("display_apps"),this),this.templates.displayApplications(this.model.get("display_types"),this)].join("")},_renderSecondaryActions:function(){switch(this.debug("_renderSecondaryActions"),this.model.get("state")){case t.NOT_VIEWABLE:return[];case t.OK:case t.FAILED_METADATA:case t.ERROR:return[this._renderDownloadButton(),this._renderShowParamsButton()]}return[this._renderShowParamsButton()]},_renderShowParamsButton:function(){return i({title:s("View details"),classes:"params-btn",href:this.model.urls.show_params,target:this.linkTarget,faIcon:"fa-info-circle",onclick:function(e){Galaxy.frame&&Galaxy.frame.active&&(Galaxy.frame.add({title:"Dataset details",url:this.href}),e.preventDefault(),e.stopPropagation())}})},_renderDownloadButton:function(){return this.model.get("purged")||!this.model.hasData()?null:l.isEmpty(this.model.get("meta_files"))?r(['','',""].join("")):this._renderMetaFileDownloadButton()},_renderMetaFileDownloadButton:function(){var e=this.model.urls;return r(['"].join("\n"))},events:l.extend(l.clone(d.prototype.events),{"click .display-btn":function(e){this.trigger("display",this,e)},"click .params-btn":function(e){this.trigger("params",this,e)},"click .download-btn":function(e){this.trigger("download",this,e)}}),toString:function(){var e=this.model?this.model+"":"(no model)";return"DatasetListItemView("+e+")"}});return h.prototype.templates=function(){var e=l.extend({},d.prototype.templates.warnings,{failed_metadata:n.wrapTemplate(['<% if( model.state === "failed_metadata" ){ %>','
    ',s("An error occurred setting the metadata for this dataset"),"
    ","<% } %>"]),error:n.wrapTemplate(["<% if( model.error ){ %>",'
    ',s("There was an error getting the data for this dataset"),": <%- model.error %>","
    ","<% } %>"]),purged:n.wrapTemplate(["<% if( model.purged ){ %>",'
    ',s("This dataset has been deleted and removed from disk"),"
    ","<% } %>"]),deleted:n.wrapTemplate(["<% if( model.deleted && !model.purged ){ %>",'
    ',s("This dataset has been deleted"),"
    ","<% } %>"])}),i=n.wrapTemplate(['
    ','
    ','
    ','
    ','
    ',"
    ","<% if( !dataset.deleted && !dataset.purged ){ %>",'
    ','
    ','
    ',"<% if( dataset.peek ){ %>",'
    <%= dataset.peek %>
    ',"<% } %>","<% } %>","
    "],"dataset"),o=n.wrapTemplate(['
    ','
    ',s("You do not have permission to view this dataset"),"
    ","
    "],"dataset"),a={};a[t.OK]=a[t.FAILED_METADATA]=n.wrapTemplate(["<% if( dataset.misc_blurb ){ %>",'
    ','<%- dataset.misc_blurb %>',"
    ","<% } %>","<% if( dataset.file_ext ){ %>",'
    ','",'<%- dataset.file_ext %>',"
    ","<% } %>","<% if( dataset.metadata_dbkey ){ %>",'
    ','",'',"<%- dataset.metadata_dbkey %>","","
    ","<% } %>","<% if( dataset.misc_info ){ %>",'
    ','<%- dataset.misc_info %>',"
    ","<% } %>"],"dataset"),a[t.NEW]=n.wrapTemplate(["
    ",s("This is a new dataset and not all of its data are available yet"),"
    "],"dataset"),a[t.NOT_VIEWABLE]=n.wrapTemplate(["
    ",s("You do not have permission to view this dataset"),"
    "],"dataset"),a[t.DISCARDED]=n.wrapTemplate(["
    ",s("The job creating this dataset was cancelled before completion"),"
    "],"dataset"),a[t.QUEUED]=n.wrapTemplate(["
    ",s("This job is waiting to run"),"
    "],"dataset"),a[t.RUNNING]=n.wrapTemplate(["
    ",s("This job is currently running"),"
    "],"dataset"),a[t.UPLOAD]=n.wrapTemplate(["
    ",s("This dataset is currently uploading"),"
    "],"dataset"),a[t.SETTING_METADATA]=n.wrapTemplate(["
    ",s("Metadata is being auto-detected"),"
    "],"dataset"),a[t.PAUSED]=n.wrapTemplate(["
    ",s('This job is paused. Use the "Resume Paused Jobs" in the history menu to resume'),"
    "],"dataset"),a[t.ERROR]=n.wrapTemplate(["<% if( !dataset.purged ){ %>","
    <%- dataset.misc_blurb %>
    ","<% } %>",'',s("An error occurred with this dataset"),":",'
    <%- dataset.misc_info %>
    '],"dataset"),a[t.EMPTY]=n.wrapTemplate(["
    ",s("No data"),": <%- dataset.misc_blurb %>
    "],"dataset"),a.unknown=n.wrapTemplate(['
    Error: unknown dataset state: "<%- dataset.state %>"
    '],"dataset");var r={resubmitted:n.wrapTemplate(["<% if( model.resubmitted ){ %>",'
    ',s("The job creating this dataset has been resubmitted"),"
    ","<% } %>"])},c=n.wrapTemplate(["<% _.each( apps, function( app ){ %>",'
    ','<%- app.label %> ','',"<% _.each( app.links, function( link ){ %>",'',"<% print( _l( link.text ) ); %>"," ","<% }); %>","","
    ","<% }); %>"],"apps");return l.extend({},d.prototype.templates,{warnings:e,details:i,noAccess:o,summaries:a,detailMessages:r,displayApplications:c})}(),{DatasetListItemView:h}}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(1),i(3),i(1),i(2))},function(e,t,i){var n,s;(function(o,a,r){n=[i(4)],s=function(e){var t=o.Model.extend({initialize:function(e){this.app=e},checksum:function(){var e="",t=this;return this.app.section.$el.find(".section-row").each(function(){var i=a(this).attr("id"),n=t.app.field_list[i];n&&(e+=i+":"+JSON.stringify(n.value&&n.value())+":"+n.collapsed+";")}),e},create:function(){function e(e,t,i){n.flat_dict[e]=t,o[e]=i,n.app.element_list[t]&&n.app.element_list[t].$el.attr("tour_id",e)}function t(s,o){for(var a in o){var r=o[a];if(r.input){var l=r.input,c=s;switch(""!=s&&(c+="|"),c+=l.name,l.type){case"repeat":var d="section-",h=[],u=null;for(var p in r){var g=p.indexOf(d);g!=-1&&(g+=d.length,h.push(parseInt(p.substr(g))),u||(u=p.substr(0,g)))}h.sort(function(e,t){return e-t});var a=0;for(var f in h)t(c+"_"+a++,r[u+h[f]]);break;case"conditional":var m=n.app.field_list[l.id].value();e(c+"|"+l.test_param.name,l.id,m);var v=i(l,m);v!=-1&&t(c,o[l.id+"-section-"+v]);break;case"section":t(!l.flat&&c||"",r);break;default:var _=n.app.field_list[l.id];if(_&&_.value){var m=_.value();if((void 0===l.ignore||l.ignore!=m)&&(_.collapsed&&l.collapsible_value&&(m=l.collapsible_value),e(c,l.id,m),l.payload))for(var y in l.payload)e(y,l.id,l.payload[y])}}}}}var n=this,s={};this._iterate(this.app.section.$el,s);var o={};return this.flat_dict={},t("",s),o},match:function(e){return this.flat_dict&&this.flat_dict[e]},matchCase:function(e,t){return i(e,t)},matchModel:function(e,t){var i=this;n(e.inputs,function(e,n){i.flat_dict[n]&&t(e,i.flat_dict[n])})},matchResponse:function(e){function t(e,s){if("string"==typeof s){var o=n.flat_dict[e];o&&(i[o]=s)}else for(var a in s){var r=a;if(""!==e){var l="|";s instanceof Array&&(l="_"),r=e+l+r}t(r,s[a])}}var i={},n=this;return t("",e),i},_iterate:function(e,t){var i=this,n=a(e).children();n.each(function(){var e=this,n=a(e).attr("id");if(a(e).hasClass("section-row")){var s=i.app.input_list[n];t[n]=s&&{input:s}||{},i._iterate(e,t[n])}else i._iterate(e,t)})}}),i=function(e,t){"boolean"==e.test_param.type&&(t="true"==t?e.test_param.truevalue||"true":e.test_param.falsevalue||"false");for(var i in e.cases)if(e.cases[i].value==t)return i;return-1},n=function(e,t,s,o){o=a.extend(!0,{},o),r.each(e,function(e){e&&e.type&&e.name&&(o[e.name]=e)});for(var l in e){var c=e[l];c.name=c.name||l;var d=s?s+"|"+c.name:c.name;switch(c.type){case"repeat":r.each(c.cache,function(e,i){n(e,t,d+"_"+i,o)});break;case"conditional":if(c.test_param){t(c.test_param,d+"|"+c.test_param.name,o);var h=i(c,c.test_param.value);h!=-1?n(c.cases[h].inputs,t,d,o):Galaxy.emit.debug("form-data::visitInputs() - Invalid case for "+d+".")}else Galaxy.emit.debug("form-data::visitInputs() - Conditional test parameter missing for "+d+".");break;case"section":n(c.inputs,t,d,o);break;default:t(c,d,o)}}};return{Manager:t,visitInputs:n}}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(3),i(1),i(2))},function(e,t,i){var n,s;(function(i,o,a){n=[],s=function(){return i.View.extend({initialize:function(e,t){this.app=e,this.app_options=e.options||{},this.field=t&&t.field||new i.View,this.model=t&&t.model||new i.Model({text_enable:this.app_options.text_enable||"Enable",text_disable:this.app_options.text_disable||"Disable",cls_enable:this.app_options.cls_enable||"fa fa-caret-square-o-down",cls_disable:this.app_options.cls_disable||"fa fa-caret-square-o-up"}).set(t),this.setElement(this._template()),this.$field=this.$(".ui-form-field"),this.$info=this.$(".ui-form-info"),this.$preview=this.$(".ui-form-preview"),this.$collapsible=this.$(".ui-form-collapsible"),this.$collapsible_text=this.$(".ui-form-collapsible-text"),this.$collapsible_icon=this.$(".ui-form-collapsible-icon"),this.$title=this.$(".ui-form-title"),this.$title_text=this.$(".ui-form-title-text"),this.$error_text=this.$(".ui-form-error-text"),this.$error=this.$(".ui-form-error"),this.$backdrop=this.$(".ui-form-backdrop"),this.$field.prepend(this.field.$el);var n=this.model.get("collapsible_value");this.field.collapsed=void 0!==n&&JSON.stringify(this.model.get("value"))==JSON.stringify(n),this.listenTo(this.model,"change",this.render,this),this.render();var s=this;this.$collapsible.on("click",function(){s.field.collapsed=!s.field.collapsed,e.trigger&&e.trigger("change"),s.render()})},backdrop:function(){this.model.set("backdrop",!0)},error:function(e){this.model.set("error_text",e)},reset:function(){this.model.set("error_text",null)},render:function(){o(".tooltip").hide();var e=this.model.get("help",""),t=this.model.get("argument");t&&e.indexOf("("+t+")")==-1&&(e+=" ("+t+")"),this.$info.html(e),this.$el[this.model.get("hidden")?"hide":"show"](),this.$preview[this.field.collapsed&&this.model.get("collapsible_preview")||this.model.get("disabled")?"show":"hide"]().html(a.escape(this.model.get("text_value")));var i=this.model.get("error_text");if(this.$error[i?"show":"hide"](),this.$el[i?"addClass":"removeClass"]("ui-error"),this.$error_text.html(i),this.$backdrop[this.model.get("backdrop")?"show":"hide"](),this.field.collapsed||this.model.get("disabled")?this.$field.hide():this.$field.show(),this.field.model&&this.field.model.set({color:this.model.get("color"),style:this.model.get("style")}),this.model.get("disabled")||void 0===this.model.get("collapsible_value"))this.$title_text.show().text(this.model.get("label")),this.$collapsible.hide();else{var n=this.field.collapsed?"enable":"disable";this.$title_text.hide(),this.$collapsible.show(),this.$collapsible_text.text(this.model.get("label")),this.$collapsible_icon.removeClass().addClass("icon").addClass(this.model.get("cls_"+n)).attr("data-original-title",this.model.get("text_"+n)).tooltip({placement:"bottom"})}},_template:function(){return o("
    ").addClass("ui-form-element").append(o("
    ").addClass("ui-form-error ui-error").append(o("").addClass("fa fa-arrow-down")).append(o("").addClass("ui-form-error-text"))).append(o("
    ").addClass("ui-form-title").append(o("
    ").addClass("ui-form-collapsible").append(o("").addClass("ui-form-collapsible-icon")).append(o("").addClass("ui-form-collapsible-text"))).append(o("").addClass("ui-form-title-text"))).append(o("
    ").addClass("ui-form-field").append(o("").addClass("ui-form-info")).append(o("
    ").addClass("ui-form-backdrop"))).append(o("
    ").addClass("ui-form-preview"))}})}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(3),i(1),i(2))},function(e,t,i){var n,s;(function(o,a,r){n=[i(4),i(7),i(48),i(50),i(49),i(46)],s=function(e,t,i,n,s,l){return o.Model.extend({types:{text:"_fieldText",select:"_fieldSelect",data_column:"_fieldSelect",genomebuild:"_fieldSelect",data:"_fieldData",data_collection:"_fieldData",integer:"_fieldSlider","float":"_fieldSlider","boolean":"_fieldBoolean",drill_down:"_fieldDrilldown",color:"_fieldColor",hidden:"_fieldHidden",hidden_data:"_fieldHidden",baseurl:"_fieldHidden",library_data:"_fieldLibrary",ftpfile:"_fieldFtp"},create:function(e){var t=this.types[e.type],i="function"==typeof this[t]?this[t].call(this,e):null;return i||(i=e.options?this._fieldSelect(e):this._fieldText(e),Galaxy.emit.debug("form-parameters::_addRow()","Auto matched field type ("+e.type+").")),void 0===e.value&&(e.value=null),i.value(e.value),i},_fieldData:function(e){return new i.View({id:"field-"+e.id,extensions:e.extensions,optional:e.optional,multiple:e.multiple,type:e.type,flavor:e.flavor,data:e.options,onchange:e.onchange})},_fieldSelect:function(e){if(e.is_workflow)return this._fieldText(e);"data_column"==e.type&&(e.error_text="Missing columns in referenced dataset.");var i=e.data;i||(i=[],a.each(e.options,function(e){i.push({label:e[0],value:e[1]})}));var n=t.Select;switch(e.display){case"checkboxes":n=t.Checkbox;break;case"radio":n=t.Radio;break;case"radiobutton":n=t.RadioButton}return new n.View({id:"field-"+e.id,data:i,error_text:e.error_text||"No options available",multiple:e.multiple,optional:e.optional,onchange:e.onchange,searchable:"workflow"!==e.flavor})},_fieldDrilldown:function(e){return e.is_workflow?this._fieldText(e):new t.Drilldown.View({id:"field-"+e.id,data:e.options,display:e.display,optional:e.optional,onchange:e.onchange})},_fieldText:function(i){if(i.options&&i.data)if(i.area=i.multiple,e.isEmpty(i.value))i.value=null;else if(r.isArray(i.value)){var n="";for(var s in i.value){if(n+=String(i.value[s]),!i.multiple)break;n+="\n"}i.value=n}return new t.Input({id:"field-"+i.id,area:i.area,placeholder:i.placeholder,onchange:i.onchange})},_fieldSlider:function(e){return new t.Slider.View({id:"field-"+e.id,precise:"float"==e.type,is_workflow:e.is_workflow,min:e.min,max:e.max,onchange:e.onchange})},_fieldHidden:function(e){return new t.Hidden({id:"field-"+e.id,info:e.info})},_fieldBoolean:function(e){return new t.RadioButton.View({id:"field-"+e.id,data:[{label:"Yes",value:"true"},{label:"No",value:"false"}],onchange:e.onchange})},_fieldColor:function(e){return new l({id:"field-"+e.id,onchange:e.onchange})},_fieldLibrary:function(e){return new n.View({id:"field-"+e.id,optional:e.optional,multiple:e.multiple,onchange:e.onchange})},_fieldFtp:function(e){return new s.View({id:"field-"+e.id,optional:e.optional,multiple:e.multiple,onchange:e.onchange})}})}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(3),i(2),i(1))},function(e,t,i){var n,s;(function(o,a,r){n=[i(4),i(9),i(7)],s=function(e,t,i){var n=o.View.extend({initialize:function(t){this.list={},this.options=e.merge(t,{title:"Repeat",empty_text:"Not available.",max:null,min:null}),this.button_new=new i.ButtonIcon({icon:"fa-plus",title:"Insert "+this.options.title,tooltip:"Add new "+this.options.title+" block",floating:"clear",cls:"ui-button-icon form-repeat-add",onclick:function(){t.onnew&&t.onnew()}}),this.setElement(a("
    ").append(this.$list=a("
    ")).append(a("
    ").append(this.button_new.$el)))},size:function(){return r.size(this.list)},add:function(e){if(!e.id||this.list[e.id])return void Galaxy.emit.debug("form-repeat::add()","Duplicate or invalid repeat block id.");var n=new i.ButtonIcon({icon:"fa-trash-o",tooltip:"Delete this repeat block",cls:"ui-button-icon-plain form-repeat-delete",onclick:function(){e.ondel&&e.ondel()}}),s=new t.View({id:e.id,title:"placeholder",cls:e.cls||"ui-portlet-repeat",operations:{button_delete:n}});s.append(e.$el),s.$el.addClass("section-row").hide(),this.list[e.id]=s,this.$list.append(s.$el.fadeIn("fast")),this.options.max>0&&this.size()>=this.options.max&&this.button_new.disable(),this._refresh()},del:function(e){return this.list[e]?(this.$list.find("#"+e).remove(),delete this.list[e],this.button_new.enable(),void this._refresh()):void Galaxy.emit.debug("form-repeat::del()","Invalid repeat block id.")},delAll:function(){for(var e in this.list)this.del(e)},hideOptions:function(){this.button_new.$el.hide(),r.each(this.list,function(e){e.hideOperation("button_delete")}),r.isEmpty(this.list)&&this.$el.append(a("
    ").addClass("ui-form-info").html(this.options.empty_text))},_refresh:function(){var e=0;for(var t in this.list){var i=this.list[t];i.title(++e+": "+this.options.title),i[this.size()>this.options.min?"showOperation":"hideOperation"]("button_delete")}}});return{View:n}}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(3),i(1),i(2))},function(e,t,i){var n,s;(function(o,a,r,l){n=[i(4),i(7),i(9),i(36),i(34),i(35)],s=function(e,t,i,n,s,c){var d=o.View.extend({initialize:function(e,t){this.app=e,this.inputs=t.inputs,this.parameters=new c,this.setElement(a("
    ")),this.render()},render:function(){var e=this;this.$el.empty(),r.each(this.inputs,function(t){e.add(t)})},add:function(t){var i=l.extend(!0,{},t);switch(i.id=t.id=e.uid(),this.app.input_list[i.id]=i,i.type){case"conditional":this._addConditional(i);break;case"repeat":this._addRepeat(i);break;case"section":this._addSection(i);break;default:this._addRow(i)}},_addConditional:function(e){var t=this;e.test_param.id=e.id,this.app.options.sustain_conditionals&&(e.test_param.disabled=!0);var i=this._addRow(e.test_param);i.model&&i.model.set("onchange",function(i){var n=t.app.data.matchCase(e,i);for(var s in e.cases){var o=e.cases[s],a=t.$("#"+e.id+"-section-"+s),r=!1;for(var l in o.inputs)if(!o.inputs[l].hidden){r=!0;break}s==n&&r?a.fadeIn("fast"):a.hide()}t.app.trigger("change")});for(var n in e.cases){var s=new d(this.app,{inputs:e.cases[n].inputs});this._append(s.$el.addClass("ui-form-section"),e.id+"-section-"+n)}i.trigger("change")},_addRepeat:function(e){function t(t){var n=e.id+"-section-"+o++,s=new d(i.app,{inputs:t});a.add({id:n,$el:s.$el,ondel:function(){a.del(n),i.app.trigger("change")}})}for(var i=this,o=0,a=new n.View({title:e.title||"Repeat",min:e.min,max:e.max,onnew:function(){t(e.inputs),i.app.trigger("change")}}),l=r.size(e.cache),c=0;c").addClass("ui-form-info").html(e.help)),this.app.on("expand",function(e){t.$("#"+e).length>0&&t.expand()}),this._append(t.$el,e.id)},_addRow:function(e){var t=this,i=e.id;e.onchange=function(){t.app.trigger("change",i)};var n=this.parameters.create(e);this.app.field_list[i]=n;var o=new s(this.app,{name:e.name,label:e.label||e.name,value:e.value,text_value:e.text_value,collapsible_value:e.collapsible_value,collapsible_preview:e.collapsible_preview,help:e.help,argument:e.argument,disabled:e.disabled,color:e.color,style:e.style,backdrop:e.backdrop,hidden:e.hidden,field:n});return this.app.element_list[i]=o,this._append(o.$el,e.id),n},_append:function(e,t){this.$el.append(e.addClass("section-row").attr("id",t))}});return{View:d}}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(3),i(1),i(2),i(1))},function(e,t,i){var n,s;(function(o,a,r){n=[i(4),i(9),i(7),i(37),i(33)],s=function(e,t,i,n,s){return o.View.extend({initialize:function(t){this.options=e.merge(t,{initial_errors:!1,cls:"ui-portlet-limited",icon:null,always_refresh:!0}),this.setElement("
    "),this.render()},update:function(e){var t=this;this.data.matchModel(e,function(e,i){var n=t.input_list[i];if(n&&n.options&&!a.isEqual(n.options,e.options)){n.options=e.options;var s=t.field_list[i];if(s.update){var o=[];if(["data","data_collection","drill_down"].indexOf(n.type)!=-1)o=n.options;else for(var r in e.options){var l=e.options[r];l.length>2&&o.push({label:l[0],value:l[1]})}s.update(o),s.trigger("change"),Galaxy.emit.debug("form-view::update()","Updating options for "+i)}}})},wait:function(e){for(var t in this.input_list){var i=this.field_list[t],n=this.input_list[t];n.is_dynamic&&i.wait&&i.unwait&&i[e?"wait":"unwait"]()}},highlight:function(e,t,i){var n=this.element_list[e];if(n&&(n.error(t||"Please verify this parameter."),this.portlet.expand(),this.trigger("expand",e),!i)){var s=this.$el.parents().filter(function(){return["auto","scroll"].indexOf(r(this).css("overflow"))!=-1}).first();s.animate({scrollTop:s.scrollTop()+n.$el.offset().top-120},500)}},errors:function(e){if(this.trigger("reset"),e&&e.errors){var t=this.data.matchResponse(e.errors);for(var i in this.element_list){this.element_list[i];t[i]&&this.highlight(i,t[i],!0)}}},render:function(){var e=this;this.off("change"),this.off("reset"),this.field_list={},this.input_list={},this.element_list={},this.data=new s.Manager(this),this._renderForm(),this.data.create(),this.options.initial_errors&&this.errors(this.options);var t=this.data.checksum();return this.on("change",function(i){var n=e.input_list[i];if(!n||n.refresh_on_change||e.options.always_refresh){var s=e.data.checksum();s!=t&&(t=s,e.options.onchange&&e.options.onchange())}}),this.on("reset",function(){a.each(e.element_list,function(e){e.reset()})}),this},_renderForm:function(){r(".tooltip").remove(),this.message=new i.Message,this.section=new n.View(this,{inputs:this.options.inputs}),this.portlet=new t.View({icon:this.options.icon,title:this.options.title,cls:this.options.cls,operations:this.options.operations,buttons:this.options.buttons,collapsible:this.options.collapsible,collapsed:this.options.collapsed}),this.portlet.append(this.message.$el),this.portlet.append(this.section.$el),this.$el.empty(),this.options.inputs&&this.$el.append(this.portlet.$el),this.options.message&&this.message.update({persistent:!0,status:"warning",message:this.options.message}),Galaxy.emit.debug("form-view::initialize()","Completed")}})}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(3),i(2),i(1))},function(e,t,i){var n,s;(function(o){n=[i(30),i(74),i(5)],s=function(e,t,i){"use strict";function n(e){return function(t,i){return this.isNew()&&(i=i||{},i.url=this.urlRoot+this.get("history_id")+"/contents",t=t||{},t.type="dataset_collection"),e.call(this,t,i)}}var s=t.HistoryContentMixin,a=e.ListDatasetCollection,r=e.PairDatasetCollection,l=e.ListPairedDatasetCollection,c=e.ListOfListsDatasetCollection,d=a.extend(s).extend({defaults:o.extend(o.clone(a.prototype.defaults),{history_content_type:"dataset_collection",collection_type:"list",model_class:"HistoryDatasetCollectionAssociation"}),save:n(a.prototype.save),toString:function(){return"History"+a.prototype.toString.call(this)}}),h=r.extend(s).extend({defaults:o.extend(o.clone(r.prototype.defaults),{history_content_type:"dataset_collection",collection_type:"paired",model_class:"HistoryDatasetCollectionAssociation"}),save:n(r.prototype.save),toString:function(){return"History"+r.prototype.toString.call(this)}}),u=l.extend(s).extend({defaults:o.extend(o.clone(l.prototype.defaults),{history_content_type:"dataset_collection",collection_type:"list:paired",model_class:"HistoryDatasetCollectionAssociation"}),save:n(l.prototype.save),toString:function(){return"History"+l.prototype.toString.call(this)}}),p=c.extend(s).extend({defaults:o.extend(o.clone(c.prototype.defaults),{history_content_type:"dataset_collection",collection_type:"list:list",model_class:"HistoryDatasetCollectionAssociation"}),save:n(c.prototype.save),toString:function(){return["HistoryListOfListsDatasetCollection(",this.get("name"),")"].join("")}});return{HistoryListDatasetCollection:d,HistoryPairDatasetCollection:h,HistoryListPairedDatasetCollection:u,HistoryListOfListsDatasetCollection:p}}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(2))},function(e,t,i){var n,s;(function(o,a,r){n=[i(67),i(72),i(39),i(41),i(6),i(129)],s=function(e,t,i,n,s,l){"use strict";var c=e.PaginatedCollection,d=c.extend(s.LoggableMixin).extend({_logNamespace:"history",model:function(e,n){if("dataset"===e.history_content_type)return new t.HistoryDatasetAssociation(e,n);if("dataset_collection"===e.history_content_type){switch(e.collection_type){case"list":return new i.HistoryListDatasetCollection(e,n);case"paired":return new i.HistoryPairDatasetCollection(e,n);case"list:paired":return new i.HistoryListPairedDatasetCollection(e,n);case"list:list":return new i.HistoryListOfListsDatasetCollection(e,n)}var s="Unknown collection_type: "+e.collection_type;return console.warn(s,e),{validationError:s}}return{validationError:"Unknown history_content_type: "+e.history_content_type}},limitPerPage:500,limitPerProgressiveFetch:500,order:"hid",urlRoot:Galaxy.root+"api/histories",url:function(){return this.urlRoot+"/"+this.historyId+"/contents"},initialize:function(e,t){t=t||{},c.prototype.initialize.call(this,e,t),this.history=t.history||null,this.setHistoryId(t.historyId||null),this.includeDeleted=t.includeDeleted||this.includeDeleted,this.includeHidden=t.includeHidden||this.includeHidden,this.model.prototype.idAttribute="type_id"},setHistoryId:function(e){this.historyId=e,this._setUpWebStorage()},_setUpWebStorage:function(e){if(this.historyId)return this.storage=new n.HistoryPrefs({id:n.HistoryPrefs.historyStorageKey(this.historyId)}),this.trigger("new-storage",this.storage,this),this.on({"include-deleted":function(e){this.storage.includeDeleted(e)},"include-hidden":function(e){this.storage.includeHidden(e)}}),this.includeDeleted=this.storage.includeDeleted()||!1,this.includeHidden=this.storage.includeHidden()||!1,this},comparators:o.extend(o.clone(c.prototype.comparators),{name:s.buildComparator("name",{ascending:!0}),"name-dsc":s.buildComparator("name",{ascending:!1}),hid:s.buildComparator("hid",{ascending:!1}),"hid-asc":s.buildComparator("hid",{ascending:!0})}),running:function(){return this.filter(function(e){return!e.inReadyState()})},runningAndActive:function(){return this.filter(function(e){return!e.inReadyState()&&e.get("visible")&&!e.get("deleted")})},getByHid:function(e){return this.findWhere({hid:e})},haveDetails:function(){return this.all(function(e){return e.hasDetails()})},hidden:function(){return this.filter(function(e){return e.hidden()})},deleted:function(){return this.filter(function(e){return e.get("deleted")})},visibleAndUndeleted:function(){return this.filter(function(e){return e.get("visible")&&!e.get("deleted")})},setIncludeDeleted:function(e,t){if(o.isBoolean(e)&&e!==this.includeDeleted){if(this.includeDeleted=e,o.result(t,"silent"))return;this.trigger("include-deleted",e,this)}},setIncludeHidden:function(e,t){if(o.isBoolean(e)&&e!==this.includeHidden){if(this.includeHidden=e,t=t||{},o.result(t,"silent"))return;this.trigger("include-hidden",e,this)}},fetch:function(e){if(e=e||{},this.historyId&&!e.details){var t=n.HistoryPrefs.get(this.historyId).toJSON();o.isEmpty(t.expandedIds)||(e.details=o.values(t.expandedIds).join(","))}return c.prototype.fetch.call(this,e)},_buildFetchData:function(e){return o.extend(c.prototype._buildFetchData.call(this,e),{v:"dev"})},_fetchParams:c.prototype._fetchParams.concat(["v","details"]),_buildFetchFilters:function(e){var t=c.prototype._buildFetchFilters.call(this,e)||{},i={};return this.includeDeleted||(i.deleted=!1,i.purged=!1),this.includeHidden||(i.visible=!0),o.defaults(t,i)},getTotalItemCount:function(){return this.history.contentsShown()},fetchUpdated:function(e,t){return e&&(t=t||{filters:{}},t.remove=!1,t.filters={"update_time-ge":e.toISOString(),visible:""}),this.fetch(t)},fetchDeleted:function(e){e=e||{};var t=this;return e.filters=o.extend(e.filters,{deleted:!0,purged:void 0}),e.remove=!1,t.trigger("fetching-deleted",t),t.fetch(e).always(function(){t.trigger("fetching-deleted-done",t)})},fetchHidden:function(e){e=e||{};var t=this;return e.filters=o.extend(e.filters,{visible:!1}),e.remove=!1,t.trigger("fetching-hidden",t),t.fetch(e).always(function(){t.trigger("fetching-hidden-done",t)})},fetchAllDetails:function(e){e=e||{};var t={details:"all"};return e.data=o.extend(e.data||{},t),this.fetch(e)},fetchCollectionCounts:function(e){return e=e||{},e.keys=["type_id","element_count"].join(","),e.filters=o.extend(e.filters||{},{history_content_type:"dataset_collection"}),e.remove=!1,this.fetch(e)},_filterAndUpdate:function(e,t){var i=this,n=i.model.prototype.idAttribute,s=[t];return i.fetch({filters:e,remove:!1}).then(function(e){return e=e.reduce(function(e,t,s){var o=i.get(t[n]);return o?e.concat(o):e},[]),i.ajaxQueue("save",s,e)})},ajaxQueue:function(e,t,i){ +return i=i||this.models,new l.AjaxQueue(i.slice().reverse().map(function(i,n){var s=o.isString(e)?i[e]:e;return function(){return s.apply(i,t)}})).deferred},progressivelyFetchDetails:function(e){function i(t){t=t||0;var a=o.extend(o.clone(e),{view:"summary",keys:c,limit:r,offset:t,reset:0===t,remove:!1});o.defer(function(){s.fetch.call(s,a).fail(n.reject).done(function(e){n.notify(e,r,t),e.length!==r?(s.allFetched=!0,n.resolve(e,r,t)):i(t+r)})})}e=e||{};var n=a.Deferred(),s=this,r=e.limitPerCall||s.limitPerProgressiveFetch,l=t.HistoryDatasetAssociation.prototype.searchAttributes,c=l.join(",");return i(),n},isCopyable:function(e){var t=["HistoryDatasetAssociation","HistoryDatasetCollectionAssociation"];return o.isObject(e)&&e.id&&o.contains(t,e.model_class)},copy:function(e){var t,i,n;o.isString(e)?(t=e,n="hda",i="dataset"):(t=e.id,n={HistoryDatasetAssociation:"hda",LibraryDatasetDatasetAssociation:"ldda",HistoryDatasetCollectionAssociation:"hdca"}[e.model_class]||"hda",i="hdca"===n?"dataset_collection":"dataset");var s=this,r=a.ajax(this.url(),{method:"POST",contentType:"application/json",data:JSON.stringify({content:t,source:n,type:i})}).done(function(e){s.add([e],{parse:!0})}).fail(function(e,o,a){s.trigger("error",s,r,{},"Error copying contents",{type:i,id:t,source:n})});return r},createHDCA:function(e,t,i,n){var s=this.model({history_content_type:"dataset_collection",collection_type:t,history_id:this.historyId,name:i,element_identifiers:e});return s.save(n)},haveSearchDetails:function(){return this.allFetched&&this.all(function(e){return o.has(e.attributes,"annotation")})},matches:function(e){return this.filter(function(t){return t.matches(e)})},clone:function(){var e=r.Collection.prototype.clone.call(this);return e.historyId=this.historyId,e},toString:function(){return["HistoryContents(",[this.historyId,this.length].join(),")"].join("")}});return{HistoryContents:d}}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(2),i(1),i(3))},function(e,t,i){var n,s;(function(o){n=[i(6)],s=function(e){"use strict";var t=e.SessionStorageModel.extend({defaults:{expandedIds:{},show_deleted:!1,show_hidden:!1},addExpanded:function(e){var t=this.get("expandedIds");t[e.id]=e.get("id"),this.save("expandedIds",t)},removeExpanded:function(e){var t=this.get("expandedIds");delete t[e.id],this.save("expandedIds",t)},isExpanded:function(e){return o.result(this.get("expandedIds"),e,!1)},allExpanded:function(){return o.values(this.get("expandedIds"))},clearExpanded:function(){this.set("expandedIds",{})},includeDeleted:function(e){return o.isUndefined(e)||this.set("show_deleted",e),this.get("show_deleted")},includeHidden:function(e){return o.isUndefined(e)||this.set("show_hidden",e),this.get("show_hidden")},toString:function(){return"HistoryPrefs("+this.id+")"}},{storageKeyPrefix:"history:",historyStorageKey:function(e){if(!e)throw new Error("HistoryPrefs.historyStorageKey needs valid id: "+e);return t.storageKeyPrefix+e},get:function(e){return new t({id:t.historyStorageKey(e)})},clearAll:function(e){for(var i in sessionStorage)0===i.indexOf(t.storageKeyPrefix)&&sessionStorage.removeItem(i)}});return{HistoryPrefs:t}}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(2))},function(e,t,i){var n,s;(function(o,a,r,l){n=[i(6),i(5)],s=function(e,t){"use strict";var i="list",n=o.View.extend(e.LoggableMixin).extend({_logNamespace:i,initialize:function(e){this.expanded=e.expanded||!1,this.log("\t expanded:",this.expanded),this.fxSpeed=void 0!==e.fxSpeed?e.fxSpeed:this.fxSpeed},fxSpeed:"fast",render:function(e){var t=this._buildNewRender();return this._setUpBehaviors(t),this._queueNewRender(t,e),this},_buildNewRender:function(){var e=a(this.templates.el(this.model.toJSON(),this));return this.expanded&&this.$details(e).replaceWith(this._renderDetails().show()),e},_queueNewRender:function(e,t){t=void 0===t?this.fxSpeed:t;var i=this;0===t?(i._swapNewRender(e),i.trigger("rendered",i)):a(i).queue("fx",[function(e){i.$el.fadeOut(t,e)},function(t){i._swapNewRender(e),t()},function(e){i.$el.fadeIn(t,e)},function(e){i.trigger("rendered",i),e()}])},_swapNewRender:function(e){return this.$el.empty().attr("class",r.isFunction(this.className)?this.className():this.className).append(e.children())},_setUpBehaviors:function(e){e=e||this.$el,e.find("[title]").tooltip({placement:"bottom"})},$details:function(e){return e=e||this.$el,e.find("> .details")},_renderDetails:function(){var e=a(this.templates.details(this.model.toJSON(),this));return this._setUpBehaviors(e),e},toggleExpanded:function(e){return e=void 0===e?!this.expanded:e,e?this.expand():this.collapse(),this},expand:function(){var e=this;return e._fetchModelDetails().always(function(){e._expand()})},_fetchModelDetails:function(){return this.model.hasDetails()?l.when():this.model.fetch()},_expand:function(){var e=this,t=e._renderDetails();e.$details().replaceWith(t),e.expanded=!0,e.$details().slideDown(e.fxSpeed,function(){e.trigger("expanded",e)})},collapse:function(){this.debug(this+"(ExpandableView).collapse");var e=this;e.expanded=!1,this.$details().slideUp(e.fxSpeed,function(){e.trigger("collapsed",e)})}}),s=n.extend(e.mixin(e.SelectableViewMixin,e.DraggableViewMixin,{tagName:"div",className:"list-item",initialize:function(t){n.prototype.initialize.call(this,t),e.SelectableViewMixin.initialize.call(this,t),e.DraggableViewMixin.initialize.call(this,t),this._setUpListeners()},_setUpListeners:function(){return this.on("selectable",function(e){e?this.$(".primary-actions").hide():this.$(".primary-actions").show()},this),this},_buildNewRender:function(){var e=n.prototype._buildNewRender.call(this);return e.children(".warnings").replaceWith(this._renderWarnings()),e.children(".title-bar").replaceWith(this._renderTitleBar()),e.children(".primary-actions").append(this._renderPrimaryActions()),e.find("> .title-bar .subtitle").replaceWith(this._renderSubtitle()),e},_swapNewRender:function(e){return n.prototype._swapNewRender.call(this,e),this.selectable&&this.showSelector(0),this.draggable&&this.draggableOn(),this.$el},_renderWarnings:function(){var e=this,t=a('
    '),i=e.model.toJSON();return r.each(e.templates.warnings,function(n){t.append(a(n(i,e)))}),t},_renderTitleBar:function(){return a(this.templates.titleBar(this.model.toJSON(),this))},_renderPrimaryActions:function(){return[]},_renderSubtitle:function(){return a(this.templates.subtitle(this.model.toJSON(),this))},events:{"click .title-bar":"_clickTitleBar","keydown .title-bar":"_keyDownTitleBar","click .selector":"toggleSelect"},_clickTitleBar:function(e){e.stopPropagation(),e.altKey?(this.toggleSelect(e),this.selectable||this.showSelector()):this.toggleExpanded()},_keyDownTitleBar:function(e){var t=32,i=13;return!e||"keydown"!==e.type||e.keyCode!==t&&e.keyCode!==i||(this.toggleExpanded(),e.stopPropagation(),!1)},toString:function(){var e=this.model?this.model+"":"(no model)";return"ListItemView("+e+")"}}));s.prototype.templates=function(){var t=e.wrapTemplate(['
    ','
    ','
    ','',"
    ",'
    ','
    ','
    ',"
    "]),i={},n=e.wrapTemplate(['
    ','','
    ','<%- element.name %>',"
    ",'
    ',"
    "],"element"),s=e.wrapTemplate(['
    ']),o=e.wrapTemplate(['
    ']);return{el:t,warnings:i,titleBar:n,subtitle:s,details:o}}();var c=s.extend({foldoutStyle:"foldout",foldoutPanelClass:null,initialize:function(e){"drilldown"===this.foldoutStyle&&(this.expanded=!1),this.foldoutStyle=e.foldoutStyle||this.foldoutStyle,this.foldoutPanelClass=e.foldoutPanelClass||this.foldoutPanelClass,s.prototype.initialize.call(this,e),this.foldout=this._createFoldoutPanel()},_renderDetails:function(){if("drilldown"===this.foldoutStyle)return a();var e=s.prototype._renderDetails.call(this);return this._attachFoldout(this.foldout,e)},_createFoldoutPanel:function(){var e=this.model,t=this._getFoldoutPanelClass(e),i=this._getFoldoutPanelOptions(e),n=new t(r.extend(i,{model:e}));return n},_getFoldoutPanelClass:function(){return this.foldoutPanelClass},_getFoldoutPanelOptions:function(){return{foldoutStyle:this.foldoutStyle,fxSpeed:this.fxSpeed}},_attachFoldout:function(e,t){return t=t||this.$("> .details"),this.foldout=e.render(0),e.$("> .controls").hide(),t.append(e.$el)},expand:function(){var e=this;return e._fetchModelDetails().always(function(){"foldout"===e.foldoutStyle?e._expand():"drilldown"===e.foldoutStyle&&e._expandByDrilldown()})},_expandByDrilldown:function(){var e=this;e.listenTo(e.foldout,"close",function(){e.trigger("collapsed:drilldown",e,e.foldout)}),e.trigger("expanded:drilldown",e,e.foldout)}});return c.prototype.templates=function(){var t=e.wrapTemplate(['
    ',"
    "],"collection");return r.extend({},s.prototype.templates,{details:t})}(),{ExpandableView:n,ListItemView:s,FoldoutListItemView:c}}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(3),i(1),i(2),i(1))},function(e,t,i){var n,s;(function(o,a){n=[i(4),i(55),i(7),i(38),i(17),i(28)],s=function(e,t,i,n,s,r){return n.extend({initialize:function(e){var i=this;n.prototype.initialize.call(this,e),this.deferred=new t,e.inputs?this._buildForm(e):this.deferred.execute(function(t){i._buildModel(t,e,!0)}),e.listen_to_history&&parent.Galaxy&&parent.Galaxy.currHistoryPanel&&this.listenTo(parent.Galaxy.currHistoryPanel.collection,"change",function(){this.refresh()}),this.$el.on("remove",function(){i.remove()})},refresh:function(){var e=this;e.deferred.reset(),this.deferred.execute(function(t){e._updateModel(t)})},remove:function(){var e=this;this.$el.hide(),this.deferred.execute(function(){n.prototype.remove.call(e),Galaxy.emit.debug("tool-form-base::remove()","Destroy view.")})},_buildForm:function(t){var i=this;this.options=e.merge(t,this.options),this.options=e.merge({icon:t.icon,title:""+t.name+" "+t.description+" (Galaxy Version "+t.version+")",operations:!this.options.hide_operations&&this._operations(),onchange:function(){i.refresh()}},this.options),this.options.customize&&this.options.customize(this.options),this.render(),this.options.collapsible||this.$el.append(o("
    ").addClass("ui-margin-top-large").append(this._footer()))},_buildModel:function(t,n,s){var a=this;this.options.id=n.id,this.options.version=n.version;var r="",l={};n.job_id?r=Galaxy.root+"api/jobs/"+n.job_id+"/build_for_rerun":(r=Galaxy.root+"api/tools/"+n.id+"/build",Galaxy.params&&Galaxy.params.tool_id==n.id&&(l=o.extend({},Galaxy.params),n.version&&(l.tool_version=n.version))),e.get({url:r,data:l,success:function(e){return e=e.tool_model||e,e.display?(a._buildForm(e),!s&&a.message.update({status:"success",message:"Now you are using '"+a.options.name+"' version "+a.options.version+", id '"+a.options.id+"'.",persistent:!1}),Galaxy.emit.debug("tool-form-base::initialize()","Initial tool model ready.",e),void t.resolve()):void(window.location=Galaxy.root)},error:function(e,n){var s=e&&e.err_msg||"Uncaught error.";401==n?window.location=Galaxy.root+"user/login?"+o.param({redirect:Galaxy.root+"?tool_id="+a.options.id}):a.$el.is(":empty")?a.$el.prepend(new i.Message({message:s,status:"danger",persistent:!0,large:!0}).$el):Galaxy.modal&&Galaxy.modal.show({title:"Tool request failed",body:s,buttons:{Close:function(){Galaxy.modal.hide()}}}),Galaxy.emit.debug("tool-form::initialize()","Initial tool model request failed.",e),t.reject()}})},_updateModel:function(t){var i=this,n=this.options.update_url||Galaxy.root+"api/tools/"+this.options.id+"/build",s={tool_id:this.options.id,tool_version:this.options.version,inputs:o.extend(!0,{},i.data.create())};this.wait(!0),Galaxy.emit.debug("tool-form-base::_updateModel()","Sending current state.",s),e.request({type:"POST",url:n,data:s,success:function(e){i.update(e.tool_model||e),i.options.update&&i.options.update(e),i.wait(!1),Galaxy.emit.debug("tool-form-base::_updateModel()","Received new model.",e),t.resolve()},error:function(e){Galaxy.emit.debug("tool-form-base::_updateModel()","Refresh request failed.",e),t.reject()}})},_operations:function(){var e=this,t=this.options,n=new i.ButtonMenu({icon:"fa-cubes",title:!t.narrow&&"Versions"||null,tooltip:"Select another tool version"});if(!t.sustain_version&&t.versions&&t.versions.length>1)for(var s in t.versions){var o=t.versions[s];o!=t.version&&n.addMenu({title:"Switch to "+o,version:o,icon:"fa-cube",onclick:function(){var i=t.id.replace(t.version,this.version),n=this.version;e.deferred.reset(),e.deferred.execute(function(t){e._buildModel(t,{id:i,version:n})})}})}else n.$el.hide();var a=new i.ButtonMenu({icon:"fa-caret-down",title:!t.narrow&&"Options"||null,tooltip:"View available options"});return t.biostar_url&&(a.addMenu({icon:"fa-question-circle",title:"Question?",tooltip:"Ask a question about this tool (Biostar)",onclick:function(){window.open(t.biostar_url+"/p/new/post/")}}),a.addMenu({icon:"fa-search",title:"Search",tooltip:"Search help for this tool (Biostar)",onclick:function(){window.open(t.biostar_url+"/local/search/page/?q="+t.name)}})),a.addMenu({icon:"fa-share",title:"Share",tooltip:"Share this tool",onclick:function(){prompt("Copy to clipboard: Ctrl+C, Enter",window.location.origin+Galaxy.root+"root?tool_id="+t.id)}}),Galaxy.user&&Galaxy.user.get("is_admin")&&a.addMenu({icon:"fa-download",title:"Download",tooltip:"Download this tool",onclick:function(){window.location.href=Galaxy.root+"api/tools/"+t.id+"/download"}}),t.requirements&&t.requirements.length>0&&a.addMenu({icon:"fa-info-circle",title:"Requirements",tooltip:"Display tool requirements",onclick:function(){!this.visible||e.portlet.collapsed?(this.visible=!0,e.portlet.expand(),e.message.update({persistent:!0,message:e._templateRequirements(t),status:"info"})):(this.visible=!1,e.message.update({message:""}))}}),t.sharable_url&&a.addMenu({icon:"fa-external-link",title:"See in Tool Shed",tooltip:"Access the repository",onclick:function(){window.open(t.sharable_url)}}),{menu:a,versions:n}},_footer:function(){var e=this.options,t=o("
    ").append(this._templateHelp(e));if(e.citations){var i=o("
    "),n=new s.ToolCitationCollection;n.tool_id=e.id;var a=new r.CitationListView({el:i,collection:n});a.render(),n.fetch(),t.append(i)}return t},_templateHelp:function(e){var t=o("
    ").addClass("ui-form-help").append(e.help);return t.find("a").attr("target","_blank"),t},_templateRequirements:function(e){var t=e.requirements.length;if(t>0){var i="This tool requires ";a.each(e.requirements,function(e,n){i+=e.name+(e.version?" (Version "+e.version+")":"")+(n").attr("target","_blank").attr("href","https://wiki.galaxyproject.org/Tools/Requirements").text("here");return o("").append(i+". Click ").append(n).append(" for more information.")}return"No requirements found."}})}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(1),i(2))},function(e,t,i){var n,s;(function(o,a){n=[i(2),i(16),i(11),i(18)],s=function(e,t,i,n){"use strict";var s={hidden:!1,show:function(){this.set("hidden",!1)},hide:function(){this.set("hidden",!0)},toggle:function(){this.set("hidden",!this.get("hidden"))},is_visible:function(){return!this.attributes.hidden}},r=o.Model.extend({defaults:{name:null,label:null,type:null,value:null,html:null,num_samples:5},initialize:function(e){this.attributes.html=unescape(this.attributes.html)},copy:function(){return new r(this.toJSON())},set_value:function(e){this.set("value",e||"")}}),l=o.Collection.extend({model:r}),c=r.extend({}),d=r.extend({set_value:function(e){this.set("value",parseInt(e,10))},get_samples:function(){return d3.scale.linear().domain([this.get("min"),this.get("max")]).ticks(this.get("num_samples"))}}),h=d.extend({set_value:function(e){this.set("value",parseFloat(e))}}),u=r.extend({get_samples:function(){return e.map(this.get("options"),function(e){return e[0]})}});r.subModelTypes={integer:d,"float":h,data:c,select:u};var p=o.Model.extend({defaults:{id:null,name:null,description:null,target:null,inputs:[],outputs:[]},urlRoot:Galaxy.root+"api/tools",initialize:function(t){this.set("inputs",new l(e.map(t.inputs,function(e){var t=r.subModelTypes[e.type]||r;return new t(e)})))},toJSON:function(){var e=o.Model.prototype.toJSON.call(this);return e.inputs=this.get("inputs").map(function(e){return e.toJSON()}),e},remove_inputs:function(e){var t=this,i=t.get("inputs").filter(function(t){return e.indexOf(t.get("type"))!==-1});t.get("inputs").remove(i)},copy:function(e){var t=new p(this.toJSON());if(e){var i=new o.Collection;t.get("inputs").each(function(e){e.get_samples()&&i.push(e)}),t.set("inputs",i)}return t},apply_search_results:function(t){return e.indexOf(t,this.attributes.id)!==-1?this.show():this.hide(),this.is_visible()},set_input_value:function(e,t){this.get("inputs").find(function(t){return t.get("name")===e}).set("value",t)},set_input_values:function(t){var i=this;e.each(e.keys(t),function(e){i.set_input_value(e,t[e])})},run:function(){return this._run()},rerun:function(e,t){return this._run({action:"rerun",target_dataset_id:e.id,regions:t})},get_inputs_dict:function(){var e={};return this.get("inputs").each(function(t){e[t.get("name")]=t.get("value")}),e},_run:function(n){var s=e.extend({tool_id:this.id,inputs:this.get_inputs_dict()},n),o=a.Deferred(),r=new t.ServerStateDeferred({ajax_settings:{url:this.urlRoot,data:JSON.stringify(s),dataType:"json",contentType:"application/json",type:"POST"},interval:2e3,success_fn:function(e){return"pending"!==e}});return a.when(r.go()).then(function(e){o.resolve(new i.DatasetCollection(e))}),o}});e.extend(p.prototype,s);var g=(o.View.extend({}),o.Collection.extend({model:p})),f=o.Model.extend(s),m=o.Model.extend({defaults:{elems:[],open:!1},clear_search_results:function(){e.each(this.attributes.elems,function(e){e.show()}),this.show(),this.set("open",!1)},apply_search_results:function(t){var i,n=!0;e.each(this.attributes.elems,function(e){e instanceof f?(i=e,i.hide()):e instanceof p&&e.apply_search_results(t)&&(n=!1,i&&i.show())}),n?this.hide():(this.show(),this.set("open",!0))}});e.extend(m.prototype,s);var v=o.Model.extend({defaults:{search_hint_string:"search tools",min_chars_for_search:3,clear_btn_url:"",search_url:"",visible:!0,query:"",results:null,clear_key:27},urlRoot:Galaxy.root+"api/tools",initialize:function(){this.on("change:query",this.do_search)},do_search:function(){var e=this.attributes.query;if(e.length");e.append(S.tool_link(this.model.toJSON()));var t=this.model.get("form_style",null);if("upload1"===this.model.id)e.find("a").on("click",function(e){e.preventDefault(),Galaxy.upload.show()});else if("regular"===t){var i=this;e.find("a").on("click",function(e){e.preventDefault();var t=new n.View({id:i.model.id,version:i.model.get("version")});t.deferred.execute(function(){Galaxy.app.display(t)})})}return this.$el.append(e),this}}),b=y.extend({tagName:"div",className:"toolPanelLabel",render:function(){return this.$el.append(a("").text(this.model.attributes.text)),this}}),x=y.extend({tagName:"div",className:"toolSectionWrapper",initialize:function(){y.prototype.initialize.call(this),this.model.on("change:open",this.update_open,this)},render:function(){this.$el.append(S.panel_section(this.model.toJSON()));var t=this.$el.find(".toolSectionBody");return e.each(this.model.attributes.elems,function(e){if(e instanceof p){var i=new w({model:e,className:"toolTitle"});i.render(),t.append(i.$el)}else if(e instanceof f){var n=new b({model:e});n.render(),t.append(n.$el)}}),this},events:{"click .toolSectionTitle > a":"toggle"},toggle:function(){this.model.set("open",!this.model.attributes.open)},update_open:function(){this.model.attributes.open?this.$el.children(".toolSectionBody").slideDown("fast"):this.$el.children(".toolSectionBody").slideUp("fast")}}),C=o.View.extend({tagName:"div",id:"tool-search",className:"bar",events:{click:"focus_and_select","keyup :input":"query_changed","click #search-clear-btn":"clear"},render:function(){return this.$el.append(S.tool_search(this.model.toJSON())),this.model.is_visible()||this.$el.hide(),a("#messagebox").is(":visible")&&this.$el.css("top","95px"),this.$el.find("[title]").tooltip(),this},focus_and_select:function(){this.$el.find(":input").focus().select()},clear:function(){return this.model.clear_search(),this.$el.find(":input").val(""),this.focus_and_select(),!1},query_changed:function(e){return this.model.attributes.clear_key&&this.model.attributes.clear_key===e.which?(this.clear(),!1):void this.model.set("query",this.$el.find(":input").val())}}),$=o.View.extend({tagName:"div",className:"toolMenu",initialize:function(){this.model.get("tool_search").on("change:results",this.handle_search_results,this)},render:function(){var e=this,t=new C({model:this.model.get("tool_search")});return t.render(),e.$el.append(t.$el),this.model.get("layout").each(function(t){if(t instanceof m){var i=new x({model:t});i.render(),e.$el.append(i.$el)}else if(t instanceof p){var n=new w({model:t,className:"toolTitleNoSection"});n.render(),e.$el.append(n.$el)}else if(t instanceof f){var s=new b({model:t});s.render(),e.$el.append(s.$el)}}),e.$el.find("a.tool-link").click(function(t){var i=a(this).attr("class").split(/\s+/)[0],n=e.model.get("tools").get(i);e.trigger("tool_link_click",t,n)}),this},handle_search_results:function(){var e=this.model.get("tool_search").get("results");e&&0===e.length?a("#search-no-results").show():a("#search-no-results").hide()}}),E=o.View.extend({className:"toolForm",render:function(){this.$el.children().remove(),this.$el.append(S.tool_form(this.model.toJSON()))}}),S=(o.View.extend({className:"toolMenuAndView",initialize:function(){this.tool_panel_view=new $({collection:this.collection}),this.tool_form_view=new E},render:function(){this.tool_panel_view.render(),this.tool_panel_view.$el.css("float","left"),this.$el.append(this.tool_panel_view.$el),this.tool_form_view.$el.hide(),this.$el.append(this.tool_form_view.$el);var e=this;this.tool_panel_view.on("tool_link_click",function(t,i){t.preventDefault(),e.show_tool(i)})},show_tool:function(e){var t=this;e.fetch().done(function(){t.tool_form_view.model=e,t.tool_form_view.render(),t.tool_form_view.$el.show(),a("#left").width("650px")})}}),{tool_search:e.template(['',' ',''].join("")),panel_section:e.template(['",'
    '+e.content+"
    "}});return{View:t}}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(3),i(1))},function(e,t,i){var n,s;(function(i){n=[],s=function(){var e=i.Model.extend({defaults:{extension:"auto",genome:"?",url_paste:"",status:"init",info:null,file_name:"",file_mode:"",file_size:0,file_type:null,file_path:"",file_data:null,percentage:0,space_to_tab:!1,to_posix_lines:!0,enabled:!0},reset:function(e){this.clear().set(this.defaults).set(e)}}),t=i.Collection.extend({model:e});return{Model:e,Collection:t}}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(3))},,function(e,t,i){var n,s;(function(o,a){n=[i(4)],s=function(e){return o.Model.extend({initialize:function(){this.active={},this.last=null},execute:function(t){var i=this,n=e.uid(),s=t.length>0;this.active[n]=!0;var o=a.Deferred();o.promise().always(function(){delete i.active[n],s&&Galaxy.emit.debug("deferred::execute()",this.state().charAt(0).toUpperCase()+this.state().slice(1)+" "+n)}),a.when(this.last).always(function(){i.active[n]?(s&&Galaxy.emit.debug("deferred::execute()","Running "+n),t(o),!s&&o.resolve()):o.reject()}),this.last=o.promise()},reset:function(){Galaxy.emit.debug("deferred::execute()","Reset");for(var e in this.active)this.active[e]=!1},ready:function(){return a.isEmptyObject(this.active)}})}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(3),i(1))},,,,,,,,,,,function(e,t,i){var n,s;(function(o,a){n=[i(6),i(5),i(15)],s=function(e,t){var i=o.View.extend(e.LoggableMixin).extend(e.HiddenUntilActivatedViewMixin).extend({tagName:"div",className:"annotation-display",initialize:function(e){e=e||{},this.tooltipConfig=e.tooltipConfig||{placement:"bottom"},this.listenTo(this.model,"change:annotation",function(){this.render()}),this.hiddenUntilActivated(e.$activator,e)},render:function(){var e=this;return this.$el.html(this._template()),this.$annotation().make_text_editable({use_textarea:!0,on_finish:function(t){e.$annotation().text(t),e.model.save({annotation:t},{silent:!0}).fail(function(){e.$annotation().text(e.model.previous("annotation"))})}}),this},_template:function(){var e=this.model.get("annotation");return['",'
    ',a.escape(e),"
    "].join("")},$annotation:function(){return this.$el.find(".annotation")},remove:function(){this.$annotation.off(),this.stopListening(this.model),o.View.prototype.remove.call(this)},toString:function(){return["AnnotationEditor(",this.model+"",")"].join("")}});return{AnnotationEditor:i}}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(3),i(2))},function(e,t,i){var n,s;(function(o){n=[i(2),i(3),i(6)],s=function(e,t,i){"use strict";var n=t.Collection.extend({initialize:function(e,i){t.Collection.prototype.initialize.call(this,e,i),this.setOrder(i.order||this.order,{silent:!0})},_setUpListeners:function(){return this.on({"changed-order":this.sort})},fetch:function(e){return e=this._buildFetchOptions(e),t.Collection.prototype.fetch.call(this,e)},_buildFetchOptions:function(t){t=e.clone(t)||{};var i=this;t.traditional=!0,t.data=t.data||i._buildFetchData(t);var n=this._buildFetchFilters(t);return e.isEmpty(n)||e.extend(t.data,this._fetchFiltersToAjaxData(n)),t},_buildFetchData:function(t){var i={};return this.order&&(i.order=this.order),e.defaults(e.pick(t,this._fetchParams),i)},_fetchParams:["order","limit","offset","view","keys"],_buildFetchFilters:function(t){return e.clone(t.filters||{})},_fetchFiltersToAjaxData:function(t){var i={q:[],qv:[]};return e.each(t,function(e,t){void 0!==e&&""!==e&&(e===!0&&(e="True"),e===!1&&(e="False"),null===e&&(e="None"),i.q.push(t),i.qv.push(e))}),i},reset:function(e,i){return this.allFetched=!1,t.Collection.prototype.reset.call(this,e,i)},order:null,comparators:{update_time:i.buildComparator("update_time",{ascending:!1}),"update_time-asc":i.buildComparator("update_time",{ascending:!0}),create_time:i.buildComparator("create_time",{ascending:!1}),"create_time-asc":i.buildComparator("create_time",{ascending:!0})},setOrder:function(t,i){i=i||{};var n=this,s=n.comparators[t];if(e.isUndefined(s))throw new Error("unknown order: "+t);if(s!==n.comparator){n.order;return n.order=t,n.comparator=s,i.silent||n.trigger("changed-order",i),n}}}),s=n.extend({limitPerPage:500,initialize:function(e,t){n.prototype.initialize.call(this,e,t),this.currentPage=t.currentPage||0},getTotalItemCount:function(){return this.length},shouldPaginate:function(){return this.getTotalItemCount()>=this.limitPerPage},getLastPage:function(){return Math.floor(this.getTotalItemCount()/this.limitPerPage)},getPageCount:function(){return this.getLastPage()+1},getPageLimitOffset:function(e){return e=this.constrainPageNum(e),{limit:this.limitPerPage,offset:e*this.limitPerPage}},constrainPageNum:function(e){return Math.max(0,Math.min(e,this.getLastPage()))},fetchPage:function(t,i){var n=this;return t=n.constrainPageNum(t),n.currentPage=t,i=e.defaults(i||{},n.getPageLimitOffset(t)),n.trigger("fetching-more"),n.fetch(i).always(function(){n.trigger("fetching-more-done")})},fetchCurrentPage:function(e){return this.fetchPage(this.currentPage,e)},fetchPrevPage:function(e){return this.fetchPage(this.currentPage-1,e)},fetchNextPage:function(e){return this.fetchPage(this.currentPage+1,e)}}),a=n.extend({limitOnFirstFetch:null,limitPerFetch:100,initialize:function(e,t){n.prototype.initialize.call(this,e,t),this.limitOnFirstFetch=t.limitOnFirstFetch||this.limitOnFirstFetch,this.limitPerFetch=t.limitPerFetch||this.limitPerFetch,this.allFetched=!1,this.lastFetched=t.lastFetched||0},_buildFetchOptions:function(e){return e.remove=e.remove||!1,n.prototype._buildFetchOptions.call(this,e)},fetchFirst:function(t){return t=t?e.clone(t):{},this.allFetched=!1,this.lastFetched=0,this.fetchMore(e.defaults(t,{reset:!0,limit:this.limitOnFirstFetch}))},fetchMore:function(t){t=e.clone(t||{});var i=this;if(!t.reset&&i.allFetched)return o.when();t.offset=t.reset?0:t.offset||i.lastFetched;var n=t.limit=t.limit||i.limitPerFetch||null;return i.trigger("fetching-more"),i.fetch(t).always(function(){i.trigger("fetching-more-done")}).done(function(t){var s=e.isArray(t)?t.length:0;i.lastFetched+=s,(!n||s .controls").add(this.$list()).hide(),e.parentName=this.model.get("name"),this.$el.append(e.render().$el)},_collapseDrilldownPanel:function(e){this.panelStack.pop(),this.render()},events:{"click .navigation .back":"close"},close:function(e){this.remove(),this.trigger("close")},toString:function(){return"CollectionView("+(this.model?this.model.get("name"):"")+")"}});l.prototype.templates=function(){var e=n.wrapTemplate(['
    ','",'
    ','
    <%- collection.name || collection.element_identifier %>
    ','
    ','<% if( collection.collection_type === "list" ){ %>',s("a list of datasets"),'<% } else if( collection.collection_type === "paired" ){ %>',s("a pair of datasets"),'<% } else if( collection.collection_type === "list:paired" ){ %>',s("a list of paired datasets"),'<% } else if( collection.collection_type === "list:list" ){ %>',s("a list of dataset lists"),"<% } %>","
    ","
    ","
    "],"collection");return o.extend(o.clone(r.prototype.templates),{controls:e})}();var c=l.extend({DatasetDCEViewClass:i.DatasetDCEListItemView,toString:function(){return"ListCollectionView("+(this.model?this.model.get("name"):"")+")"}}),d=c.extend({toString:function(){return"PairCollectionView("+(this.model?this.model.get("name"):"")+")"}}),h=l.extend({NestedDCDCEViewClass:i.NestedDCDCEListItemView.extend({foldoutPanelClass:d}),toString:function(){return"ListOfPairsCollectionView("+(this.model?this.model.get("name"):"")+")"}}),u=l.extend({NestedDCDCEViewClass:i.NestedDCDCEListItemView.extend({foldoutPanelClass:d}),toString:function(){return"ListOfListsCollectionView("+(this.model?this.model.get("name"):"")+")"}});return{CollectionView:l,ListCollectionView:c,PairCollectionView:d,ListOfPairsCollectionView:h,ListOfListsCollectionView:u}}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(2))},function(e,t,i){var n,s;(function(o,a){n=[i(12),i(32),i(77),i(66),i(22),i(6),i(5)],s=function(e,t,n,s,r,l,c){"use strict";var d=t.DatasetListItemView,h=d.extend({initialize:function(e){d.prototype.initialize.call(this,e),this.hasUser=e.hasUser,this.purgeAllowed=e.purgeAllowed||!1,this.tagsEditorShown=e.tagsEditorShown||!1,this.annotationEditorShown=e.annotationEditorShown||!1},_renderPrimaryActions:function(){var t=d.prototype._renderPrimaryActions.call(this);return this.model.get("state")===e.NOT_VIEWABLE?t:d.prototype._renderPrimaryActions.call(this).concat([this._renderEditButton(),this._renderDeleteButton()])},_renderEditButton:function(){if(this.model.get("state")===e.DISCARDED||!this.model.get("accessible"))return null;var t=this.model.get("purged"),i=this.model.get("deleted"),n={title:c("Edit attributes"),href:this.model.urls.edit,target:this.linkTarget,faIcon:"fa-pencil",classes:"edit-btn"};return i||t?(n.disabled=!0,t?n.title=c("Cannot edit attributes of datasets removed from disk"):i&&(n.title=c("Undelete dataset to edit attributes"))):o.contains([e.UPLOAD,e.NEW],this.model.get("state"))&&(n.disabled=!0,n.title=c("This dataset is not yet editable")),r(n)},_renderDeleteButton:function(){if(!this.model.get("accessible"))return null;var e=this,t=this.model.isDeletedOrPurged();return r({title:c(t?"Dataset is already deleted":"Delete"),disabled:t,faIcon:"fa-times",classes:"delete-btn",onclick:function(){e.$el.find(".icon-btn.delete-btn").trigger("mouseout"),e.model["delete"]()}})},_renderDetails:function(){var t=d.prototype._renderDetails.call(this),i=this.model.get("state");return!this.model.isDeletedOrPurged()&&o.contains([e.OK,e.FAILED_METADATA],i)&&(this._renderTags(t),this._renderAnnotation(t),this._makeDbkeyEditLink(t)),this._setUpBehaviors(t),t},_renderSecondaryActions:function(){var t=d.prototype._renderSecondaryActions.call(this);switch(this.model.get("state")){case e.UPLOAD:case e.NOT_VIEWABLE:return t;case e.ERROR:return t.unshift(this._renderErrButton()),t.concat([this._renderRerunButton()]);case e.OK:case e.FAILED_METADATA:return t.concat([this._renderRerunButton(),this._renderVisualizationsButton()])}return t.concat([this._renderRerunButton()])},_renderErrButton:function(){return r({title:c("View or report this error"),href:this.model.urls.report_error,classes:"report-error-btn",target:this.linkTarget,faIcon:"fa-bug"})},_renderRerunButton:function(){var e=this.model.get("creating_job");if(this.model.get("rerunnable"))return r({title:c("Run this job again"),href:this.model.urls.rerun,classes:"rerun-btn",target:this.linkTarget,faIcon:"fa-refresh",onclick:function(t){t.preventDefault(),!function(){var t=[i(18)];(function(t){var i=new t.View({job_id:e});i.deferred.execute(function(){Galaxy.app.display(i)})}).apply(null,t)}()}})},_renderVisualizationsButton:function(){var e=this.model.get("visualizations");if(this.model.isDeletedOrPurged()||!this.hasUser||!this.model.hasData()||o.isEmpty(e))return null;if(!o.isObject(e[0]))return this.warn("Visualizations have been switched off"),null;var t=a(this.templates.visualizations(e,this));return t.find('[target="galaxy_main"]').attr("target",this.linkTarget),this._addScratchBookFn(t.find(".visualization-link").addBack(".visualization-link")),t},_addScratchBookFn:function(e){e.click(function(e){Galaxy.frame&&Galaxy.frame.active&&(Galaxy.frame.add({title:"Visualization",url:a(this).attr("href")}),e.preventDefault(),e.stopPropagation())})},_renderTags:function(e){if(this.hasUser){var t=this;this.tagsEditor=new n.TagsEditor({model:this.model,el:e.find(".tags-display"),onshowFirstTime:function(){this.render()},onshow:function(){t.tagsEditorShown=!0},onhide:function(){t.tagsEditorShown=!1},$activator:r({title:c("Edit dataset tags"),classes:"tag-btn",faIcon:"fa-tags"}).appendTo(e.find(".actions .right"))}),this.tagsEditorShown&&this.tagsEditor.toggle(!0)}},_renderAnnotation:function(e){if(this.hasUser){var t=this;this.annotationEditor=new s.AnnotationEditor({model:this.model,el:e.find(".annotation-display"),onshowFirstTime:function(){this.render()},onshow:function(){t.annotationEditorShown=!0},onhide:function(){t.annotationEditorShown=!1},$activator:r({title:c("Edit dataset annotation"),classes:"annotate-btn",faIcon:"fa-comment"}).appendTo(e.find(".actions .right"))}),this.annotationEditorShown&&this.annotationEditor.toggle(!0)}},_makeDbkeyEditLink:function(e){if("?"===this.model.get("metadata_dbkey")&&!this.model.isDeletedOrPurged()){var t=a('?').attr("href",this.model.urls.edit).attr("target",this.linkTarget);e.find(".dbkey .value").replaceWith(t)}},events:o.extend(o.clone(d.prototype.events),{"click .undelete-link":"_clickUndeleteLink","click .purge-link":"_clickPurgeLink","click .edit-btn":function(e){this.trigger("edit",this,e)},"click .delete-btn":function(e){this.trigger("delete",this,e)},"click .rerun-btn":function(e){this.trigger("rerun",this,e)},"click .report-err-btn":function(e){this.trigger("report-err",this,e)},"click .visualization-btn":function(e){this.trigger("visualize",this,e)},"click .dbkey a":function(e){this.trigger("edit",this,e)}}),_clickUndeleteLink:function(e){return this.model.undelete(),!1},_clickPurgeLink:function(e){return confirm(c("This will permanently remove the data in your dataset. Are you sure?"))&&this.model.purge(),!1},toString:function(){var e=this.model?this.model+"":"(no model)";return"HDAEditView("+e+")"}});return h.prototype.templates=function(){var e=o.extend({},d.prototype.templates.warnings,{failed_metadata:l.wrapTemplate(['<% if( dataset.state === "failed_metadata" ){ %>','","<% } %>"],"dataset"),deleted:l.wrapTemplate(["<% if( dataset.deleted && !dataset.purged ){ %>",'
    ',c("This dataset has been deleted"),'
    ',c("Undelete it"),"","<% if( view.purgeAllowed ){ %>",'
    ',c("Permanently remove it from disk"),"","<% } %>","
    ","<% } %>"],"dataset")}),t=l.wrapTemplate(["<% if( visualizations.length === 1 ){ %>",'">','',"","<% } else { %>",'","<% } %>"],"visualizations");return o.extend({},d.prototype.templates,{warnings:e,visualizations:t})}(),{DatasetListItemEdit:h}}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(2),i(1))},function(e,t,i){var n,s;(function(o,a,r){n=[i(12),i(6),i(5)],s=function(e,t,i){"use strict";var n="dataset",s=t.SearchableModelMixin,l=o.Model.extend(t.LoggableMixin).extend(t.mixin(s,{_logNamespace:n,defaults:{state:e.NEW,deleted:!1,purged:!1,name:"(unnamed dataset)",accessible:!0,data_type:"",file_ext:"",file_size:0,meta_files:[],misc_blurb:"",misc_info:"",tags:[]},initialize:function(t,i){this.debug(this+"(Dataset).initialize",t,i), +//!! this state is not in trans.app.model.Dataset.states - set it here - +this.get("accessible")||this.set("state",e.NOT_VIEWABLE),this.urls=this._generateUrls(),this._setUpListeners()},_generateUrls:function(){var e=this.get("id");if(!e)return{};var t={purge:"datasets/"+e+"/purge_async",display:"datasets/"+e+"/display/?preview=True",edit:"datasets/"+e+"/edit",download:"datasets/"+e+"/display?to_ext="+this.get("file_ext"),report_error:"dataset/errors?id="+e,rerun:"tool_runner/rerun?id="+e,show_params:"datasets/"+e+"/show_params",visualization:"visualization",meta_download:"dataset/get_metadata_file?hda_id="+e+"&metadata_name="};return a.each(t,function(e,i){t[i]=Galaxy.root+e}),this.urls=t,t},_setUpListeners:function(){this.on("change:state",function(e,t){this.log(this+" has changed state:",e,t),this.inReadyState()&&this.trigger("state:ready",e,t,this.previous("state"))}),this.on("change:id change:file_ext",function(e){this._generateUrls()})},toJSON:function(){var e=o.Model.prototype.toJSON.call(this);return a.extend(e,{urls:this.urls})},isDeletedOrPurged:function(){return this.get("deleted")||this.get("purged")},inReadyState:function(){var t=a.contains(e.READY_STATES,this.get("state"));return this.isDeletedOrPurged()||t},hasDetails:function(){return!this.get("accessible")||this.has("annotation")},hasData:function(){return this.get("file_size")>0},fetch:function(e){var t=this;return o.Model.prototype.fetch.call(this,e).always(function(){t._generateUrls()})},parse:function(e,t){var i=o.Model.prototype.parse.call(this,e,t);return i.create_time&&(i.create_time=new Date(i.create_time)),i.update_time&&(i.update_time=new Date(i.update_time)),i},save:function(e,t){return t=t||{},t.wait=!!a.isUndefined(t.wait)||t.wait,o.Model.prototype.save.call(this,e,t)},"delete":function(e){return this.get("deleted")?r.when():this.save({deleted:!0},e)},undelete:function(e){return!this.get("deleted")||this.get("purged")?r.when():this.save({deleted:!1},e)},purge:function(e){if(this.get("purged"))return r.when();e=e||{},e.url=this.urls.purge;var t=this,n=r.ajax(e);return n.done(function(e,i,n){t.set({deleted:!0,purged:!0})}),n.fail(function(n,s,o){var a=i("Unable to purge dataset"),r="Removal of datasets by users is not allowed in this Galaxy instance";n.responseJSON&&n.responseJSON.error?a=n.responseJSON.error:n.responseText.indexOf(r)!==-1&&(a=r),n.responseText=a,t.trigger("error",t,n,e,i(a),{error:a})}),n},searchAttributes:["name","file_ext","genome_build","misc_blurb","misc_info","annotation","tags"],searchAliases:{title:"name",format:"file_ext",database:"genome_build",blurb:"misc_blurb",description:"misc_blurb",info:"misc_info",tag:"tags"},toString:function(){var e=this.get("id")||"";return this.get("name")&&(e='"'+this.get("name")+'",'+e),"Dataset("+e+")"}})),c=o.Collection.extend(t.LoggableMixin).extend({_logNamespace:n,model:l,urlRoot:Galaxy.root+"api/datasets",url:function(){return this.urlRoot},ids:function(){return this.map(function(e){return e.get("id")})},notReady:function(){return this.filter(function(e){return!e.inReadyState()})},haveDetails:function(){return this.all(function(e){return e.hasDetails()})},ajaxQueue:function(e,t){var i=r.Deferred(),n=this.length,s=[];if(!n)return i.resolve([]),i;var o=this.chain().reverse().map(function(a,r){return function(){var l=e.call(a,t);l.done(function(e){i.notify({curr:r,total:n,response:e,model:a})}),l.always(function(e){s.push(e),o.length?o.shift()():i.resolve(s)})}}).value();return o.shift()(),i},matches:function(e){return this.filter(function(t){return t.matches(e)})},toString:function(){return["DatasetAssociationCollection(",this.length,")"].join("")}});return{DatasetAssociation:l,DatasetAssociationCollection:c}}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(3),i(2),i(1))},function(e,t,i){var n,s;(function(o){n=[i(32),i(6),i(5)],s=function(e,t,i){"use strict";var n=e.DatasetListItemView,s=n.extend({className:n.prototype.className+" history-content",initialize:function(e,t){n.prototype.initialize.call(this,e,t)},toString:function(){var e=this.model?this.model+"":"(no model)";return"HDAListItemView("+e+")"}});return s.prototype.templates=function(){var e=t.wrapTemplate(['
    ','','
    ','<%- dataset.hid %> ','<%- dataset.name %>',"
    ","
    "],"dataset"),s=o.extend({},n.prototype.templates.warnings,{hidden:t.wrapTemplate(["<% if( !dataset.visible ){ %>",'
    ',i("This dataset has been hidden"),"
    ","<% } %>"],"dataset")});return o.extend({},n.prototype.templates,{titleBar:e,warnings:s})}(),{HDAListItemView:s}}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(2))},function(e,t,i){var n,s;(function(o){n=[i(70),i(74),i(6),i(5)],s=function(e,t,i,n){"use strict";var s=e.DatasetAssociation,a=t.HistoryContentMixin,r=s.extend(i.mixin(a,{defaults:o.extend({},s.prototype.defaults,a.defaults,{history_content_type:"dataset",model_class:"HistoryDatasetAssociation"})}));return{HistoryDatasetAssociation:r}}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(2))},function(e,t,i){var n,s;(function(o){n=[i(12),i(29),i(68),i(6),i(5)],s=function(e,t,i,n,s){"use strict";var a=t.DCListItemView,r=a.extend({className:a.prototype.className+" history-content",_setUpListeners:function(){a.prototype._setUpListeners.call(this),this.listenTo(this.model,{"change:populated change:visible":function(e,t){this.render()}})},_getFoldoutPanelClass:function(){switch(this.model.get("collection_type")){case"list":return i.ListCollectionView;case"paired":return i.PairCollectionView;case"list:paired":return i.ListOfPairsCollectionView;case"list:list":return i.ListOfListsCollectionView}throw new TypeError("Uknown collection_type: "+this.model.get("collection_type"))},_swapNewRender:function(t){a.prototype._swapNewRender.call(this,t);var i=this.model.get("populated")?e.OK:e.RUNNING;return this.$el.addClass("state-"+i),this.$el},toString:function(){var e=this.model?this.model+"":"(no model)";return"HDCAListItemView("+e+")"}});return r.prototype.templates=function(){var e=o.extend({},a.prototype.templates.warnings,{hidden:n.wrapTemplate(["<% if( !collection.visible ){ %>",'
    ',s("This collection has been hidden"),"
    ","<% } %>"],"collection")}),t=n.wrapTemplate(['
    ','','
    ','<%- collection.hid %> ','<%- collection.name %>',"
    ",'
    ',"
    "],"collection");return o.extend({},a.prototype.templates,{warnings:e,titleBar:t})}(),{HDCAListItemView:r}}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(2))},function(e,t,i){var n,s;(function(o){n=[i(12),i(6),i(5)],s=function(e,t,i){"use strict";var n={defaults:{history_id:null,history_content_type:null,hid:null,visible:!0},idAttribute:"type_id",hidden:function(){return!this.get("visible")},isVisible:function(e,t){var i=!0;return e||!this.get("deleted")&&!this.get("purged")||(i=!1),t||this.get("visible")||(i=!1),i},urlRoot:Galaxy.root+"api/histories/",url:function(){var e=this.urlRoot+this.get("history_id")+"/contents/"+this.get("history_content_type")+"s/"+this.get("id");return e},hide:function(e){return this.get("visible")?this.save({visible:!1},e):o.when()},unhide:function(e){return this.get("visible")?o.when():this.save({visible:!0},e)},toString:function(){return[this.get("type_id"),this.get("hid"),this.get("name")].join(":")}};return{HistoryContentMixin:n}}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(1))},function(e,t,i){var n,s;(function(o,a,r,l){n=[i(40),i(41),i(67),i(4),i(6),i(5)],s=function(e,t,i,n,s,c){"use strict";var d=o.Model.extend(s.LoggableMixin).extend(s.mixin(s.SearchableModelMixin,{_logNamespace:"history",UPDATE_DELAY:4e3,defaults:{model_class:"History",id:null,name:"Unnamed History",state:"new",deleted:!1,contents_active:{},contents_states:{}},urlRoot:Galaxy.root+"api/histories",contentsClass:e.HistoryContents,searchAttributes:["name","annotation","tags"],searchAliases:{title:"name",tag:"tags"},initialize:function(e,t){t=t||{},this.logger=t.logger||null,this.log(this+".initialize:",e,t),this.contents=new this.contentsClass([],{history:this,historyId:this.get("id"),order:t.order}),this._setUpListeners(),this._setUpCollectionListeners(),this.updateTimeoutId=null},_setUpListeners:function(){return this.on({error:function(e,t,i,n,s){this.clearUpdateTimeout()},"change:id":function(e,t){this.contents&&(this.contents.historyId=t)}})},_setUpCollectionListeners:function(){return this.contents?this.listenTo(this.contents,{error:function(){this.trigger.apply(this,a.makeArray(arguments))}}):this},contentsShown:function(){var e=this.get("contents_active"),t=e.active||0;return t+=this.contents.includeDeleted?e.deleted:0,t+=this.contents.includeHidden?e.hidden:0},nice_size:function(){var e=this.get("size");return e?n.bytesToString(e,!0,2):c("(empty)")},toJSON:function(){return r.extend(o.Model.prototype.toJSON.call(this),{nice_size:this.nice_size()})},get:function(e){return"nice_size"===e?this.nice_size():o.Model.prototype.get.apply(this,arguments)},ownedByCurrUser:function(){return!(!Galaxy||!Galaxy.user)&&(!Galaxy.user.isAnonymous()&&Galaxy.user.id===this.get("user_id"))},numOfUnfinishedJobs:function(){var e=this.get("non_ready_jobs");return e?e.length:0},numOfUnfinishedShownContents:function(){return this.contents.runningAndActive().length||0},_fetchContentRelatedAttributes:function(){var e=["size","non_ready_jobs","contents_active","hid_counter"];return this.fetch({data:l.param({keys:e.join(",")})})},refresh:function(e){e=e||{};var t=this,i=t.lastUpdateTime;this.contents.allFetched=!1;var n=0!==t.contents.currentPage?function(){return t.contents.fetchPage(0)}:function(){return t.contents.fetchUpdated(i)};return n().done(function(i,n,s){var o;try{o=new Date(s.getResponseHeader("Date"))}catch(a){}t.lastUpdateTime=o||new Date,t.checkForUpdates(e)})},checkForUpdates:function(e){function t(){n.clearUpdateTimeout(),n.updateTimeoutId=setTimeout(function(){n.refresh(e)},i)}e=e||{};var i=this.UPDATE_DELAY,n=this;if(n.id){var s=this.numOfUnfinishedShownContents();s>0?t():n._fetchContentRelatedAttributes().done(function(e){n.numOfUnfinishedJobs()>0?t():n.trigger("ready")})}},clearUpdateTimeout:function(){this.updateTimeoutId&&(clearTimeout(this.updateTimeoutId),this.updateTimeoutId=null)},parse:function(e,t){var i=o.Model.prototype.parse.call(this,e,t);return i.create_time&&(i.create_time=new Date(i.create_time)),i.update_time&&(i.update_time=new Date(i.update_time)),i},fetchWithContents:function(e,t){e=e||{};var i=this;return e.view="dev-detailed",this.fetch(e).then(function(e){return i.contents.history=i,i.contents.setHistoryId(e.id),i.fetchContents(t)})},fetchContents:function(e){e=e||{};var t=this;return t.lastUpdateTime=new Date,t.contents.fetchCurrentPage(e)},_delete:function(e){return this.get("deleted")?a.when():this.save({deleted:!0},e)},purge:function(e){return this.get("purged")?a.when():this.save({deleted:!0,purged:!0},e)},undelete:function(e){return this.get("deleted")?this.save({deleted:!1},e):a.when()},copy:function(e,t,i){if(e=void 0===e||e,!this.id)throw new Error("You must set the history ID before copying it.");var n={history_id:this.id};e&&(n.current=!0),t&&(n.name=t),i||(n.all_datasets=!1),n.view="dev-detailed";var s=this,o=a.post(this.urlRoot,n);return e?o.then(function(e){var t=new d(e);return t.setAsCurrent().done(function(){s.trigger("copied",s,e)})}):o.done(function(e){s.trigger("copied",s,e)})},setAsCurrent:function(){var e=this,t=a.getJSON(Galaxy.root+"history/set_as_current?id="+this.id);return t.done(function(){e.trigger("set-as-current",e)}),t},toString:function(){return"History("+this.get("id")+","+this.get("name")+")"}})),h=i.InfinitelyScrollingCollection,u=h.extend(s.LoggableMixin).extend({_logNamespace:"history",model:d,order:"update_time",limitOnFirstFetch:10,limitPerFetch:10,initialize:function(e,t){t=t||{},this.log("HistoryCollection.initialize",e,t),h.prototype.initialize.call(this,e,t),this.includeDeleted=t.includeDeleted||!1,this.currentHistoryId=t.currentHistoryId,this.setUpListeners()},urlRoot:Galaxy.root+"api/histories",url:function(){return this.urlRoot},setUpListeners:function(){return this.on({"change:deleted":function(e){this.debug("change:deleted",this.includeDeleted,e.get("deleted")),!this.includeDeleted&&e.get("deleted")&&this.remove(e)},copied:function(e,t){this.setCurrent(new d(t,[]))},"set-as-current":function(e){var t=this.currentHistoryId;this.trigger("no-longer-current",t),this.currentHistoryId=e.id}})},_buildFetchData:function(e){return r.extend(h.prototype._buildFetchData.call(this,e),{view:"dev-detailed"})},_buildFetchFilters:function(e){var t=h.prototype._buildFetchFilters.call(this,e)||{},i={};return this.includeDeleted?i.deleted=null:(i.deleted=!1,i.purged=!1),r.defaults(t,i)},fetchFirst:function(e){var t=this,i=l.when();return this.currentHistoryId&&(i=h.prototype.fetchFirst.call(t,{silent:!0,limit:1,filters:{purged:"",deleted:"","encoded_id-in":this.currentHistoryId}})),i.then(function(){return e=e||{},e.offset=0,t.fetchMore(e)})},comparators:r.extend(r.clone(h.prototype.comparators),{name:s.buildComparator("name",{ascending:!0}),"name-dsc":s.buildComparator("name",{ascending:!1}),size:s.buildComparator("size",{ascending:!1}),"size-asc":s.buildComparator("size",{ascending:!0})}),sort:function(e){e=e||{};var t=e.silent,i=this.remove(this.get(this.currentHistoryId));return h.prototype.sort.call(this,r.defaults({silent:!0},e)),this.unshift(i,{silent:!0}),t||this.trigger("sort",this,e),this},create:function(e,t,i,n){var s=this,o=a.getJSON(Galaxy.root+"history/create_new_current");return o.done(function(e){s.setCurrent(new d(e,[],i||{}))})},setCurrent:function(e,t){return t=t||{},this.unshift(e,t),this.currentHistoryId=e.get("id"),t.silent||this.trigger("new-current",e,this),this},toString:function(){return"HistoryCollection("+this.length+",current:"+this.currentHistoryId+")"}});return{History:d,HistoryCollection:u}}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(3),i(1),i(2),i(1))},function(e,t,i){var n,s;(function(o,a,r){n=[i(42),i(127),i(6),i(5),i(85)],s=function(e,t,i,n){"use strict";var s="list",l=o.View.extend(i.LoggableMixin).extend({_logNamespace:s,viewClass:e.ListItemView,collectionClass:o.Collection,tagName:"div",className:"list-panel",fxSpeed:"fast",emptyMsg:n("This list is empty"),noneFoundMsg:n("No matching items found"),searchPlaceholder:n("search"),initialize:function(e,t){e=e||{},e.logger&&(this.logger=e.logger),this.log(this+".initialize:",e),this.fxSpeed=a.has(e,"fxSpeed")?e.fxSpeed:this.fxSpeed,this.filters=[],this.searchFor=e.searchFor||"",this.selecting=void 0===e.selecting||e.selecting,this.selected=e.selected||[],this.lastSelected=null,this.dragItems=e.dragItems||!1,this.viewClass=e.viewClass||this.viewClass,this.views=[],this.collection=e.collection||this._createDefaultCollection(),this.filters=e.filters||[],this.$scrollContainer=e.$scrollContainer||this.$scrollContainer,this.title=e.title||"",this.subtitle=e.subtitle||"",this._setUpListeners()},_setUpListeners:function(){return this.off(),this.on({error:function(e,t,i,n,s){console.error(e,t,i,n,s)},loading:function(){this._showLoadingIndicator("loading...",40)},"loading-done":function(){this._hideLoadingIndicator(40)}}),this.once("rendered",function(){this.trigger("rendered:initial",this)}),this._setUpCollectionListeners(),this._setUpViewListeners(),this},_createDefaultCollection:function(){return new this.collectionClass([])},_setUpCollectionListeners:function(){return this.log(this+"._setUpCollectionListeners",this.collection),this.stopListening(this.collection),this.listenTo(this.collection,{error:function(e,t,i,n,s){this.trigger("error",e,t,i,n,s)},update:function(e,t){var i=t.changes;return t.renderAll||i.added.length+i.removed.length>1?this.renderItems():1===i.added.length?this.addItemView(a.first(i.added),e,t):1===i.removed.length?this.removeItemView(a.first(i.removed),e,t):void 0}}),this},_setUpViewListeners:function(){this.log(this+"._setUpViewListeners"),this.on({"view:selected":function(e,t){if(t&&t.shiftKey&&this.lastSelected){var i=this.viewFromModelId(this.lastSelected);i&&this.selectRange(e,i)}else t&&t.altKey&&!this.selecting&&this.showSelectors();this.selected.push(e.model.id),this.lastSelected=e.model.id},"view:de-selected":function(e,t){this.selected=a.without(this.selected,e.model.id)}})},render:function(e){this.log(this+".render",e);var t=this._buildNewRender();return this._setUpBehaviors(t),this._queueNewRender(t,e),this},_buildNewRender:function(){this.debug(this+"(ListPanel)._buildNewRender");var e=r(this.templates.el({},this));return this._renderControls(e),this._renderTitle(e),this._renderSubtitle(e),this._renderSearch(e),this.renderItems(e),e},_renderControls:function(e){this.debug(this+"(ListPanel)._renderControls");var t=r(this.templates.controls({},this));return e.find(".controls").replaceWith(t),t},_renderTitle:function(e){},_renderSubtitle:function(e){},_queueNewRender:function(e,t){t=void 0===t?this.fxSpeed:t;var i=this;i.log("_queueNewRender:",e,t),r(i).queue("fx",[function(e){i.$el.fadeOut(t,e)},function(t){i._swapNewRender(e),t()},function(e){i.$el.fadeIn(t,e)},function(e){i.trigger("rendered",i),e()}])},_swapNewRender:function(e){return this.$el.empty().attr("class",this.className).append(e.children()),this.selecting&&this.showSelectors(0),this},_setUpBehaviors:function(e){return e=e||this.$el,this.$controls(e).find("[title]").tooltip(),this._renderMultiselectActionMenu(e),this},_renderMultiselectActionMenu:function(e){e=e||this.$el;var t=e.find(".list-action-menu"),i=this.multiselectActions();if(!i.length)return t.empty();var s=r(['
    ','",'","
    "].join("")),o=i.map(function(e){var t=['
  • ',e.html,"
  • "].join("");return r(t).click(function(t){return t.preventDefault(),e.func(t)})});return s.find("ul").append(o),t.replaceWith(s),s},multiselectActions:function(){return[]},$scrollContainer:function(e){return(e||this.$el).parent().parent()},$controls:function(e){return(e||this.$el).find("> .controls")},$list:function(e){return(e||this.$el).find("> .list-items")},$messages:function(e){return(e||this.$el).find("> .controls .messages")},$emptyMessage:function(e){return(e||this.$el).find("> .empty-message")},renderItems:function(e){e=e||this.$el;var t=this;t.log(this+".renderItems",e);var i=t.$list(e);t.freeViews();var n=t._filterCollection();return t.views=n.map(function(e){var i=t._createItemView(e);return i}),i.empty(),t.views.length&&t._attachItems(e),t._renderEmptyMessage(e).toggle(!t.views.length),t.trigger("views:ready",t.views),t.views},_filterCollection:function(){var e=this;return e.collection.filter(a.bind(e._filterItem,e))},_filterItem:function(e){var t=this;return a.every(t.filters.map(function(t){return t.call(e)}))&&(!t.searchFor||e.matchesAll(t.searchFor))},_createItemView:function(e){var t=this._getItemViewClass(e),i=a.extend(this._getItemViewOptions(e),{model:e}),n=new t(i);return this._setUpItemViewListeners(n),n},_destroyItemView:function(e){this.stopListening(e),this.views=a.without(this.views,e)},_destroyItemViews:function(e){var t=this;return t.views.forEach(function(e){t.stopListening(e)}),t.views=[],t},freeViews:function(){return this._destroyItemViews()},_getItemViewClass:function(e){return this.viewClass},_getItemViewOptions:function(e){return{fxSpeed:this.fxSpeed,expanded:!1,selectable:this.selecting,selected:a.contains(this.selected,e.id),draggable:this.dragItems}},_setUpItemViewListeners:function(e){var t=this;return this.listenTo(e,"all",function(){var e=Array.prototype.slice.call(arguments,0);e[0]="view:"+e[0],t.trigger.apply(t,e)}),this.listenTo(e,"draggable:dragstart",function(e,t){var i={},n=this.getSelectedModels();i=n.length?n.toJSON():[t.model.toJSON()],e.dataTransfer.setData("text",JSON.stringify(i))},this),t},_attachItems:function(e){var t=this;return this.$list(e).append(this.views.map(function(e){return t._renderItemView$el(e)})),this},_renderItemView$el:function(e){return e.render(0).$el},_renderEmptyMessage:function(e){this.debug("_renderEmptyMessage",e,this.searchFor);var t=this.searchFor?this.noneFoundMsg:this.emptyMsg;return this.$emptyMessage(e).text(t)},expandAll:function(){a.each(this.views,function(e){e.expand()})},collapseAll:function(){a.each(this.views,function(e){e.collapse()})},addItemView:function(e,t,i){var n=this,s=n._filterCollection().indexOf(e);if(s!==-1){var o=n._createItemView(e);return r(o).queue("fx",[function(e){n.$emptyMessage().is(":visible")?n.$emptyMessage().fadeOut(n.fxSpeed,e):e()},function(e){n._attachView(o,s),e()}]),o}},_attachView:function(e,t,i){i=!!a.isUndefined(i)||i,t=t||0;var n=this;return n.views.splice(t,0,e),n._insertIntoListAt(t,n._renderItemView$el(e).hide()),n.trigger("view:attached",e),i?e.$el.slideDown(n.fxSpeed,function(){n.trigger("view:attached:rendered")}):(e.$el.show(),n.trigger("view:attached:rendered")),e},_insertIntoListAt:function(e,t){var i=this.$list();return 0===e?i.prepend(t):i.children().eq(e-1).after(t),t},removeItemView:function(e,t,i){var n=this,s=a.find(n.views,function(t){return t.model===e});if(s)return n.views=a.without(n.views,s),n.trigger("view:removed",s),r({}).queue("fx",[function(e){s.$el.fadeOut(n.fxSpeed,e)},function(e){s.remove(),n.trigger("view:removed:rendered"),n.views.length?e():n._renderEmptyMessage().fadeIn(n.fxSpeed,e)}]),s},viewFromModelId:function(e){return a.find(this.views,function(t){return t.model.id===e})},viewFromModel:function(e){return e?this.viewFromModelId(e.id):void 0},viewsWhereModel:function(e){return this.views.filter(function(t){return a.isMatch(t.model.attributes,e)})},viewRange:function(e,t){if(e===t)return e?[e]:[];var i=this.views.indexOf(e),n=this.views.indexOf(t);return i===-1||n===-1?i===n?[]:i===-1?[t]:[e]:i .controls .search-query");return i.val()!==e&&i.val(e),this},clearSearch:function(e){return this.searchFor="",this.trigger("search:clear",this),this.$("> .controls .search-query").val(""),this.renderItems(),this},THROTTLE_SELECTOR_FX_AT:20,showSelectors:function(e){e=void 0!==e?e:this.fxSpeed,this.selecting=!0,this.$(".list-actions").slideDown(e),e=this.views.length>=this.THROTTLE_SELECTOR_FX_AT?0:e,a.each(this.views,function(t){t.showSelector(e)})},hideSelectors:function(e){e=void 0!==e?e:this.fxSpeed,this.selecting=!1,this.$(".list-actions").slideUp(e),e=this.views.length>=this.THROTTLE_SELECTOR_FX_AT?0:e,a.each(this.views,function(t){t.hideSelector(e)}),this.selected=[],this.lastSelected=null},toggleSelectors:function(){this.selecting?this.hideSelectors():this.showSelectors()},selectAll:function(e){a.each(this.views,function(t){t.select(e)})},deselectAll:function(e){this.lastSelected=null,a.each(this.views,function(t){t.deselect(e)})},selectRange:function(e,t){var i=this.viewRange(e,t);return a.each(i,function(e){e.select()}),i},getSelectedViews:function(){return a.filter(this.views,function(e){return e.selected})},getSelectedModels:function(){return new this.collection.constructor(a.map(this.getSelectedViews(),function(e){return e.model}))},_showLoadingIndicator:function(e,i,n){this.debug("_showLoadingIndicator",this.indicator,e,i,n),i=void 0!==i?i:this.fxSpeed,this.indicator||(this.indicator=new t(this.$el),this.debug("\t created",this.indicator)),this.$el.is(":visible")?(this.$el.fadeOut(i),this.indicator.show(e,i,n)):this.indicator.show(0,n)},_hideLoadingIndicator:function(e,t){this.debug("_hideLoadingIndicator",this.indicator,e,t),e=void 0!==e?e:this.fxSpeed,this.indicator&&this.indicator.hide(e,t)},scrollPosition:function(){return this.$scrollContainer().scrollTop()},scrollTo:function(e,t){return t=t||0,this.$scrollContainer().animate({scrollTop:e},t),this},scrollToTop:function(e){return this.scrollTo(0,e)},scrollToItem:function(e,t){return e?this:this},scrollToId:function(e,t){return this.scrollToItem(this.viewFromModelId(e),t)},events:{"click .select-all":"selectAll","click .deselect-all":"deselectAll"},toString:function(){return"ListPanel("+this.collection+")"}});l.prototype.templates=function(){var e=i.wrapTemplate(["
    ",'
    ','
    ','
    ',"
    "]),t=i.wrapTemplate(['
    ','
    ','
    <%- view.title %>
    ',"
    ",'
    <%- view.subtitle %>
    ','
    ','
    ','",'
    ','
    ','",'","
    ",'
    ',"
    ","
    ","
    "]);return{el:e,controls:t}}();var c=l.extend({modelCollectionKey:"contents",initialize:function(e){l.prototype.initialize.call(this,e),this.selecting=void 0!==e.selecting&&e.selecting,this.setModel(this.model,e)},setModel:function(e,t){if(t=t||{},this.debug(this+".setModel:",e,t),this.freeModel(),this.freeViews(),e){var i=this.model?this.model.get("id"):null;this.model=e,this.logger&&(this.model.logger=this.logger),this._setUpModelListeners(),this.stopListening(this.collection),this.collection=this.model[this.modelCollectionKey]||t.collection||this._createDefaultCollection(),this._setUpCollectionListeners(),i&&e.get("id")!==i&&this.trigger("new-model",this)}return this},freeModel:function(){return this.model&&this.stopListening(this.model),this},_setUpModelListeners:function(){return this.log(this+"._setUpModelListeners",this.model),this.listenTo(this.model,"error",function(){var e=Array.prototype.slice.call(arguments,0);e.unshift("error"),this.trigger.apply(this,e)},this),this.logger&&this.listenTo(this.model,"all",function(e){this.info(this+"(model)",e,arguments)}),this},_renderControls:function(e){this.debug(this+"(ModelListPanel)._renderControls");var t=this.model?this.model.toJSON():{},i=r(this.templates.controls(t,this));return e.find(".controls").replaceWith(i),i},toString:function(){return"ModelListPanel("+this.model+")"}});return c.prototype.templates=function(){var e=i.wrapTemplate(['
    ','
    ','
    <%- model.name %>
    ',"
    ",'
    <%- view.subtitle %>
    ','
    ','
    ','",'
    ','
    ','",'","
    ",'
    ',"
    ","
    ","
    "]);return a.extend(a.clone(l.prototype.templates),{controls:e})}(),{ListPanel:l,ModelListPanel:c}}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(3),i(2),i(1))},function(e,t,i){var n,s;(function(o,a){n=[i(6),i(5)],s=function(e,t){var i=o.View.extend(e.LoggableMixin).extend(e.HiddenUntilActivatedViewMixin).extend({tagName:"div",className:"tags-display",initialize:function(e){this.listenTo(this.model,"change:tags",function(){this.render()}),this.hiddenUntilActivated(e.$activator,e)},render:function(){var e=this;return this.$el.html(this._template()),this.$input().select2({placeholder:"Add tags",width:"100%",tags:function(){return e._getTagsUsed()}}),this._setUpBehaviors(),this},_template:function(){return['",''].join("")},tagsToCSV:function(){var e=this.model.get("tags");return!a.isArray(e)||a.isEmpty(e)?"":e.map(function(e){return a.escape(e)}).sort().join(",")},$input:function(){return this.$el.find("input.tags-input")},_getTagsUsed:function(){return Galaxy.user.get("tags_used")},_setUpBehaviors:function(){var e=this;this.$input().on("change",function(t){e.model.save({tags:t.val},{silent:!0}),t.added&&e._addNewTagToTagsUsed(t.added.text+"")})},_addNewTagToTagsUsed:function(e){var t=Galaxy.user.get("tags_used");a.contains(t,e)||(t.push(e),t.sort(),Galaxy.user.set("tags_used",t))},remove:function(){this.$input.off(),this.stopListening(this.model),o.View.prototype.remove.call(this)},toString:function(){return["TagsEditor(",this.model+"",")"].join("")}});return{TagsEditor:i}}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(3),i(2))},function(e,t,i){var n,s;(function(o,a){n=[i(5)],s=function(e){"use strict";function t(t,i,n){return Galaxy.modal.show({title:i,body:t,closing_events:!0,buttons:{Ok:function(){Galaxy.modal.hide()}}}),Galaxy.modal.$el.addClass("error-modal"),n&&(Galaxy.modal.$(".error-details").add(Galaxy.modal.$('button:contains("Details")')).remove(),o("
    ").addClass("error-details").hide().appendTo(Galaxy.modal.$(".modal-content")).append([o("

    ").text(h),o("

    ").text(JSON.stringify(n,null,"  "))]),o('").appendTo(Galaxy.modal.$(".buttons")).click(function(){Galaxy.modal.$(".error-details").toggle()})),Galaxy.modal}function i(i,n,s){if(i){if(i=e(i),n=e(n)||e("Error:"),window.Galaxy&&Galaxy.modal)return t(i,n,s);alert(n+"\n\n"+i),console.log("error details:",JSON.stringify(s))}}function n(){return i(e("You appear to be offline. Please check your connection and try again."),e("Offline?"))}function s(){return i(e("Galaxy is currently unreachable. Please try again in a few minutes.")+" "+c,e("Cannot connect to Galaxy"))}function r(t,n,s,o,a){o=o||d,o+=" "+c,a=a||e("An error occurred");var r=l(t,n,s);return i(o,a,r)}function l(e,t,i){return{raven:a.result(window.Raven,"lastEventId"),userAgent:navigator.userAgent,onLine:navigator.onLine,version:a.result(Galaxy.config,"version_major"),xhr:a.omit(t,a.functions(t)),options:a.omit(i,"xhr"),url:a.result(Galaxy.lastAjax,"url"),data:a.result(Galaxy.lastAjax,"data"),model:a.result(e,"toJSON",e+""),user:a.omit(a.result(Galaxy.user,"toJSON"),"email")}}var c=e("Please contact a Galaxy administrator if the problem persists."),d=e("An error occurred while updating information with the server."),h=e("The following information can assist the developers in finding the source of the error:");return{errorModal:i,offlineErrorModal:n,badGatewayErrorModal:s,ajaxErrorModal:r}}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(1),i(2))},function(e,t,i){var n,s;(function(i,o,a,r){n=[],s=function(){var e=i.View.extend({initialize:function(e,t){this.$button=e,this.$button.length||(this.$button=o("
    ")),this.options=t||[],this.$button.data("popupmenu",this);var i=this;this.$button.click(function(e){return o(".popmenu-wrapper").remove(),i._renderAndShow(e),!1})},_renderAndShow:function(e){this.render(),this.$el.appendTo("body").css(this._getShownPosition(e)).show(),this._setUpCloseBehavior()},render:function(){if(this.$el.addClass("popmenu-wrapper").hide().css({position:"absolute"}).html(this.template(this.$button.attr("id"),this.options)), +this.options.length){var e=this;this.$el.find("li").each(function(t,i){var n=e.options[t];n.func&&o(this).children("a.popupmenu-option").click(function(t){n.func.call(e,t,n),t.preventDefault()})})}return this},template:function(e,t){return['"].join("")},_templateOptions:function(e){return e.length?a.map(e,function(e){if(e.divider)return'
  • ';if(e.header)return['
  • ',e.html,"
  • "].join("");var t=e.href||"javascript:void(0);",i=e.target?' target="'+e.target+'"':"",n=e.checked?'':"";return['
  • ",n,e.html,"
  • "].join("")}).join(""):"
  • (no options)
  • "},_getShownPosition:function(e){var t=this.$el.width(),i=e.pageX-t/2;return i=Math.min(i,o(document).scrollLeft()+o(window).width()-t-5),i=Math.max(i,o(document).scrollLeft()+5),{top:e.pageY,left:i}},_setUpCloseBehavior:function(){function e(e){if(o(document).off("click.close_popup"),window&&window.parent!==window)try{o(window.parent.document).off("click.close_popup")}catch(i){}else try{o("iframe#galaxy_main").contents().off("click.close_popup")}catch(i){}t.remove()}var t=this;if(o("html").one("click.close_popup",e),window&&window.parent!==window)try{o(window.parent.document).find("html").one("click.close_popup",e)}catch(i){}else try{o("iframe#galaxy_main").contents().one("click.close_popup",e)}catch(i){}},addItem:function(e,t){return t=t>=0?t:this.options.length,this.options.splice(t,0,e),this},removeItem:function(e){return e>=0&&this.options.splice(e,1),this},findIndexByHtml:function(e){for(var t=0;t0){this.$(".upload-ftp-content").html(a(this._templateTable()));var i=0;for(index in t)this.rows.push(this._add(t[index])),i+=t[index].size;if(this.$(".upload-ftp-number").html(t.length+" files"),this.$(".upload-ftp-disk").html(e.bytesToString(i,!0)),this.collection){var n=this;this.$("._has_collection").show(),this.$select_all=this.$(".upload-selectall").addClass(this.options.class_add),this.$select_all.on("click",function(){var e=n.$select_all.hasClass(n.options.class_add);for(index in t){var i=t[index],s=n._find(i);(!s&&e||s&&!e)&&n.rows[index].trigger("click")}}),this._refresh()}}else this.$(".upload-ftp-content").html(a(this._templateInfo()));this.$(".upload-ftp-wait").hide()},_add:function(e){var t=this,i=a(this._templateRow(e)),n=i.find(".icon");return this.$("tbody").append(i),this.collection?(n.addClass(this._find(e)?this.options.class_remove:this.options.class_add),i.on("click",function(){var i=t._find(e);n.removeClass(),i?(t.options.onremove(i),n.addClass(t.options.class_add)):(t.options.onadd(e),n.addClass(t.options.class_remove)),t._refresh()})):i.on("click",function(){t.options.onchange(e)}),i},_refresh:function(){var e=this.collection.where({file_mode:"ftp",enabled:!0});this.$select_all.removeClass(),0==e.length?this.$select_all.addClass(this.options.class_add):this.$select_all.addClass(e.length==this.rows.length?this.options.class_remove:this.options.class_partial)},_find:function(e){var t=this.collection.findWhere({file_path:e.path,file_mode:"ftp",enabled:!0});return t&&t.get("id")},_templateRow:function(t){return'
    '+t.path+''+e.bytesToString(t.size)+''+t.ctime+""},_templateTable:function(){return'Available files:   
    NameSizeCreated
    '},_templateInfo:function(){return'
    Your FTP directory does not contain any files.
    '},_template:function(){return'
    This Galaxy server allows you to upload files via FTP. To upload some files, log in to the FTP server at '+this.options.ftp_upload_site+' using your Galaxy credentials (email address and password).
    '}})}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(3),i(1))},function(e,t,i){var n,s;(function(o,a,r){n=[i(4)],s=function(e){return o.View.extend({options:{class_check:"fa-check-square-o",class_uncheck:"fa-square-o",parameters:[{id:"space_to_tab",title:"Convert spaces to tabs"},{id:"to_posix_lines",title:"Use POSIX standard"}]},initialize:function(e){this.model=e.model,this.setElement(a("
    ").addClass("upload-settings")),this.$el.append(a("
    ").addClass("upload-settings-cover")),this.$el.append(a("").addClass("upload-settings-table ui-table-striped").append("")),this.$cover=this.$(".upload-settings-cover"),this.$table=this.$(".upload-settings-table > tbody"),this.listenTo(this.model,"change",this.render,this),this.model.trigger("change")},render:function(){var e=this;this.$table.empty(),r.each(this.options.parameters,function(t){var i=a("
    ").addClass("upload-"+t.id+" upload-icon-button fa").addClass(e.model.get(t.id)&&e.options.class_check||e.options.class_uncheck).on("click",function(){e.model.get("enabled")&&e.model.set(t.id,!e.model.get(t.id))});e.$table.append(a("
    ").append(a("');\n\t wrapper.append($el);\n\t this.row.append(wrapper);\n\t },\n\t \n\t // header\n\t appendHeader: function() {\n\t // append header row\n\t this.$thead.append(this.row);\n\t\n\t // row\n\t this.row = $('');\n\t },\n\t \n\t // add row cell\n\t add: function($el, width, align) {\n\t var wrapper = $('');\n\t if (width) {\n\t wrapper.css('width', width);\n\t }\n\t if (align) {\n\t wrapper.css('text-align', align);\n\t }\n\t wrapper.append($el);\n\t this.row.append(wrapper);\n\t },\n\t \n\t // append\n\t append: function(id, fade) {\n\t this._commit(id, fade, false);\n\t },\n\t \n\t // prepend\n\t prepend: function(id, fade) {\n\t this._commit(id, fade, true);\n\t },\n\t \n\t // get element\n\t get: function(id) {\n\t return this.$el.find('#' + id);\n\t },\n\t \n\t // delete\n\t del: function(id) {\n\t var item = this.$tbody.find('#' + id);\n\t if (item.length > 0) {\n\t item.remove();\n\t this.row_count--;\n\t this._refresh();\n\t }\n\t },\n\t\n\t // delete all\n\t delAll: function() {\n\t this.$tbody.empty();\n\t this.row_count = 0;\n\t this._refresh();\n\t },\n\t \n\t // value\n\t value: function(new_value) {\n\t // get current id/value\n\t this.before = this.$tbody.find('.current').attr('id');\n\t \n\t // check if new_value is defined\n\t if (new_value !== undefined) {\n\t this.$tbody.find('tr').removeClass('current');\n\t if (new_value) {\n\t this.$tbody.find('#' + new_value).addClass('current');\n\t }\n\t }\n\t \n\t // get current id/value\n\t var after = this.$tbody.find('.current').attr('id');\n\t if(after === undefined) {\n\t return null;\n\t } else {\n\t // fire onchange\n\t if (after != this.before && this.options.onchange) {\n\t this.options.onchange(new_value);\n\t }\n\t \n\t // return current value\n\t return after;\n\t }\n\t },\n\t \n\t // size\n\t size: function() {\n\t return this.$tbody.find('tr').length;\n\t },\n\t \n\t // commit\n\t _commit: function(id, fade, prepend) {\n\t // remove previous item with same id\n\t this.del(id);\n\t \n\t // add\n\t this.row.attr('id', id);\n\t \n\t // add row\n\t if (prepend) {\n\t this.$tbody.prepend(this.row);\n\t } else {\n\t this.$tbody.append(this.row);\n\t }\n\t \n\t // fade mode\n\t if (fade) {\n\t this.row.hide();\n\t this.row.fadeIn();\n\t }\n\t \n\t // row\n\t this.row = this._row();\n\t \n\t // row count\n\t this.row_count++;\n\t this._refresh();\n\t },\n\t \n\t // create new row\n\t _row: function() {\n\t return $('');\n\t },\n\t \n\t // onclick\n\t _onclick: function(e) {\n\t // get values\n\t var old_value = this.value();\n\t var new_value = $(e.target).closest('tr').attr('id');\n\t if (new_value != ''){\n\t // check equality\n\t if (new_value && old_value != new_value) {\n\t if (this.options.onconfirm) {\n\t this.options.onconfirm(new_value);\n\t } else {\n\t this.value(new_value);\n\t }\n\t }\n\t }\n\t },\n\t\n\t // ondblclick\n\t _ondblclick: function(e) {\n\t var value = this.value();\n\t if (value && this.options.ondblclick) {\n\t this.options.ondblclick(value);\n\t }\n\t },\n\t \n\t // refresh\n\t _refresh: function() {\n\t if (this.row_count == 0) {\n\t this.$tmessage.show();\n\t } else {\n\t this.$tmessage.hide();\n\t }\n\t },\n\t \n\t // load html template\n\t _template: function(options) {\n\t return '
    ' +\n\t '
    ").append(i)).append(a("").append(t.title)))}),this.$cover[this.model.get("enabled")&&"hide"||"show"]()}})}.apply(t,n),!(void 0!==s&&(e.exports=s))}).call(t,i(3),i(1),i(2))},,,function(e,t,i){var n,s,o;(function(i,a){!function(i){s=[],n=i,o="function"==typeof n?n.apply(t,s):n,!(void 0!==o&&(e.exports=o))}(function(){i.fn.extend({hoverhighlight:function(e,t){return e=e||"body",this.length?(a(this).each(function(){var i=a(this),n=i.data("target");n&&i.mouseover(function(i){a(n,e).css({background:t})}).mouseout(function(e){a(n).css({background:""})})}),this):this}})})}).call(t,i(1),i(1))},function(e,t,i){var n,s,o;(function(i,a){!function(i){s=[],n=i,o="function"==typeof n?n.apply(t,s):n,!(void 0!==o&&(e.exports=o))}(function(){function e(e,n){function s(e){var t=i(this).parent().children("input");t.val("").trigger("searchInput.clear").blur(),n.onclear()}function o(e,t){return t?(i(this).trigger("search.search",t),void("function"==typeof n.onfirstsearch&&f?(f=!1,n.onfirstsearch(t)):n.onsearch(t))):s()}function r(){return['"].join("")}function l(){return i(r()).focus(function(e){i(this).select()}).keyup(function(e){if(e.preventDefault(),e.stopPropagation(),e.which===u&&n.escWillClear)s.call(this,e);else{var t=i(this).val();(e.which===p||n.minSearchLen&&t.length>=n.minSearchLen)&&o.call(this,e,t)}}).val(n.initialVal)}function c(){return i([''].join("")).tooltip({placement:"bottom"}).click(function(e){s.call(this,e)})}function d(){return i([''].join("")).hide().tooltip({placement:"bottom"})}function h(){g.find(".search-loading").toggle(),g.find(".search-clear").toggle()}var u=27,p=13,g=i(e),f=!0,m={initialVal:"",name:"search",placeholder:"search",classes:"",onclear:function(){},onfirstsearch:null,onsearch:function(e){},minSearchLen:0,escWillClear:!0,oninit:function(){}};return"string"===a.type(n)?("toggle-loading"===n&&h(),g):("object"===a.type(n)&&(n=a.extend(!0,{},m,n)),g.addClass("search-input").prepend([l(),c(),d()]))}var t=window._l||function(e){return e};a.fn.extend({searchInput:function(t){return this.each(function(){return e(this,t)})}})})}).call(t,i(1),i(1))},,function(e,t,i){var n,s;n=[],s=function(){function e(e,t){var i=/(-?[0-9\.]+)/g,n=e.toString().toLowerCase()||"",s=t.toString().toLowerCase()||"",o=String.fromCharCode(0),a=n.replace(i,o+"$1"+o).split(o),r=s.replace(i,o+"$1"+o).split(o),l=new Date(n).getTime(),c=l?new Date(s).getTime():null;if(c){if(lc)return 1}for(var d,h,u=0,p=Math.max(a.length,r.length);uh)return 1}return 0}return e}.apply(t,n),!(void 0!==s&&(e.exports=s))},function(e,t,i){(function(e,t){!function(i){e.event.props.push("dataTransfer"),i.uploadpost=function(t){var n=i.extend({},{data:{},success:function(){},error:function(){},progress:function(){},url:null,maxfilesize:2048,error_filesize:"File exceeds 2GB. Please use a FTP client.",error_default:"Please make sure the file is available.",error_server:"Upload request failed.",error_login:"Uploads require you to log in."},t),s=n.data;if(s.error_message)return void n.error(s.error_message);var o=new FormData;for(var a in s.payload)o.append(a,s.payload[a]);var r=0;for(var a in s.files){var l=s.files[a];o.append(l.name,l.file,l.file.name),r+=l.file.size}return r>1048576*n.maxfilesize?void n.error(n.error_filesize):(xhr=new XMLHttpRequest,xhr.open("POST",n.url,!0),xhr.setRequestHeader("Accept","application/json"),xhr.setRequestHeader("Cache-Control","no-cache"),xhr.setRequestHeader("X-Requested-With","XMLHttpRequest"),xhr.onreadystatechange=function(){if(xhr.readyState==xhr.DONE){var t=null;if(xhr.responseText)try{t=e.parseJSON(xhr.responseText)}catch(i){t=xhr.responseText}if(xhr.status<200||xhr.status>299){var s=xhr.statusText;403==xhr.status?s=n.error_login:0==xhr.status?s=n.error_server:s||(s=n.error_default),n.error(s+" ("+xhr.status+")")}else n.success(t)}},xhr.upload.addEventListener("progress",function(e){e.lengthComputable&&n.progress(Math.round(100*e.loaded/e.total))},!1),Galaxy.emit.debug("uploadbox::uploadpost()","Posting following data.",n),void xhr.send(o))},i.fn.uploadinput=function(e){var t=this,n=i.extend({},{ondragover:function(){},ondragleave:function(){},onchange:function(){},multiple:!1},e),s=i('");return t.append(s.change(function(e){n.onchange(e.target.files),i(this).val("")})),t.on("drop",function(e){n.ondragleave(e),e.dataTransfer&&(n.onchange(e.dataTransfer.files),e.preventDefault())}),t.on("dragover",function(e){e.preventDefault(),n.ondragover(e)}),t.on("dragleave",function(e){e.stopPropagation(),n.ondragleave(e)}),{dialog:function(){s.trigger("click")}}},i.fn.uploadbox=function(e){function n(e){if(e&&e.length&&!m){var i=g;return t.each(e,function(e,i){"new"!==e.mode&&t.filter(p,function(t){return t.name===e.name&&t.size===e.size}).length&&(e.duplicate=!0)}),t.each(e,function(e){if(!e.duplicate){var t=String(g++);p[t]=e,u.announce(t,p[t]),f++}}),i}}function s(e){p[e]&&(delete p[e],f--)}function o(){if(0==f||v)return v=!1,m=!1,void u.complete();m=!0;var e=-1;for(var t in p){e=t;break}p[e];s(e),i.uploadpost({url:u.url,data:u.initialize(e),success:function(t){u.success(e,t),o()},error:function(t){u.error(e,t),o()},progress:function(t){u.progress(e,t)}})}function a(){_.dialog()}function r(e){for(e in p)s(e)}function l(){m||(m=!0,o())}function c(){v=!0}function d(e){return u=i.extend({},u,e)}function h(){return window.File&&window.FormData&&window.XMLHttpRequest&&window.FileList}var u=i.extend({},{dragover:function(){},dragleave:function(){},announce:function(e){},initialize:function(e){},progress:function(e,t){},success:function(e,t){},error:function(e,t){alert(t)},complete:function(){}},e),p={},g=0,f=0,m=!1,v=!1,_=i(this).uploadinput({multiple:!0,onchange:function(e){n(e)},ondragover:e.ondragover,ondragleave:e.ondragleave});return{select:a,add:n,remove:s,start:l,stop:c,reset:r,configure:d,compatible:h}}}(e)}).call(t,i(1),i(2))},function(e,t,i){(function(t){var n=i(10).RightPanel,s=(i(7),i(116));CurrentHistoryView=i(113).CurrentHistoryView,_l=i(5);var o=n.extend({title:_l("History"),initialize:function(e){n.prototype.initialize.call(this,e),this.options=t.pick(e,"userIsAnonymous","allow_user_dataset_purge","galaxyRoot"),this.historyView=new CurrentHistoryView({className:CurrentHistoryView.prototype.className+" middle",purgeAllowed:e.allow_user_dataset_purge,linkTarget:"galaxy_main"})},$toggleButton:function(){return this.$(".footer > .panel-collapse")},render:function(){n.prototype.render.call(this),this.optionsMenu=s(this.$("#history-options-button"),{anonymous:this.options.userIsAnonymous,purgeAllowed:this.options.allow_user_dataset_purge,root:this.options.galaxyRoot}),this.$("> .header .buttons [title]").tooltip({placement:"bottom"}),this.historyView.setElement(this.$(".history-panel")),this.$el.attr("class","history-right-panel")},_templateHeader:function(e){var i=this.options.galaxyRoot+"history",n=this.options.galaxyRoot+"history/view_multiple";return['
    ','
    ','','',this.options.userIsAnonymous?"":[''].join(""),"
    ",'
    ',t.escape(this.title),"
    ","
    "].join("")},_templateBody:function(e){return['
    '].join("")},_templateFooter:function(e){return['
    You can tell Galaxy to download data from web by entering URL in this box (one per line). You can also directly paste the contents of a file.
    ',\n\t '
    ',\n\t '
    '\n\t ].join( '' );\n\t }\n\t});\n\t\n\t//==============================================================================\n\treturn {\n\t CitationView : CitationView,\n\t CitationListView : CitationListView\n\t};\n\t\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3)))\n\n/***/ },\n/* 29 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(_, $, jQuery) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(42),\n\t __webpack_require__(32),\n\t __webpack_require__(6),\n\t __webpack_require__(5)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( LIST_ITEM, DATASET_LI, BASE_MVC, _l ){\n\t\n\t'use strict';\n\t//==============================================================================\n\tvar FoldoutListItemView = LIST_ITEM.FoldoutListItemView,\n\t ListItemView = LIST_ITEM.ListItemView;\n\t/** @class Read only view for DatasetCollection.\n\t */\n\tvar DCListItemView = FoldoutListItemView.extend(\n\t/** @lends DCListItemView.prototype */{\n\t\n\t className : FoldoutListItemView.prototype.className + \" dataset-collection\",\n\t id : function(){\n\t return [ 'dataset_collection', this.model.get( 'id' ) ].join( '-' );\n\t },\n\t\n\t /** override to add linkTarget */\n\t initialize : function( attributes ){\n\t this.linkTarget = attributes.linkTarget || '_blank';\n\t this.hasUser = attributes.hasUser;\n\t FoldoutListItemView.prototype.initialize.call( this, attributes );\n\t },\n\t\n\t /** event listeners */\n\t _setUpListeners : function(){\n\t FoldoutListItemView.prototype._setUpListeners.call( this );\n\t this.listenTo( this.model, 'change', function( model, options ){\n\t // if the model has changed deletion status render it entirely\n\t if( _.has( model.changed, 'deleted' ) ){\n\t this.render();\n\t\n\t // if the model has been decorated after the fact with the element count,\n\t // render the subtitle where the count is displayed\n\t } else if( _.has( model.changed, 'element_count' ) ){\n\t this.$( '> .title-bar .subtitle' ).replaceWith( this._renderSubtitle() );\n\t }\n\t });\n\t },\n\t\n\t // ......................................................................... rendering\n\t /** render a subtitle to show the user what sort of collection this is */\n\t _renderSubtitle : function(){\n\t return $( this.templates.subtitle( this.model.toJSON(), this ) );\n\t },\n\t\n\t // ......................................................................... foldout\n\t /** override to add linktarget to sub-panel */\n\t _getFoldoutPanelOptions : function(){\n\t var options = FoldoutListItemView.prototype._getFoldoutPanelOptions.call( this );\n\t return _.extend( options, {\n\t linkTarget : this.linkTarget,\n\t hasUser : this.hasUser\n\t });\n\t },\n\t\n\t /** override to not catch sub-panel selectors */\n\t $selector : function(){\n\t return this.$( '> .selector' );\n\t },\n\t\n\t // ......................................................................... misc\n\t /** String representation */\n\t toString : function(){\n\t var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n\t return 'DCListItemView(' + modelString + ')';\n\t }\n\t});\n\t\n\t// ............................................................................ TEMPLATES\n\t/** underscore templates */\n\tDCListItemView.prototype.templates = (function(){\n\t\n\t var warnings = _.extend( {}, FoldoutListItemView.prototype.templates.warnings, {\n\t error : BASE_MVC.wrapTemplate([\n\t // error during index fetch - show error on dataset\n\t '<% if( model.error ){ %>',\n\t '
    ',\n\t _l( 'There was an error getting the data for this collection' ), ': <%- model.error %>',\n\t '
    ',\n\t '<% } %>'\n\t ]),\n\t purged : BASE_MVC.wrapTemplate([\n\t '<% if( model.purged ){ %>',\n\t '
    ',\n\t _l( 'This collection has been deleted and removed from disk' ),\n\t '
    ',\n\t '<% } %>'\n\t ]),\n\t deleted : BASE_MVC.wrapTemplate([\n\t // deleted not purged\n\t '<% if( model.deleted && !model.purged ){ %>',\n\t '
    ',\n\t _l( 'This collection has been deleted' ),\n\t '
    ',\n\t '<% } %>'\n\t ])\n\t });\n\t\n\t // use element identifier\n\t var titleBarTemplate = BASE_MVC.wrapTemplate([\n\t '
    ',\n\t '
    ',\n\t '<%- collection.element_identifier || collection.name %>',\n\t '
    ',\n\t '
    ',\n\t '
    '\n\t ], 'collection' );\n\t\n\t // use element identifier\n\t var subtitleTemplate = BASE_MVC.wrapTemplate([\n\t '
    ',\n\t '<% var countText = collection.element_count? ( collection.element_count + \" \" ) : \"\"; %>',\n\t '<% if( collection.collection_type === \"list\" ){ %>',\n\t _l( 'a list of <%- countText %>datasets' ),\n\t '<% } else if( collection.collection_type === \"paired\" ){ %>',\n\t _l( 'a pair of datasets' ),\n\t '<% } else if( collection.collection_type === \"list:paired\" ){ %>',\n\t _l( 'a list of <%- countText %>dataset pairs' ),\n\t '<% } else if( collection.collection_type === \"list:list\" ){ %>',\n\t _l( 'a list of <%- countText %>dataset lists' ),\n\t '<% } %>',\n\t '
    '\n\t ], 'collection' );\n\t\n\t return _.extend( {}, FoldoutListItemView.prototype.templates, {\n\t warnings : warnings,\n\t titleBar : titleBarTemplate,\n\t subtitle : subtitleTemplate\n\t });\n\t}());\n\t\n\t\n\t//==============================================================================\n\t/** @class Read only view for DatasetCollectionElement.\n\t */\n\tvar DCEListItemView = ListItemView.extend(\n\t/** @lends DCEListItemView.prototype */{\n\t\n\t /** add the DCE class to the list item */\n\t className : ListItemView.prototype.className + \" dataset-collection-element\",\n\t\n\t /** set up */\n\t initialize : function( attributes ){\n\t if( attributes.logger ){ this.logger = this.model.logger = attributes.logger; }\n\t this.log( 'DCEListItemView.initialize:', attributes );\n\t ListItemView.prototype.initialize.call( this, attributes );\n\t },\n\t\n\t // ......................................................................... misc\n\t /** String representation */\n\t toString : function(){\n\t var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n\t return 'DCEListItemView(' + modelString + ')';\n\t }\n\t});\n\t\n\t// ............................................................................ TEMPLATES\n\t/** underscore templates */\n\tDCEListItemView.prototype.templates = (function(){\n\t\n\t // use the element identifier here - since that will persist and the user will need it\n\t var titleBarTemplate = BASE_MVC.wrapTemplate([\n\t '
    ',\n\t '
    ',\n\t '<%- element.element_identifier %>',\n\t '
    ',\n\t '
    ',\n\t '
    '\n\t ], 'element' );\n\t\n\t return _.extend( {}, ListItemView.prototype.templates, {\n\t titleBar : titleBarTemplate\n\t });\n\t}());\n\t\n\t\n\t//==============================================================================\n\t/** @class Read only view for a DatasetCollectionElement that is also an DatasetAssociation\n\t * (a dataset contained in a dataset collection).\n\t */\n\tvar DatasetDCEListItemView = DATASET_LI.DatasetListItemView.extend(\n\t/** @lends DatasetDCEListItemView.prototype */{\n\t\n\t className : DATASET_LI.DatasetListItemView.prototype.className + \" dataset-collection-element\",\n\t\n\t /** set up */\n\t initialize : function( attributes ){\n\t if( attributes.logger ){ this.logger = this.model.logger = attributes.logger; }\n\t this.log( 'DatasetDCEListItemView.initialize:', attributes );\n\t DATASET_LI.DatasetListItemView.prototype.initialize.call( this, attributes );\n\t },\n\t\n\t /** In this override, only get details if in the ready state.\n\t * Note: fetch with no 'change' event triggering to prevent automatic rendering.\n\t */\n\t _fetchModelDetails : function(){\n\t var view = this;\n\t if( view.model.inReadyState() && !view.model.hasDetails() ){\n\t return view.model.fetch({ silent: true });\n\t }\n\t return jQuery.when();\n\t },\n\t\n\t // ......................................................................... misc\n\t /** String representation */\n\t toString : function(){\n\t var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n\t return 'DatasetDCEListItemView(' + modelString + ')';\n\t }\n\t});\n\t\n\t// ............................................................................ TEMPLATES\n\t/** underscore templates */\n\tDatasetDCEListItemView.prototype.templates = (function(){\n\t\n\t // use the element identifier here and not the dataset name\n\t //TODO:?? can we steal the DCE titlebar?\n\t var titleBarTemplate = BASE_MVC.wrapTemplate([\n\t '
    ',\n\t '',\n\t '
    ',\n\t '<%- element.element_identifier %>',\n\t '
    ',\n\t '
    '\n\t ], 'element' );\n\t\n\t return _.extend( {}, DATASET_LI.DatasetListItemView.prototype.templates, {\n\t titleBar : titleBarTemplate\n\t });\n\t}());\n\t\n\t\n\t//==============================================================================\n\t/** @class Read only view for a DatasetCollectionElement that is also a DatasetCollection\n\t * (a nested DC).\n\t */\n\tvar NestedDCDCEListItemView = DCListItemView.extend(\n\t/** @lends NestedDCDCEListItemView.prototype */{\n\t\n\t className : DCListItemView.prototype.className + \" dataset-collection-element\",\n\t\n\t /** In this override, add the state as a class for use with state-based CSS */\n\t _swapNewRender : function( $newRender ){\n\t DCListItemView.prototype._swapNewRender.call( this, $newRender );\n\t var state = this.model.get( 'state' ) || 'ok';\n\t this.$el.addClass( 'state-' + state );\n\t return this.$el;\n\t },\n\t\n\t // ......................................................................... misc\n\t /** String representation */\n\t toString : function(){\n\t var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n\t return 'NestedDCDCEListItemView(' + modelString + ')';\n\t }\n\t});\n\t\n\t\n\t//==============================================================================\n\t return {\n\t DCListItemView : DCListItemView,\n\t DCEListItemView : DCEListItemView,\n\t DatasetDCEListItemView : DatasetDCEListItemView,\n\t NestedDCDCEListItemView : NestedDCDCEListItemView\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(2), __webpack_require__(1), __webpack_require__(1)))\n\n/***/ },\n/* 30 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(_, Backbone, jQuery) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(70),\n\t __webpack_require__(6),\n\t __webpack_require__(5)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( DATASET_MODEL, BASE_MVC, _l ){\n\t'use strict';\n\t\n\t//==============================================================================\n\t/*\n\tNotes:\n\t\n\tTerminology:\n\t DatasetCollection/DC : a container of datasets or nested DatasetCollections\n\t Element/DatasetCollectionElement/DCE : an item contained in a DatasetCollection\n\t HistoryDatasetCollectionAssociation/HDCA: a DatasetCollection contained in a history\n\t\n\t\n\tThis all seems too complex unfortunately:\n\t\n\t- Terminology collision between DatasetCollections (DCs) and Backbone Collections.\n\t- In the DatasetCollections API JSON, DC Elements use a 'Has A' stucture to *contain*\n\t either a dataset or a nested DC. This would make the hierarchy much taller. I've\n\t decided to merge the contained JSON with the DC element json - making the 'has a'\n\t relation into an 'is a' relation. This seems simpler to me and allowed a lot of\n\t DRY in both models and views, but may make tracking or tracing within these models\n\t more difficult (since DatasetCollectionElements are now *also* DatasetAssociations\n\t or DatasetCollections (nested)). This also violates the rule of thumb about\n\t favoring aggregation over inheritance.\n\t- Currently, there are three DatasetCollection subclasses: List, Pair, and ListPaired.\n\t These each should a) be usable on their own, b) be usable in the context of\n\t nesting within a collection model (at least in the case of ListPaired), and\n\t c) be usable within the context of other container models (like History or\n\t LibraryFolder, etc.). I've tried to separate/extract classes in order to\n\t handle those three situations, but it's proven difficult to do in a simple,\n\t readable manner.\n\t- Ideally, histories and libraries would inherit from the same server models as\n\t dataset collections do since they are (in essence) dataset collections themselves -\n\t making the whole nested structure simpler. This would be a large, error-prone\n\t refactoring and migration.\n\t\n\tMany of the classes and heirarchy are meant as extension points so, while the\n\trelations and flow may be difficult to understand initially, they'll allow us to\n\thandle the growth or flux dataset collection in the future (w/o actually implementing\n\tany YAGNI).\n\t\n\t*/\n\t//_________________________________________________________________________________________________ ELEMENTS\n\t/** @class mixin for Dataset collection elements.\n\t * When collection elements are passed from the API, the underlying element is\n\t * in a sub-object 'object' (IOW, a DCE representing an HDA will have HDA json in element.object).\n\t * This mixin uses the constructor and parse methods to merge that JSON with the DCE attribtues\n\t * effectively changing a DCE from a container to a subclass (has a --> is a).\n\t */\n\tvar DatasetCollectionElementMixin = {\n\t\n\t /** default attributes used by elements in a dataset collection */\n\t defaults : {\n\t model_class : 'DatasetCollectionElement',\n\t element_identifier : null,\n\t element_index : null,\n\t element_type : null\n\t },\n\t\n\t /** merge the attributes of the sub-object 'object' into this model */\n\t _mergeObject : function( attributes ){\n\t // if we don't preserve and correct ids here, the element id becomes the object id\n\t // and collision in backbone's _byId will occur and only\n\t _.extend( attributes, attributes.object, { element_id: attributes.id });\n\t delete attributes.object;\n\t return attributes;\n\t },\n\t\n\t /** override to merge this.object into this */\n\t constructor : function( attributes, options ){\n\t // console.debug( '\\t DatasetCollectionElement.constructor:', attributes, options );\n\t attributes = this._mergeObject( attributes );\n\t this.idAttribute = 'element_id';\n\t Backbone.Model.apply( this, arguments );\n\t },\n\t\n\t /** when the model is fetched, merge this.object into this */\n\t parse : function( response, options ){\n\t var attributes = response;\n\t attributes = this._mergeObject( attributes );\n\t return attributes;\n\t }\n\t};\n\t\n\t/** @class Concrete class of Generic DatasetCollectionElement */\n\tvar DatasetCollectionElement = Backbone.Model\n\t .extend( BASE_MVC.LoggableMixin )\n\t .extend( DatasetCollectionElementMixin )\n\t .extend({ _logNamespace : 'collections' });\n\t\n\t\n\t//==============================================================================\n\t/** @class Base/Abstract Backbone collection for Generic DCEs. */\n\tvar DCECollection = Backbone.Collection.extend( BASE_MVC.LoggableMixin ).extend(\n\t/** @lends DCECollection.prototype */{\n\t _logNamespace : 'collections',\n\t\n\t model: DatasetCollectionElement,\n\t\n\t /** String representation. */\n\t toString : function(){\n\t return ([ 'DatasetCollectionElementCollection(', this.length, ')' ].join( '' ));\n\t }\n\t});\n\t\n\t\n\t//==============================================================================\n\t/** @class Backbone model for a dataset collection element that is a dataset (HDA).\n\t */\n\tvar DatasetDCE = DATASET_MODEL.DatasetAssociation.extend( BASE_MVC.mixin( DatasetCollectionElementMixin,\n\t/** @lends DatasetDCE.prototype */{\n\t\n\t /** url fn */\n\t url : function(){\n\t // won't always be an hda\n\t if( !this.has( 'history_id' ) ){\n\t console.warn( 'no endpoint for non-hdas within a collection yet' );\n\t // (a little silly since this api endpoint *also* points at hdas)\n\t return Galaxy.root + 'api/datasets';\n\t }\n\t return Galaxy.root + 'api/histories/' + this.get( 'history_id' ) + '/contents/' + this.get( 'id' );\n\t },\n\t\n\t defaults : _.extend( {},\n\t DATASET_MODEL.DatasetAssociation.prototype.defaults,\n\t DatasetCollectionElementMixin.defaults\n\t ),\n\t\n\t // because all objects have constructors (as this hashmap would even if this next line wasn't present)\n\t // the constructor in hcontentMixin won't be attached by BASE_MVC.mixin to this model\n\t // - re-apply manually for now\n\t /** call the mixin constructor */\n\t constructor : function( attributes, options ){\n\t this.debug( '\\t DatasetDCE.constructor:', attributes, options );\n\t //DATASET_MODEL.DatasetAssociation.prototype.constructor.call( this, attributes, options );\n\t DatasetCollectionElementMixin.constructor.call( this, attributes, options );\n\t },\n\t\n\t /** Does this model already contain detailed data (as opposed to just summary level data)? */\n\t hasDetails : function(){\n\t return this.elements && this.elements.length;\n\t },\n\t\n\t /** String representation. */\n\t toString : function(){\n\t var objStr = this.get( 'element_identifier' );\n\t return ([ 'DatasetDCE(', objStr, ')' ].join( '' ));\n\t }\n\t}));\n\t\n\t\n\t//==============================================================================\n\t/** @class DCECollection of DatasetDCE's (a list of datasets, a pair of datasets).\n\t */\n\tvar DatasetDCECollection = DCECollection.extend(\n\t/** @lends DatasetDCECollection.prototype */{\n\t model: DatasetDCE,\n\t\n\t /** String representation. */\n\t toString : function(){\n\t return ([ 'DatasetDCECollection(', this.length, ')' ].join( '' ));\n\t }\n\t});\n\t\n\t\n\t//_________________________________________________________________________________________________ COLLECTIONS\n\t/** @class Backbone model for Dataset Collections.\n\t * The DC API returns an array of JSON objects under the attribute elements.\n\t * This model:\n\t * - removes that array/attribute ('elements') from the model,\n\t * - creates a bbone collection (of the class defined in the 'collectionClass' attribute),\n\t * - passes that json onto the bbone collection\n\t * - caches the bbone collection in this.elements\n\t */\n\tvar DatasetCollection = Backbone.Model\n\t .extend( BASE_MVC.LoggableMixin )\n\t .extend( BASE_MVC.SearchableModelMixin )\n\t .extend(/** @lends DatasetCollection.prototype */{\n\t _logNamespace : 'collections',\n\t\n\t /** default attributes for a model */\n\t defaults : {\n\t /* 'list', 'paired', or 'list:paired' */\n\t collection_type : null,\n\t //??\n\t deleted : false\n\t },\n\t\n\t /** Which class to use for elements */\n\t collectionClass : DCECollection,\n\t\n\t /** set up: create elements instance var and (on changes to elements) update them */\n\t initialize : function( model, options ){\n\t this.debug( this + '(DatasetCollection).initialize:', model, options, this );\n\t this.elements = this._createElementsModel();\n\t this.on( 'change:elements', function(){\n\t this.log( 'change:elements' );\n\t //TODO: prob. better to update the collection instead of re-creating it\n\t this.elements = this._createElementsModel();\n\t });\n\t },\n\t\n\t /** move elements model attribute to full collection */\n\t _createElementsModel : function(){\n\t this.debug( this + '._createElementsModel', this.collectionClass, this.get( 'elements' ), this.elements );\n\t //TODO: same patterns as DatasetCollectionElement _createObjectModel - refactor to BASE_MVC.hasSubModel?\n\t var elements = this.get( 'elements' ) || [];\n\t this.unset( 'elements', { silent: true });\n\t this.elements = new this.collectionClass( elements );\n\t //this.debug( 'collectionClass:', this.collectionClass + '', this.elements );\n\t return this.elements;\n\t },\n\t\n\t // ........................................................................ common queries\n\t /** pass the elements back within the model json when this is serialized */\n\t toJSON : function(){\n\t var json = Backbone.Model.prototype.toJSON.call( this );\n\t if( this.elements ){\n\t json.elements = this.elements.toJSON();\n\t }\n\t return json;\n\t },\n\t\n\t /** Is this collection in a 'ready' state no processing (for the collection) is left\n\t * to do on the server.\n\t */\n\t inReadyState : function(){\n\t var populated = this.get( 'populated' );\n\t return ( this.isDeletedOrPurged() || populated );\n\t },\n\t\n\t //TODO:?? the following are the same interface as DatasetAssociation - can we combine?\n\t /** Does the DC contain any elements yet? Is a fetch() required? */\n\t hasDetails : function(){\n\t return this.elements.length !== 0;\n\t },\n\t\n\t /** Given the filters, what models in this.elements would be returned? */\n\t getVisibleContents : function( filters ){\n\t // filters unused for now\n\t return this.elements;\n\t },\n\t\n\t // ........................................................................ ajax\n\t /** override to use actual Dates objects for create/update times */\n\t parse : function( response, options ){\n\t var parsed = Backbone.Model.prototype.parse.call( this, response, options );\n\t if( parsed.create_time ){\n\t parsed.create_time = new Date( parsed.create_time );\n\t }\n\t if( parsed.update_time ){\n\t parsed.update_time = new Date( parsed.update_time );\n\t }\n\t return parsed;\n\t },\n\t\n\t /** save this dataset, _Mark_ing it as deleted (just a flag) */\n\t 'delete' : function( options ){\n\t if( this.get( 'deleted' ) ){ return jQuery.when(); }\n\t return this.save( { deleted: true }, options );\n\t },\n\t /** save this dataset, _Mark_ing it as undeleted */\n\t undelete : function( options ){\n\t if( !this.get( 'deleted' ) || this.get( 'purged' ) ){ return jQuery.when(); }\n\t return this.save( { deleted: false }, options );\n\t },\n\t\n\t /** Is this collection deleted or purged? */\n\t isDeletedOrPurged : function(){\n\t return ( this.get( 'deleted' ) || this.get( 'purged' ) );\n\t },\n\t\n\t // ........................................................................ searchable\n\t /** searchable attributes for collections */\n\t searchAttributes : [\n\t 'name'\n\t ],\n\t\n\t // ........................................................................ misc\n\t /** String representation */\n\t toString : function(){\n\t var idAndName = [ this.get( 'id' ), this.get( 'name' ) || this.get( 'element_identifier' ) ];\n\t return 'DatasetCollection(' + ( idAndName.join(',') ) + ')';\n\t }\n\t});\n\t\n\t\n\t//==============================================================================\n\t/** Model for a DatasetCollection containing datasets (non-nested).\n\t */\n\tvar ListDatasetCollection = DatasetCollection.extend(\n\t/** @lends ListDatasetCollection.prototype */{\n\t\n\t /** override since we know the collection will only contain datasets */\n\t collectionClass : DatasetDCECollection,\n\t\n\t /** String representation. */\n\t toString : function(){ return 'List' + DatasetCollection.prototype.toString.call( this ); }\n\t});\n\t\n\t\n\t//==============================================================================\n\t/** Model for a DatasetCollection containing fwd/rev datasets (a list of 2).\n\t */\n\tvar PairDatasetCollection = ListDatasetCollection.extend(\n\t/** @lends PairDatasetCollection.prototype */{\n\t\n\t /** String representation. */\n\t toString : function(){ return 'Pair' + DatasetCollection.prototype.toString.call( this ); }\n\t});\n\t\n\t\n\t//_________________________________________________________________________________________________ NESTED COLLECTIONS\n\t// this is where things get weird, man. Weird.\n\t//TODO: it might be possible to compact all the following...I think.\n\t//==============================================================================\n\t/** @class Backbone model for a Generic DatasetCollectionElement that is also a DatasetCollection\n\t * (a nested collection). Currently only list:paired.\n\t */\n\tvar NestedDCDCE = DatasetCollection.extend( BASE_MVC.mixin( DatasetCollectionElementMixin,\n\t/** @lends NestedDCDCE.prototype */{\n\t\n\t // because all objects have constructors (as this hashmap would even if this next line wasn't present)\n\t // the constructor in hcontentMixin won't be attached by BASE_MVC.mixin to this model\n\t // - re-apply manually it now\n\t /** call the mixin constructor */\n\t constructor : function( attributes, options ){\n\t this.debug( '\\t NestedDCDCE.constructor:', attributes, options );\n\t DatasetCollectionElementMixin.constructor.call( this, attributes, options );\n\t },\n\t\n\t /** String representation. */\n\t toString : function(){\n\t var objStr = ( this.object )?( '' + this.object ):( this.get( 'element_identifier' ) );\n\t return ([ 'NestedDCDCE(', objStr, ')' ].join( '' ));\n\t }\n\t}));\n\t\n\t\n\t//==============================================================================\n\t/** @class Backbone collection containing Generic NestedDCDCE's (nested dataset collections).\n\t */\n\tvar NestedDCDCECollection = DCECollection.extend(\n\t/** @lends NestedDCDCECollection.prototype */{\n\t\n\t /** This is a collection of nested collections */\n\t model: NestedDCDCE,\n\t\n\t /** String representation. */\n\t toString : function(){\n\t return ([ 'NestedDCDCECollection(', this.length, ')' ].join( '' ));\n\t }\n\t});\n\t\n\t\n\t//==============================================================================\n\t/** @class Backbone model for a paired dataset collection within a list:paired dataset collection.\n\t */\n\tvar NestedPairDCDCE = PairDatasetCollection.extend( BASE_MVC.mixin( DatasetCollectionElementMixin,\n\t/** @lends NestedPairDCDCE.prototype */{\n\t//TODO:?? possibly rename to NestedDatasetCollection?\n\t\n\t // because all objects have constructors (as this hashmap would even if this next line wasn't present)\n\t // the constructor in hcontentMixin won't be attached by BASE_MVC.mixin to this model\n\t // - re-apply manually it now\n\t /** This is both a collection and a collection element - call the constructor */\n\t constructor : function( attributes, options ){\n\t this.debug( '\\t NestedPairDCDCE.constructor:', attributes, options );\n\t //DatasetCollection.constructor.call( this, attributes, options );\n\t DatasetCollectionElementMixin.constructor.call( this, attributes, options );\n\t },\n\t\n\t /** String representation. */\n\t toString : function(){\n\t var objStr = ( this.object )?( '' + this.object ):( this.get( 'element_identifier' ) );\n\t return ([ 'NestedPairDCDCE(', objStr, ')' ].join( '' ));\n\t }\n\t}));\n\t\n\t\n\t//==============================================================================\n\t/** @class Backbone collection for a backbone collection containing paired dataset collections.\n\t */\n\tvar NestedPairDCDCECollection = NestedDCDCECollection.extend(\n\t/** @lends PairDCDCECollection.prototype */{\n\t\n\t /** We know this collection is composed of only nested pair collections */\n\t model: NestedPairDCDCE,\n\t\n\t /** String representation. */\n\t toString : function(){\n\t return ([ 'NestedPairDCDCECollection(', this.length, ')' ].join( '' ));\n\t }\n\t});\n\t\n\t\n\t//==============================================================================\n\t/** @class Backbone Model for a DatasetCollection (list) that contains DatasetCollections (pairs).\n\t */\n\tvar ListPairedDatasetCollection = DatasetCollection.extend(\n\t/** @lends ListPairedDatasetCollection.prototype */{\n\t\n\t /** list:paired is the only collection that itself contains collections */\n\t collectionClass : NestedPairDCDCECollection,\n\t\n\t /** String representation. */\n\t toString : function(){\n\t return ([ 'ListPairedDatasetCollection(', this.get( 'name' ), ')' ].join( '' ));\n\t }\n\t});\n\t\n\t\n\t//==============================================================================\n\t/** @class Backbone model for a list dataset collection within a list:list dataset collection. */\n\tvar NestedListDCDCE = ListDatasetCollection.extend( BASE_MVC.mixin( DatasetCollectionElementMixin,\n\t/** @lends NestedListDCDCE.prototype */{\n\t\n\t /** This is both a collection and a collection element - call the constructor */\n\t constructor : function( attributes, options ){\n\t this.debug( '\\t NestedListDCDCE.constructor:', attributes, options );\n\t DatasetCollectionElementMixin.constructor.call( this, attributes, options );\n\t },\n\t\n\t /** String representation. */\n\t toString : function(){\n\t var objStr = ( this.object )?( '' + this.object ):( this.get( 'element_identifier' ) );\n\t return ([ 'NestedListDCDCE(', objStr, ')' ].join( '' ));\n\t }\n\t}));\n\t\n\t\n\t//==============================================================================\n\t/** @class Backbone collection containing list dataset collections. */\n\tvar NestedListDCDCECollection = NestedDCDCECollection.extend({\n\t\n\t /** We know this collection is composed of only nested pair collections */\n\t model: NestedListDCDCE,\n\t\n\t /** String representation. */\n\t toString : function(){\n\t return ([ 'NestedListDCDCECollection(', this.length, ')' ].join( '' ));\n\t }\n\t});\n\t\n\t\n\t//==============================================================================\n\t/** @class Backbone Model for a DatasetCollection (list) that contains other lists. */\n\tvar ListOfListsDatasetCollection = DatasetCollection.extend({\n\t\n\t /** list:paired is the only collection that itself contains collections */\n\t collectionClass : NestedListDCDCECollection,\n\t\n\t /** String representation. */\n\t toString : function(){\n\t return ([ 'ListOfListsDatasetCollection(', this.get( 'name' ), ')' ].join( '' ));\n\t }\n\t});\n\t\n\t\n\t//==============================================================================\n\t return {\n\t ListDatasetCollection : ListDatasetCollection,\n\t PairDatasetCollection : PairDatasetCollection,\n\t ListPairedDatasetCollection : ListPairedDatasetCollection,\n\t ListOfListsDatasetCollection: ListOfListsDatasetCollection\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(2), __webpack_require__(3), __webpack_require__(1)))\n\n/***/ },\n/* 31 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, _, $, jQuery) {\n\t!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(39),\n\t __webpack_require__(12),\n\t __webpack_require__(6),\n\t __webpack_require__(8),\n\t __webpack_require__(87),\n\t __webpack_require__(5),\n\t __webpack_require__(84)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( HDCA, STATES, BASE_MVC, UI_MODAL, naturalSort, _l ){\n\t\n\t'use strict';\n\t\n\tvar logNamespace = 'collections';\n\t/*==============================================================================\n\tTODO:\n\t use proper Element model and not just json\n\t straighten out createFn, collection.createHDCA\n\t possibly stop using modals for this\n\t It would be neat to do a drag and drop\n\t\n\t==============================================================================*/\n\t/** A view for both DatasetDCEs and NestedDCDCEs\n\t * (things that implement collection-model:DatasetCollectionElementMixin)\n\t */\n\tvar DatasetCollectionElementView = Backbone.View.extend( BASE_MVC.LoggableMixin ).extend({\n\t _logNamespace : logNamespace,\n\t\n\t//TODO: use proper class (DatasetDCE or NestedDCDCE (or the union of both))\n\t tagName : 'li',\n\t className : 'collection-element',\n\t\n\t initialize : function( attributes ){\n\t this.element = attributes.element || {};\n\t this.selected = attributes.selected || false;\n\t },\n\t\n\t render : function(){\n\t this.$el\n\t .attr( 'data-element-id', this.element.id )\n\t .attr( 'draggable', true )\n\t .html( this.template({ element: this.element }) );\n\t if( this.selected ){\n\t this.$el.addClass( 'selected' );\n\t }\n\t return this;\n\t },\n\t\n\t //TODO: lots of unused space in the element - possibly load details and display them horiz.\n\t template : _.template([\n\t '',\n\t '<%- element.name %>',\n\t '',\n\t '',\n\t ].join('')),\n\t\n\t /** select this element and pub */\n\t select : function( toggle ){\n\t this.$el.toggleClass( 'selected', toggle );\n\t this.trigger( 'select', {\n\t source : this,\n\t selected : this.$el.hasClass( 'selected' )\n\t });\n\t },\n\t\n\t /** animate the removal of this element and pub */\n\t discard : function(){\n\t var view = this,\n\t parentWidth = this.$el.parent().width();\n\t this.$el.animate({ 'margin-right' : parentWidth }, 'fast', function(){\n\t view.trigger( 'discard', {\n\t source : view\n\t });\n\t view.destroy();\n\t });\n\t },\n\t\n\t /** remove the DOM and any listeners */\n\t destroy : function(){\n\t this.off();\n\t this.$el.remove();\n\t },\n\t\n\t events : {\n\t 'click' : '_click',\n\t 'click .name' : '_clickName',\n\t 'click .discard': '_clickDiscard',\n\t\n\t 'dragstart' : '_dragstart',\n\t 'dragend' : '_dragend',\n\t 'dragover' : '_sendToParent',\n\t 'drop' : '_sendToParent'\n\t },\n\t\n\t /** select when the li is clicked */\n\t _click : function( ev ){\n\t ev.stopPropagation();\n\t this.select( ev );\n\t },\n\t\n\t /** rename a pair when the name is clicked */\n\t _clickName : function( ev ){\n\t ev.stopPropagation();\n\t ev.preventDefault();\n\t var promptString = [ _l( 'Enter a new name for the element' ), ':\\n(',\n\t _l( 'Note that changing the name here will not rename the dataset' ), ')' ].join( '' ),\n\t response = prompt( _l( 'Enter a new name for the element' ) + ':', this.element.name );\n\t if( response ){\n\t this.element.name = response;\n\t this.render();\n\t }\n\t //TODO: cancelling with ESC leads to closure of the creator...\n\t },\n\t\n\t /** discard when the discard button is clicked */\n\t _clickDiscard : function( ev ){\n\t ev.stopPropagation();\n\t this.discard();\n\t },\n\t\n\t /** dragging pairs for re-ordering */\n\t _dragstart : function( ev ){\n\t if( ev.originalEvent ){ ev = ev.originalEvent; }\n\t ev.dataTransfer.effectAllowed = 'move';\n\t ev.dataTransfer.setData( 'text/plain', JSON.stringify( this.element ) );\n\t\n\t this.$el.addClass( 'dragging' );\n\t this.$el.parent().trigger( 'collection-element.dragstart', [ this ] );\n\t },\n\t\n\t /** dragging for re-ordering */\n\t _dragend : function( ev ){\n\t this.$el.removeClass( 'dragging' );\n\t this.$el.parent().trigger( 'collection-element.dragend', [ this ] );\n\t },\n\t\n\t /** manually bubble up an event to the parent/container */\n\t _sendToParent : function( ev ){\n\t this.$el.parent().trigger( ev );\n\t },\n\t\n\t /** string rep */\n\t toString : function(){\n\t return 'DatasetCollectionElementView()';\n\t }\n\t});\n\t\n\t\n\t// ============================================================================\n\t/** An interface for building collections.\n\t */\n\tvar ListCollectionCreator = Backbone.View.extend( BASE_MVC.LoggableMixin ).extend({\n\t _logNamespace : logNamespace,\n\t\n\t /** the class used to display individual elements */\n\t elementViewClass : DatasetCollectionElementView,\n\t /** the class this creator will create and save */\n\t collectionClass : HDCA.HistoryListDatasetCollection,\n\t className : 'list-collection-creator collection-creator flex-row-container',\n\t\n\t /** minimum number of valid elements to start with in order to build a collection of this type */\n\t minElements : 1,\n\t\n\t defaultAttributes : {\n\t//TODO: remove - use new collectionClass().save()\n\t /** takes elements and creates the proper collection - returns a promise */\n\t creationFn : function(){ throw new TypeError( 'no creation fn for creator' ); },\n\t /** fn to call when the collection is created (scoped to this) */\n\t oncreate : function(){},\n\t /** fn to call when the cancel button is clicked (scoped to this) - if falsy, no btn is displayed */\n\t oncancel : function(){},\n\t /** distance from list edge to begin autoscrolling list */\n\t autoscrollDist : 24,\n\t /** Color passed to hoverhighlight */\n\t highlightClr : 'rgba( 64, 255, 255, 1.0 )'\n\t },\n\t\n\t /** set up initial options, instance vars, behaviors */\n\t initialize : function( attributes ){\n\t this.metric( 'ListCollectionCreator.initialize', attributes );\n\t var creator = this;\n\t _.each( this.defaultAttributes, function( value, key ){\n\t value = attributes[ key ] || value;\n\t creator[ key ] = value;\n\t });\n\t\n\t /** unordered, original list - cache to allow reversal */\n\t creator.initialElements = attributes.elements || [];\n\t\n\t this._instanceSetUp();\n\t this._elementsSetUp();\n\t this._setUpBehaviors();\n\t },\n\t\n\t /** set up instance vars */\n\t _instanceSetUp : function(){\n\t /** Ids of elements that have been selected by the user - to preserve over renders */\n\t this.selectedIds = {};\n\t /** DOM elements currently being dragged */\n\t this.$dragging = null;\n\t /** Used for blocking UI events during ajax/operations (don't post twice) */\n\t this.blocking = false;\n\t },\n\t\n\t // ------------------------------------------------------------------------ process raw list\n\t /** set up main data */\n\t _elementsSetUp : function(){\n\t //this.debug( '-- _dataSetUp' );\n\t /** a list of invalid elements and the reasons they aren't valid */\n\t this.invalidElements = [];\n\t//TODO: handle fundamental problem of syncing DOM, views, and list here\n\t /** data for list in progress */\n\t this.workingElements = [];\n\t /** views for workingElements */\n\t this.elementViews = [];\n\t\n\t // copy initial list, sort, add ids if needed\n\t this.workingElements = this.initialElements.slice( 0 );\n\t this._ensureElementIds();\n\t this._validateElements();\n\t this._mangleDuplicateNames();\n\t this._sortElements();\n\t },\n\t\n\t /** add ids to dataset objs in initial list if none */\n\t _ensureElementIds : function(){\n\t this.workingElements.forEach( function( element ){\n\t if( !element.hasOwnProperty( 'id' ) ){\n\t element.id = _.uniqueId();\n\t }\n\t });\n\t return this.workingElements;\n\t },\n\t\n\t /** separate working list into valid and invalid elements for this collection */\n\t _validateElements : function(){\n\t var creator = this,\n\t existingNames = {};\n\t creator.invalidElements = [];\n\t\n\t this.workingElements = this.workingElements.filter( function( element ){\n\t var problem = creator._isElementInvalid( element );\n\t if( problem ){\n\t creator.invalidElements.push({\n\t element : element,\n\t text : problem\n\t });\n\t }\n\t return !problem;\n\t });\n\t return this.workingElements;\n\t },\n\t\n\t /** describe what is wrong with a particular element if anything */\n\t _isElementInvalid : function( element ){\n\t if( element.history_content_type !== 'dataset' ){\n\t return _l( \"is not a dataset\" );\n\t }\n\t if( element.state !== STATES.OK ){\n\t if( _.contains( STATES.NOT_READY_STATES, element.state ) ){\n\t return _l( \"hasn't finished running yet\" );\n\t }\n\t return _l( \"has errored, is paused, or is not accessible\" );\n\t }\n\t if( element.deleted || element.purged ){\n\t return _l( \"has been deleted or purged\" );\n\t }\n\t return null;\n\t },\n\t\n\t /** mangle duplicate names using a mac-like '(counter)' addition to any duplicates */\n\t _mangleDuplicateNames : function(){\n\t var SAFETY = 900,\n\t counter = 1,\n\t existingNames = {};\n\t this.workingElements.forEach( function( element ){\n\t var currName = element.name;\n\t while( existingNames.hasOwnProperty( currName ) ){\n\t currName = element.name + ' (' + counter + ')';\n\t counter += 1;\n\t if( counter >= SAFETY ){\n\t throw new Error( 'Safety hit in while loop - thats impressive' );\n\t }\n\t }\n\t element.name = currName;\n\t existingNames[ element.name ] = true;\n\t });\n\t },\n\t\n\t /** sort a list of elements */\n\t _sortElements : function( list ){\n\t // // currently only natural sort by name\n\t // this.workingElements.sort( function( a, b ){ return naturalSort( a.name, b.name ); });\n\t // return this.workingElements;\n\t },\n\t\n\t // ------------------------------------------------------------------------ rendering\n\t // templates : ListCollectionCreator.templates,\n\t /** render the entire interface */\n\t render : function( speed, callback ){\n\t //this.debug( '-- _render' );\n\t if( this.workingElements.length < this.minElements ){\n\t return this._renderInvalid( speed, callback );\n\t }\n\t\n\t this.$el.empty().html( this.templates.main() );\n\t this._renderHeader( speed );\n\t this._renderMiddle( speed );\n\t this._renderFooter( speed );\n\t this._addPluginComponents();\n\t this.$( '.collection-name' ).focus();\n\t this.trigger( 'rendered', this );\n\t return this;\n\t },\n\t\n\t\n\t /** render a simplified interface aimed at telling the user why they can't move forward */\n\t _renderInvalid : function( speed, callback ){\n\t //this.debug( '-- _render' );\n\t this.$el.empty().html( this.templates.invalidInitial({\n\t problems: this.invalidElements,\n\t elements: this.workingElements,\n\t }));\n\t if( typeof this.oncancel === 'function' ){\n\t this.$( '.cancel-create.btn' ).show();\n\t }\n\t this.trigger( 'rendered', this );\n\t return this;\n\t },\n\t\n\t /** render the header section */\n\t _renderHeader : function( speed, callback ){\n\t var $header = this.$( '.header' ).empty().html( this.templates.header() )\n\t .find( '.help-content' ).prepend( $( this.templates.helpContent() ) );\n\t //TODO: should only show once despite calling _renderHeader again\n\t if( this.invalidElements.length ){\n\t this._invalidElementsAlert();\n\t }\n\t return $header;\n\t },\n\t\n\t /** render the middle including the elements */\n\t _renderMiddle : function( speed, callback ){\n\t var $middle = this.$( '.middle' ).empty().html( this.templates.middle() );\n\t this._renderList( speed );\n\t return $middle;\n\t },\n\t\n\t /** render the footer, completion controls, and cancel controls */\n\t _renderFooter : function( speed, callback ){\n\t var $footer = this.$( '.footer' ).empty().html( this.templates.footer() );\n\t if( typeof this.oncancel === 'function' ){\n\t this.$( '.cancel-create.btn' ).show();\n\t }\n\t return $footer;\n\t },\n\t\n\t /** add any jQuery/bootstrap/custom plugins to elements rendered */\n\t _addPluginComponents : function(){\n\t this.$( '.help-content i' ).hoverhighlight( '.collection-creator', this.highlightClr );\n\t },\n\t\n\t /** build and show an alert describing any elements that could not be included due to problems */\n\t _invalidElementsAlert : function(){\n\t this._showAlert( this.templates.invalidElements({ problems: this.invalidElements }), 'alert-warning' );\n\t },\n\t\n\t /** add (or clear if clear is truthy) a validation warning to the DOM element described in what */\n\t _validationWarning : function( what, clear ){\n\t var VALIDATION_CLASS = 'validation-warning';\n\t if( what === 'name' ){\n\t what = this.$( '.collection-name' ).add( this.$( '.collection-name-prompt' ) );\n\t this.$( '.collection-name' ).focus().select();\n\t }\n\t if( clear ){\n\t what = what || this.$( '.' + VALIDATION_CLASS );\n\t what.removeClass( VALIDATION_CLASS );\n\t } else {\n\t what.addClass( VALIDATION_CLASS );\n\t }\n\t },\n\t\n\t _disableNameAndCreate : function( disable ){\n\t disable = !_.isUndefined( disable )? disable : true;\n\t if( disable ){\n\t this.$( '.collection-name' ).prop( 'disabled', true );\n\t this.$( '.create-collection' ).toggleClass( 'disabled', true );\n\t // } else {\n\t // this.$( '.collection-name' ).prop( 'disabled', false );\n\t // this.$( '.create-collection' ).removeClass( 'disable' );\n\t }\n\t },\n\t\n\t // ------------------------------------------------------------------------ rendering elements\n\t /** conv. to the main list display DOM */\n\t $list : function(){\n\t return this.$( '.collection-elements' );\n\t },\n\t\n\t /** show or hide the clear selected control based on the num of selected elements */\n\t _renderClearSelected : function(){\n\t if( _.size( this.selectedIds ) ){\n\t this.$( '.collection-elements-controls > .clear-selected' ).show();\n\t } else {\n\t this.$( '.collection-elements-controls > .clear-selected' ).hide();\n\t }\n\t },\n\t\n\t /** render the elements in order (or a warning if no elements found) */\n\t _renderList : function( speed, callback ){\n\t //this.debug( '-- _renderList' );\n\t var creator = this,\n\t $tmp = jQuery( '
    ' ),\n\t $list = creator.$list();\n\t\n\t _.each( this.elementViews, function( view ){\n\t view.destroy();\n\t creator.removeElementView( view );\n\t });\n\t\n\t // if( !this.workingElements.length ){\n\t // this._renderNoValidElements();\n\t // return;\n\t // }\n\t\n\t creator.workingElements.forEach( function( element ){\n\t var elementView = creator._createElementView( element );\n\t $tmp.append( elementView.$el );\n\t });\n\t\n\t creator._renderClearSelected();\n\t $list.empty().append( $tmp.children() );\n\t _.invoke( creator.elementViews, 'render' );\n\t\n\t if( $list.height() > $list.css( 'max-height' ) ){\n\t $list.css( 'border-width', '1px 0px 1px 0px' );\n\t } else {\n\t $list.css( 'border-width', '0px' );\n\t }\n\t },\n\t\n\t /** create an element view, cache in elementViews, set up listeners, and return */\n\t _createElementView : function( element ){\n\t var elementView = new this.elementViewClass({\n\t//TODO: use non-generic class or not all\n\t // model : COLLECTION.DatasetDCE( element )\n\t element : element,\n\t selected: _.has( this.selectedIds, element.id )\n\t });\n\t this.elementViews.push( elementView );\n\t this._listenToElementView( elementView );\n\t return elementView;\n\t },\n\t\n\t /** listen to any element events */\n\t _listenToElementView : function( view ){\n\t var creator = this;\n\t creator.listenTo( view, {\n\t select : function( data ){\n\t var element = data.source.element;\n\t if( data.selected ){\n\t creator.selectedIds[ element.id ] = true;\n\t } else {\n\t delete creator.selectedIds[ element.id ];\n\t }\n\t creator.trigger( 'elements:select', data );\n\t },\n\t discard : function( data ){\n\t creator.trigger( 'elements:discard', data );\n\t }\n\t });\n\t },\n\t\n\t /** add a new element view based on the json in element */\n\t addElementView : function( element ){\n\t//TODO: workingElements is sorted, add element in appropo index\n\t // add element, sort elements, find element index\n\t // var view = this._createElementView( element );\n\t // return view;\n\t },\n\t\n\t /** stop listening to view and remove from caches */\n\t removeElementView : function( view ){\n\t delete this.selectedIds[ view.element.id ];\n\t this._renderClearSelected();\n\t\n\t this.elementViews = _.without( this.elementViews, view );\n\t this.stopListening( view );\n\t },\n\t\n\t /** render a message in the list that no elements remain to create a collection */\n\t _renderNoElementsLeft : function(){\n\t this._disableNameAndCreate( true );\n\t this.$( '.collection-elements' ).append( this.templates.noElementsLeft() );\n\t },\n\t\n\t // /** render a message in the list that no valid elements were found to create a collection */\n\t // _renderNoValidElements : function(){\n\t // this._disableNameAndCreate( true );\n\t // this.$( '.collection-elements' ).append( this.templates.noValidElements() );\n\t // },\n\t\n\t // ------------------------------------------------------------------------ API\n\t /** convert element into JSON compatible with the collections API */\n\t _elementToJSON : function( element ){\n\t // return element.toJSON();\n\t return element;\n\t },\n\t\n\t /** create the collection via the API\n\t * @returns {jQuery.xhr Object} the jquery ajax request\n\t */\n\t createList : function( name ){\n\t if( !this.workingElements.length ){\n\t var message = _l( 'No valid elements for final list' ) + '. ';\n\t message += '' + _l( 'Cancel' ) + ' ';\n\t message += _l( 'or' );\n\t message += ' ' + _l( 'start over' ) + '.';\n\t this._showAlert( message );\n\t return;\n\t }\n\t\n\t var creator = this,\n\t elements = this.workingElements.map( function( element ){\n\t return creator._elementToJSON( element );\n\t });\n\t\n\t creator.blocking = true;\n\t return creator.creationFn( elements, name )\n\t .always( function(){\n\t creator.blocking = false;\n\t })\n\t .fail( function( xhr, status, message ){\n\t creator.trigger( 'error', {\n\t xhr : xhr,\n\t status : status,\n\t message : _l( 'An error occurred while creating this collection' )\n\t });\n\t })\n\t .done( function( response, message, xhr ){\n\t creator.trigger( 'collection:created', response, message, xhr );\n\t creator.metric( 'collection:created', response );\n\t if( typeof creator.oncreate === 'function' ){\n\t creator.oncreate.call( this, response, message, xhr );\n\t }\n\t });\n\t },\n\t\n\t // ------------------------------------------------------------------------ events\n\t /** set up event handlers on self */\n\t _setUpBehaviors : function(){\n\t this.on( 'error', this._errorHandler );\n\t\n\t this.once( 'rendered', function(){\n\t this.trigger( 'rendered:initial', this );\n\t });\n\t\n\t this.on( 'elements:select', function( data ){\n\t this._renderClearSelected();\n\t });\n\t\n\t this.on( 'elements:discard', function( data ){\n\t var element = data.source.element;\n\t this.removeElementView( data.source );\n\t\n\t this.workingElements = _.without( this.workingElements, element );\n\t if( !this.workingElements.length ){\n\t this._renderNoElementsLeft();\n\t }\n\t });\n\t\n\t //this.on( 'all', function(){\n\t // this.info( arguments );\n\t //});\n\t return this;\n\t },\n\t\n\t /** handle errors with feedback and details to the user (if available) */\n\t _errorHandler : function( data ){\n\t this.error( data );\n\t\n\t var creator = this;\n\t content = data.message || _l( 'An error occurred' );\n\t if( data.xhr ){\n\t var xhr = data.xhr,\n\t message = data.message;\n\t if( xhr.readyState === 0 && xhr.status === 0 ){\n\t content += ': ' + _l( 'Galaxy could not be reached and may be updating.' ) +\n\t _l( ' Try again in a few minutes.' );\n\t } else if( xhr.responseJSON ){\n\t content += ':
    ' + JSON.stringify( xhr.responseJSON ) + '
    ';\n\t } else {\n\t content += ': ' + message;\n\t }\n\t }\n\t creator._showAlert( content, 'alert-danger' );\n\t },\n\t\n\t events : {\n\t // header\n\t 'click .more-help' : '_clickMoreHelp',\n\t 'click .less-help' : '_clickLessHelp',\n\t 'click .main-help' : '_toggleHelp',\n\t 'click .header .alert button' : '_hideAlert',\n\t\n\t 'click .reset' : 'reset',\n\t 'click .clear-selected' : 'clearSelectedElements',\n\t\n\t // elements - selection\n\t 'click .collection-elements' : 'clearSelectedElements',\n\t\n\t // elements - drop target\n\t // 'dragenter .collection-elements': '_dragenterElements',\n\t // 'dragleave .collection-elements': '_dragleaveElements',\n\t 'dragover .collection-elements' : '_dragoverElements',\n\t 'drop .collection-elements' : '_dropElements',\n\t\n\t // these bubble up from the elements as custom events\n\t 'collection-element.dragstart .collection-elements' : '_elementDragstart',\n\t 'collection-element.dragend .collection-elements' : '_elementDragend',\n\t\n\t // footer\n\t 'change .collection-name' : '_changeName',\n\t 'keydown .collection-name' : '_nameCheckForEnter',\n\t 'click .cancel-create' : function( ev ){\n\t if( typeof this.oncancel === 'function' ){\n\t this.oncancel.call( this );\n\t }\n\t },\n\t 'click .create-collection' : '_clickCreate'//,\n\t },\n\t\n\t // ........................................................................ header\n\t /** expand help */\n\t _clickMoreHelp : function( ev ){\n\t ev.stopPropagation();\n\t this.$( '.main-help' ).addClass( 'expanded' );\n\t this.$( '.more-help' ).hide();\n\t },\n\t /** collapse help */\n\t _clickLessHelp : function( ev ){\n\t ev.stopPropagation();\n\t this.$( '.main-help' ).removeClass( 'expanded' );\n\t this.$( '.more-help' ).show();\n\t },\n\t /** toggle help */\n\t _toggleHelp : function( ev ){\n\t ev.stopPropagation();\n\t this.$( '.main-help' ).toggleClass( 'expanded' );\n\t this.$( '.more-help' ).toggle();\n\t },\n\t\n\t /** show an alert on the top of the interface containing message (alertClass is bootstrap's alert-*) */\n\t _showAlert : function( message, alertClass ){\n\t alertClass = alertClass || 'alert-danger';\n\t this.$( '.main-help' ).hide();\n\t this.$( '.header .alert' )\n\t .attr( 'class', 'alert alert-dismissable' ).addClass( alertClass ).show()\n\t .find( '.alert-message' ).html( message );\n\t },\n\t /** hide the alerts at the top */\n\t _hideAlert : function( message ){\n\t this.$( '.main-help' ).show();\n\t this.$( '.header .alert' ).hide();\n\t },\n\t\n\t // ........................................................................ elements\n\t /** reset all data to the initial state */\n\t reset : function(){\n\t this._instanceSetUp();\n\t this._elementsSetUp();\n\t this.render();\n\t },\n\t\n\t /** deselect all elements */\n\t clearSelectedElements : function( ev ){\n\t this.$( '.collection-elements .collection-element' ).removeClass( 'selected' );\n\t this.$( '.collection-elements-controls > .clear-selected' ).hide();\n\t },\n\t\n\t //_dragenterElements : function( ev ){\n\t // //this.debug( '_dragenterElements:', ev );\n\t //},\n\t//TODO: if selected are dragged out of the list area - remove the placeholder - cuz it won't work anyway\n\t // _dragleaveElements : function( ev ){\n\t // //this.debug( '_dragleaveElements:', ev );\n\t // },\n\t\n\t /** track the mouse drag over the list adding a placeholder to show where the drop would occur */\n\t _dragoverElements : function( ev ){\n\t //this.debug( '_dragoverElements:', ev );\n\t ev.preventDefault();\n\t\n\t var $list = this.$list();\n\t this._checkForAutoscroll( $list, ev.originalEvent.clientY );\n\t var $nearest = this._getNearestElement( ev.originalEvent.clientY );\n\t\n\t //TODO: no need to re-create - move instead\n\t this.$( '.element-drop-placeholder' ).remove();\n\t var $placeholder = $( '
    ' );\n\t if( !$nearest.length ){\n\t $list.append( $placeholder );\n\t } else {\n\t $nearest.before( $placeholder );\n\t }\n\t },\n\t\n\t /** If the mouse is near enough to the list's top or bottom, scroll the list */\n\t _checkForAutoscroll : function( $element, y ){\n\t var AUTOSCROLL_SPEED = 2,\n\t offset = $element.offset(),\n\t scrollTop = $element.scrollTop(),\n\t upperDist = y - offset.top,\n\t lowerDist = ( offset.top + $element.outerHeight() ) - y;\n\t if( upperDist >= 0 && upperDist < this.autoscrollDist ){\n\t $element.scrollTop( scrollTop - AUTOSCROLL_SPEED );\n\t } else if( lowerDist >= 0 && lowerDist < this.autoscrollDist ){\n\t $element.scrollTop( scrollTop + AUTOSCROLL_SPEED );\n\t }\n\t },\n\t\n\t /** get the nearest element based on the mouse's Y coordinate.\n\t * If the y is at the end of the list, return an empty jQuery object.\n\t */\n\t _getNearestElement : function( y ){\n\t var WIGGLE = 4,\n\t lis = this.$( '.collection-elements li.collection-element' ).toArray();\n\t for( var i=0; i y && top - halfHeight < y ){\n\t return $li;\n\t }\n\t }\n\t return $();\n\t },\n\t\n\t /** drop (dragged/selected elements) onto the list, re-ordering the internal list */\n\t _dropElements : function( ev ){\n\t if( ev.originalEvent ){ ev = ev.originalEvent; }\n\t // both required for firefox\n\t ev.preventDefault();\n\t ev.dataTransfer.dropEffect = 'move';\n\t\n\t // insert before the nearest element or after the last.\n\t var $nearest = this._getNearestElement( ev.clientY );\n\t if( $nearest.length ){\n\t this.$dragging.insertBefore( $nearest );\n\t } else {\n\t // no nearest before - insert after last element\n\t this.$dragging.insertAfter( this.$( '.collection-elements .collection-element' ).last() );\n\t }\n\t // resync the creator's list based on the new DOM order\n\t this._syncOrderToDom();\n\t return false;\n\t },\n\t\n\t /** resync the creator's list of elements based on the DOM order */\n\t _syncOrderToDom : function(){\n\t var creator = this,\n\t newElements = [];\n\t //TODO: doesn't seem wise to use the dom to store these - can't we sync another way?\n\t this.$( '.collection-elements .collection-element' ).each( function(){\n\t var id = $( this ).attr( 'data-element-id' ),\n\t element = _.findWhere( creator.workingElements, { id: id });\n\t if( element ){\n\t newElements.push( element );\n\t } else {\n\t console.error( 'missing element: ', id );\n\t }\n\t });\n\t this.workingElements = newElements;\n\t this._renderList();\n\t },\n\t\n\t /** drag communication with element sub-views: dragstart */\n\t _elementDragstart : function( ev, element ){\n\t // auto select the element causing the event and move all selected\n\t element.select( true );\n\t this.$dragging = this.$( '.collection-elements .collection-element.selected' );\n\t },\n\t\n\t /** drag communication with element sub-views: dragend - remove the placeholder */\n\t _elementDragend : function( ev, element ){\n\t $( '.element-drop-placeholder' ).remove();\n\t this.$dragging = null;\n\t },\n\t\n\t // ........................................................................ footer\n\t /** handle a collection name change */\n\t _changeName : function( ev ){\n\t this._validationWarning( 'name', !!this._getName() );\n\t },\n\t\n\t /** check for enter key press when in the collection name and submit */\n\t _nameCheckForEnter : function( ev ){\n\t if( ev.keyCode === 13 && !this.blocking ){\n\t this._clickCreate();\n\t }\n\t },\n\t\n\t /** get the current collection name */\n\t _getName : function(){\n\t return _.escape( this.$( '.collection-name' ).val() );\n\t },\n\t\n\t /** attempt to create the current collection */\n\t _clickCreate : function( ev ){\n\t var name = this._getName();\n\t if( !name ){\n\t this._validationWarning( 'name' );\n\t } else if( !this.blocking ){\n\t this.createList( name );\n\t }\n\t },\n\t\n\t // ------------------------------------------------------------------------ templates\n\t //TODO: move to require text plugin and load these as text\n\t //TODO: underscore currently unnecc. bc no vars are used\n\t //TODO: better way of localizing text-nodes in long strings\n\t /** underscore template fns attached to class */\n\t templates : {\n\t /** the skeleton */\n\t main : _.template([\n\t '
    ',\n\t '
    ',\n\t '
    '\n\t ].join('')),\n\t\n\t /** the header (not including help text) */\n\t header : _.template([\n\t '
    ',\n\t '', _l( 'More help' ), '',\n\t '
    ',\n\t '', _l( 'Less' ), '',\n\t '
    ',\n\t '
    ',\n\t '
    ',\n\t '',\n\t '',\n\t '
    ',\n\t ].join('')),\n\t\n\t /** the middle: element list */\n\t middle : _.template([\n\t '',\n\t '
    ',\n\t '
    '\n\t ].join('')),\n\t\n\t /** creation and cancel controls */\n\t footer : _.template([\n\t '
    ',\n\t '
    ',\n\t '',\n\t '
    ', _l( 'Name' ), ':
    ',\n\t '
    ',\n\t '
    ',\n\t\n\t '
    ',\n\t '
    ',\n\t '',\n\t '
    ',\n\t '',\n\t '',\n\t '
    ',\n\t '
    ',\n\t\n\t '
    ',\n\t '',\n\t '
    ',\n\t '
    '\n\t ].join('')),\n\t\n\t /** help content */\n\t helpContent : _.template([\n\t '

    ', _l([\n\t 'Collections of datasets are permanent, ordered lists of datasets that can be passed to tools and ',\n\t 'workflows in order to have analyses done on each member of the entire group. This interface allows ',\n\t 'you to create a collection and re-order the final collection.'\n\t ].join( '' )), '

    ',\n\t '
      ',\n\t '
    • ', _l([\n\t 'Rename elements in the list by clicking on ',\n\t 'the existing name.'\n\t ].join( '' )), '
    • ',\n\t '
    • ', _l([\n\t 'Discard elements from the final created list by clicking on the ',\n\t '\"Discard\" button.'\n\t ].join( '' )), '
    • ',\n\t '
    • ', _l([\n\t 'Reorder the list by clicking and dragging elements. Select multiple elements by clicking on ',\n\t 'them and you can then move those selected by dragging the ',\n\t 'entire group. Deselect them by clicking them again or by clicking the ',\n\t 'the \"Clear selected\" link.'\n\t ].join( '' )), '
    • ',\n\t '
    • ', _l([\n\t 'Click the \"Start over\" link to begin again as if you had just opened ',\n\t 'the interface.'\n\t ].join( '' )), '
    • ',\n\t '
    • ', _l([\n\t 'Click the \"Cancel\" button to exit the interface.'\n\t ].join( '' )), '
    • ',\n\t '

    ',\n\t '

    ', _l([\n\t 'Once your collection is complete, enter a name and ',\n\t 'click \"Create list\".'\n\t ].join( '' )), '

    '\n\t ].join('')),\n\t\n\t /** shown in list when all elements are discarded */\n\t invalidElements : _.template([\n\t _l( 'The following selections could not be included due to problems:' ),\n\t '
      <% _.each( problems, function( problem ){ %>',\n\t '
    • <%- problem.element.name %>: <%- problem.text %>
    • ',\n\t '<% }); %>
    '\n\t ].join('')),\n\t\n\t /** shown in list when all elements are discarded */\n\t noElementsLeft : _.template([\n\t '
  • ',\n\t _l( 'No elements left! ' ),\n\t _l( 'Would you like to ' ), '', _l( 'start over' ), '?',\n\t '
  • '\n\t ].join('')),\n\t\n\t /** a simplified page communicating what went wrong and why the user needs to reselect something else */\n\t invalidInitial : _.template([\n\t '
    ',\n\t '
    ',\n\t '',\n\t '<% if( _.size( problems ) ){ %>',\n\t _l( 'The following selections could not be included due to problems' ), ':',\n\t '
      <% _.each( problems, function( problem ){ %>',\n\t '
    • <%- problem.element.name %>: <%- problem.text %>
    • ',\n\t '<% }); %>
    ',\n\t '<% } else if( _.size( elements ) < 1 ){ %>',\n\t _l( 'No datasets were selected' ), '.',\n\t '<% } %>',\n\t '
    ',\n\t _l( 'At least one element is needed for the collection' ), '. ',\n\t _l( 'You may need to ' ),\n\t '', _l( 'cancel' ), ' ',\n\t _l( 'and reselect new elements' ), '.',\n\t '
    ',\n\t '
    ',\n\t '
    ',\n\t '
    ',\n\t '
    ',\n\t '
    ',\n\t '',\n\t // _l( 'Create a different kind of collection' ),\n\t '
    ',\n\t '
    ',\n\t '
    '\n\t ].join('')),\n\t },\n\t\n\t // ------------------------------------------------------------------------ misc\n\t /** string rep */\n\t toString : function(){ return 'ListCollectionCreator'; }\n\t});\n\t\n\t\n\t\n\t//=============================================================================\n\t/** Create a modal and load its body with the given CreatorClass creator type\n\t * @returns {Deferred} resolved when creator has built a collection.\n\t */\n\tvar collectionCreatorModal = function _collectionCreatorModal( elements, options, CreatorClass ){\n\t\n\t var deferred = jQuery.Deferred(),\n\t modal = Galaxy.modal || ( new UI_MODAL.View() ),\n\t creator;\n\t\n\t options = _.defaults( options || {}, {\n\t elements : elements,\n\t oncancel : function(){\n\t modal.hide();\n\t deferred.reject( 'cancelled' );\n\t },\n\t oncreate : function( creator, response ){\n\t modal.hide();\n\t deferred.resolve( response );\n\t }\n\t });\n\t\n\t creator = new CreatorClass( options );\n\t modal.show({\n\t title : options.title || _l( 'Create a collection' ),\n\t body : creator.$el,\n\t width : '80%',\n\t height : '100%',\n\t closing_events: true\n\t });\n\t creator.render();\n\t window._collectionCreator = creator;\n\t\n\t //TODO: remove modal header\n\t return deferred;\n\t};\n\t\n\t/** List collection flavor of collectionCreatorModal. */\n\tvar listCollectionCreatorModal = function _listCollectionCreatorModal( elements, options ){\n\t options = options || {};\n\t options.title = _l( 'Create a collection from a list of datasets' );\n\t return collectionCreatorModal( elements, options, ListCollectionCreator );\n\t};\n\t\n\t\n\t//==============================================================================\n\t/** Use a modal to create a list collection, then add it to the given history contents.\n\t * @returns {Deferred} resolved when the collection is added to the history.\n\t */\n\tfunction createListCollection( contents ){\n\t var elements = contents.toJSON(),\n\t promise = listCollectionCreatorModal( elements, {\n\t creationFn : function( elements, name ){\n\t elements = elements.map( function( element ){\n\t return {\n\t id : element.id,\n\t name : element.name,\n\t //TODO: this allows for list:list even if the filter above does not - reconcile\n\t src : ( element.history_content_type === 'dataset'? 'hda' : 'hdca' )\n\t };\n\t });\n\t return contents.createHDCA( elements, 'list', name );\n\t }\n\t });\n\t return promise;\n\t}\n\t\n\t//==============================================================================\n\t return {\n\t DatasetCollectionElementView: DatasetCollectionElementView,\n\t ListCollectionCreator : ListCollectionCreator,\n\t\n\t collectionCreatorModal : collectionCreatorModal,\n\t listCollectionCreatorModal : listCollectionCreatorModal,\n\t createListCollection : createListCollection\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(2), __webpack_require__(1), __webpack_require__(1)))\n\n/***/ },\n/* 32 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(jQuery, Backbone, $, _) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(42),\n\t __webpack_require__(12),\n\t __webpack_require__(22),\n\t __webpack_require__(6),\n\t __webpack_require__(5)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( LIST_ITEM, STATES, faIconButton, BASE_MVC, _l ){\n\t'use strict';\n\t\n\tvar logNamespace = 'dataset';\n\t/*==============================================================================\n\tTODO:\n\t straighten out state rendering and templates used\n\t inaccessible/STATES.NOT_VIEWABLE is a special case\n\t simplify button rendering\n\t\n\t==============================================================================*/\n\tvar _super = LIST_ITEM.ListItemView;\n\t/** @class Read only list view for either LDDAs, HDAs, or HDADCEs.\n\t * Roughly, any DatasetInstance (and not a raw Dataset).\n\t */\n\tvar DatasetListItemView = _super.extend(\n\t/** @lends DatasetListItemView.prototype */{\n\t _logNamespace : logNamespace,\n\t\n\t className : _super.prototype.className + \" dataset\",\n\t //TODO:?? doesn't exactly match an hda's type_id\n\t id : function(){\n\t return [ 'dataset', this.model.get( 'id' ) ].join( '-' );\n\t },\n\t\n\t /** Set up: instance vars, options, and event handlers */\n\t initialize : function( attributes ){\n\t if( attributes.logger ){ this.logger = this.model.logger = attributes.logger; }\n\t this.log( this + '.initialize:', attributes );\n\t _super.prototype.initialize.call( this, attributes );\n\t\n\t /** where should pages from links be displayed? (default to new tab/window) */\n\t this.linkTarget = attributes.linkTarget || '_blank';\n\t },\n\t\n\t /** event listeners */\n\t _setUpListeners : function(){\n\t _super.prototype._setUpListeners.call( this );\n\t var self = this;\n\t\n\t // re-rendering on any model changes\n\t return self.listenTo( self.model, {\n\t 'change': function( model, options ){\n\t // if the model moved into the ready state and is expanded without details, fetch those details now\n\t if( self.model.changedAttributes().state\n\t && self.model.inReadyState()\n\t && self.expanded\n\t && !self.model.hasDetails() ){\n\t // normally, will render automatically (due to fetch -> change),\n\t // but! setting_metadata sometimes doesn't cause any other changes besides state\n\t // so, not rendering causes it to seem frozen in setting_metadata state\n\t self.model.fetch({ silent : true })\n\t .done( function(){ self.render(); });\n\t\n\t } else {\n\t self.render();\n\t }\n\t }\n\t });\n\t },\n\t\n\t // ......................................................................... expandable\n\t /** In this override, only get details if in the ready state, get rerunnable if in other states.\n\t * Note: fetch with no 'change' event triggering to prevent automatic rendering.\n\t */\n\t _fetchModelDetails : function(){\n\t var view = this;\n\t if( view.model.inReadyState() && !view.model.hasDetails() ){\n\t return view.model.fetch({ silent: true });\n\t }\n\t return jQuery.when();\n\t },\n\t\n\t // ......................................................................... removal\n\t /** Remove this view's html from the DOM and remove all event listeners.\n\t * @param {Number or String} speed jq effect speed\n\t * @param {Function} callback an optional function called when removal is done (scoped to this view)\n\t */\n\t remove : function( speed, callback ){\n\t var view = this;\n\t speed = speed || this.fxSpeed;\n\t this.$el.fadeOut( speed, function(){\n\t Backbone.View.prototype.remove.call( view );\n\t if( callback ){ callback.call( view ); }\n\t });\n\t },\n\t\n\t // ......................................................................... rendering\n\t /* TODO:\n\t dataset states are the issue primarily making dataset rendering complex\n\t each state should have it's own way of displaying/set of details\n\t often with different actions that can be applied\n\t throw in deleted/purged/visible and things get complicated easily\n\t I've considered (a couple of times) - creating a view for each state\n\t - but recreating the view during an update...seems wrong\n\t */\n\t /** In this override, add the dataset state as a class for use with state-based CSS */\n\t _swapNewRender : function( $newRender ){\n\t _super.prototype._swapNewRender.call( this, $newRender );\n\t if( this.model.has( 'state' ) ){\n\t this.$el.addClass( 'state-' + this.model.get( 'state' ) );\n\t }\n\t return this.$el;\n\t },\n\t\n\t // ................................................................................ titlebar\n\t /** In this override, add the dataset display button. */\n\t _renderPrimaryActions : function(){\n\t // render just the display for read-only\n\t return [ this._renderDisplayButton() ];\n\t },\n\t\n\t /** Render icon-button to display dataset data */\n\t _renderDisplayButton : function(){\n\t // don't show display if not viewable or not accessible\n\t var state = this.model.get( 'state' );\n\t if( ( state === STATES.NOT_VIEWABLE )\n\t || ( state === STATES.DISCARDED )\n\t || ( !this.model.get( 'accessible' ) ) ){\n\t return null;\n\t }\n\t\n\t var displayBtnData = {\n\t target : this.linkTarget,\n\t classes : 'display-btn'\n\t };\n\t\n\t // show a disabled display if the data's been purged\n\t if( this.model.get( 'purged' ) ){\n\t displayBtnData.disabled = true;\n\t displayBtnData.title = _l( 'Cannot display datasets removed from disk' );\n\t\n\t // disable if still uploading\n\t } else if( state === STATES.UPLOAD ){\n\t displayBtnData.disabled = true;\n\t displayBtnData.title = _l( 'This dataset must finish uploading before it can be viewed' );\n\t\n\t // disable if still new\n\t } else if( state === STATES.NEW ){\n\t displayBtnData.disabled = true;\n\t displayBtnData.title = _l( 'This dataset is not yet viewable' );\n\t\n\t } else {\n\t displayBtnData.title = _l( 'View data' );\n\t\n\t // default link for dataset\n\t displayBtnData.href = this.model.urls.display;\n\t\n\t // add frame manager option onclick event\n\t var self = this;\n\t displayBtnData.onclick = function( ev ){\n\t if (Galaxy.frame && Galaxy.frame.active) {\n\t // Add dataset to frames.\n\t Galaxy.frame.addDataset(self.model.get('id'));\n\t ev.preventDefault();\n\t }\n\t };\n\t }\n\t displayBtnData.faIcon = 'fa-eye';\n\t return faIconButton( displayBtnData );\n\t },\n\t\n\t // ......................................................................... rendering details\n\t /** Render the enclosing div of the hda body and, if expanded, the html in the body\n\t * @returns {jQuery} rendered DOM\n\t */\n\t _renderDetails : function(){\n\t //TODO: generalize to be allow different details for each state\n\t\n\t // no access - render nothing but a message\n\t if( this.model.get( 'state' ) === STATES.NOT_VIEWABLE ){\n\t return $( this.templates.noAccess( this.model.toJSON(), this ) );\n\t }\n\t\n\t var $details = _super.prototype._renderDetails.call( this );\n\t $details.find( '.actions .left' ).empty().append( this._renderSecondaryActions() );\n\t $details.find( '.summary' ).html( this._renderSummary() )\n\t .prepend( this._renderDetailMessages() );\n\t $details.find( '.display-applications' ).html( this._renderDisplayApplications() );\n\t\n\t this._setUpBehaviors( $details );\n\t return $details;\n\t },\n\t\n\t /** Defer to the appropo summary rendering fn based on state */\n\t _renderSummary : function(){\n\t var json = this.model.toJSON(),\n\t summaryRenderFn = this.templates.summaries[ json.state ];\n\t summaryRenderFn = summaryRenderFn || this.templates.summaries.unknown;\n\t return summaryRenderFn( json, this );\n\t },\n\t\n\t /** Render messages to be displayed only when the details are shown */\n\t _renderDetailMessages : function(){\n\t var view = this,\n\t $warnings = $( '
    ' ),\n\t json = view.model.toJSON();\n\t //TODO:! unordered (map)\n\t _.each( view.templates.detailMessages, function( templateFn ){\n\t $warnings.append( $( templateFn( json, view ) ) );\n\t });\n\t return $warnings;\n\t },\n\t\n\t /** Render the external display application links */\n\t _renderDisplayApplications : function(){\n\t if( this.model.isDeletedOrPurged() ){ return ''; }\n\t // render both old and new display apps using the same template\n\t return [\n\t this.templates.displayApplications( this.model.get( 'display_apps' ), this ),\n\t this.templates.displayApplications( this.model.get( 'display_types' ), this )\n\t ].join( '' );\n\t },\n\t\n\t // ......................................................................... secondary/details actions\n\t /** A series of links/buttons for less commonly used actions: re-run, info, etc. */\n\t _renderSecondaryActions : function(){\n\t this.debug( '_renderSecondaryActions' );\n\t switch( this.model.get( 'state' ) ){\n\t case STATES.NOT_VIEWABLE:\n\t return [];\n\t case STATES.OK:\n\t case STATES.FAILED_METADATA:\n\t case STATES.ERROR:\n\t return [ this._renderDownloadButton(), this._renderShowParamsButton() ];\n\t }\n\t return [ this._renderShowParamsButton() ];\n\t },\n\t\n\t /** Render icon-button to show the input and output (stdout/err) for the job that created this.\n\t * @returns {jQuery} rendered DOM\n\t */\n\t _renderShowParamsButton : function(){\n\t // gen. safe to show in all cases\n\t return faIconButton({\n\t title : _l( 'View details' ),\n\t classes : 'params-btn',\n\t href : this.model.urls.show_params,\n\t target : this.linkTarget,\n\t faIcon : 'fa-info-circle',\n\t onclick : function( ev ) {\n\t if ( Galaxy.frame && Galaxy.frame.active ) {\n\t Galaxy.frame.add( { title: 'Dataset details', url: this.href } );\n\t ev.preventDefault();\n\t ev.stopPropagation();\n\t }\n\t }\n\t });\n\t },\n\t\n\t /** Render icon-button/popupmenu to download the data (and/or the associated meta files (bai, etc.)) for this.\n\t * @returns {jQuery} rendered DOM\n\t */\n\t _renderDownloadButton : function(){\n\t // don't show anything if the data's been purged\n\t if( this.model.get( 'purged' ) || !this.model.hasData() ){ return null; }\n\t\n\t // return either: a popupmenu with links to download assoc. meta files (if there are meta files)\n\t // or a single download icon-button (if there are no meta files)\n\t if( !_.isEmpty( this.model.get( 'meta_files' ) ) ){\n\t return this._renderMetaFileDownloadButton();\n\t }\n\t\n\t return $([\n\t '',\n\t '',\n\t ''\n\t ].join( '' ));\n\t },\n\t\n\t /** Render the download button which opens a dropdown with links to download assoc. meta files (indeces, etc.) */\n\t _renderMetaFileDownloadButton : function(){\n\t var urls = this.model.urls;\n\t return $([\n\t '
    ',\n\t '',\n\t '',\n\t '',\n\t '',\n\t '
    '\n\t ].join( '\\n' ));\n\t },\n\t\n\t // ......................................................................... misc\n\t events : _.extend( _.clone( _super.prototype.events ), {\n\t 'click .display-btn' : function( ev ){ this.trigger( 'display', this, ev ); },\n\t 'click .params-btn' : function( ev ){ this.trigger( 'params', this, ev ); },\n\t 'click .download-btn' : function( ev ){ this.trigger( 'download', this, ev ); }\n\t }),\n\t\n\t // ......................................................................... misc\n\t /** String representation */\n\t toString : function(){\n\t var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n\t return 'DatasetListItemView(' + modelString + ')';\n\t }\n\t});\n\t\n\t// ............................................................................ TEMPLATES\n\t/** underscore templates */\n\tDatasetListItemView.prototype.templates = (function(){\n\t//TODO: move to require text! plugin\n\t\n\t var warnings = _.extend( {}, _super.prototype.templates.warnings, {\n\t failed_metadata : BASE_MVC.wrapTemplate([\n\t // failed metadata is rendered as a warning on an otherwise ok dataset view\n\t '<% if( model.state === \"failed_metadata\" ){ %>',\n\t '
    ',\n\t _l( 'An error occurred setting the metadata for this dataset' ),\n\t '
    ',\n\t '<% } %>'\n\t ]),\n\t error : BASE_MVC.wrapTemplate([\n\t // error during index fetch - show error on dataset\n\t '<% if( model.error ){ %>',\n\t '
    ',\n\t _l( 'There was an error getting the data for this dataset' ), ': <%- model.error %>',\n\t '
    ',\n\t '<% } %>'\n\t ]),\n\t purged : BASE_MVC.wrapTemplate([\n\t '<% if( model.purged ){ %>',\n\t '
    ',\n\t _l( 'This dataset has been deleted and removed from disk' ),\n\t '
    ',\n\t '<% } %>'\n\t ]),\n\t deleted : BASE_MVC.wrapTemplate([\n\t // deleted not purged\n\t '<% if( model.deleted && !model.purged ){ %>',\n\t '
    ',\n\t _l( 'This dataset has been deleted' ),\n\t '
    ',\n\t '<% } %>'\n\t ])\n\t\n\t //NOTE: hidden warning is only needed for HDAs\n\t });\n\t\n\t var detailsTemplate = BASE_MVC.wrapTemplate([\n\t '
    ',\n\t '
    ',\n\t\n\t '
    ',\n\t '
    ',\n\t '
    ',\n\t '
    ',\n\t\n\t // do not display tags, annotation, display apps, or peek when deleted\n\t '<% if( !dataset.deleted && !dataset.purged ){ %>',\n\t '
    ',\n\t '
    ',\n\t\n\t '
    ',\n\t\n\t '<% if( dataset.peek ){ %>',\n\t '
    <%= dataset.peek %>
    ',\n\t '<% } %>',\n\t '<% } %>',\n\t '
    '\n\t ], 'dataset' );\n\t\n\t var noAccessTemplate = BASE_MVC.wrapTemplate([\n\t '
    ',\n\t '
    ',\n\t _l( 'You do not have permission to view this dataset' ),\n\t '
    ',\n\t '
    '\n\t ], 'dataset' );\n\t\n\t//TODO: still toooooooooooooo complex - rework\n\t var summaryTemplates = {};\n\t summaryTemplates[ STATES.OK ] = summaryTemplates[ STATES.FAILED_METADATA ] = BASE_MVC.wrapTemplate([\n\t '<% if( dataset.misc_blurb ){ %>',\n\t '
    ',\n\t '<%- dataset.misc_blurb %>',\n\t '
    ',\n\t '<% } %>',\n\t\n\t '<% if( dataset.file_ext ){ %>',\n\t '
    ',\n\t '',\n\t '<%- dataset.file_ext %>',\n\t '
    ',\n\t '<% } %>',\n\t\n\t '<% if( dataset.metadata_dbkey ){ %>',\n\t '
    ',\n\t '',\n\t '',\n\t '<%- dataset.metadata_dbkey %>',\n\t '',\n\t '
    ',\n\t '<% } %>',\n\t\n\t '<% if( dataset.misc_info ){ %>',\n\t '
    ',\n\t '<%- dataset.misc_info %>',\n\t '
    ',\n\t '<% } %>'\n\t ], 'dataset' );\n\t summaryTemplates[ STATES.NEW ] = BASE_MVC.wrapTemplate([\n\t '
    ', _l( 'This is a new dataset and not all of its data are available yet' ), '
    '\n\t ], 'dataset' );\n\t summaryTemplates[ STATES.NOT_VIEWABLE ] = BASE_MVC.wrapTemplate([\n\t '
    ', _l( 'You do not have permission to view this dataset' ), '
    '\n\t ], 'dataset' );\n\t summaryTemplates[ STATES.DISCARDED ] = BASE_MVC.wrapTemplate([\n\t '
    ', _l( 'The job creating this dataset was cancelled before completion' ), '
    '\n\t ], 'dataset' );\n\t summaryTemplates[ STATES.QUEUED ] = BASE_MVC.wrapTemplate([\n\t '
    ', _l( 'This job is waiting to run' ), '
    '\n\t ], 'dataset' );\n\t summaryTemplates[ STATES.RUNNING ] = BASE_MVC.wrapTemplate([\n\t '
    ', _l( 'This job is currently running' ), '
    '\n\t ], 'dataset' );\n\t summaryTemplates[ STATES.UPLOAD ] = BASE_MVC.wrapTemplate([\n\t '
    ', _l( 'This dataset is currently uploading' ), '
    '\n\t ], 'dataset' );\n\t summaryTemplates[ STATES.SETTING_METADATA ] = BASE_MVC.wrapTemplate([\n\t '
    ', _l( 'Metadata is being auto-detected' ), '
    '\n\t ], 'dataset' );\n\t summaryTemplates[ STATES.PAUSED ] = BASE_MVC.wrapTemplate([\n\t '
    ', _l( 'This job is paused. Use the \"Resume Paused Jobs\" in the history menu to resume' ), '
    '\n\t ], 'dataset' );\n\t summaryTemplates[ STATES.ERROR ] = BASE_MVC.wrapTemplate([\n\t '<% if( !dataset.purged ){ %>',\n\t '
    <%- dataset.misc_blurb %>
    ',\n\t '<% } %>',\n\t '', _l( 'An error occurred with this dataset' ), ':',\n\t '
    <%- dataset.misc_info %>
    '\n\t ], 'dataset' );\n\t summaryTemplates[ STATES.EMPTY ] = BASE_MVC.wrapTemplate([\n\t '
    ', _l( 'No data' ), ': <%- dataset.misc_blurb %>
    '\n\t ], 'dataset' );\n\t summaryTemplates.unknown = BASE_MVC.wrapTemplate([\n\t '
    Error: unknown dataset state: \"<%- dataset.state %>\"
    '\n\t ], 'dataset' );\n\t\n\t // messages to be displayed only within the details section ('below the fold')\n\t var detailMessageTemplates = {\n\t resubmitted : BASE_MVC.wrapTemplate([\n\t // deleted not purged\n\t '<% if( model.resubmitted ){ %>',\n\t '
    ',\n\t _l( 'The job creating this dataset has been resubmitted' ),\n\t '
    ',\n\t '<% } %>'\n\t ])\n\t };\n\t\n\t // this is applied to both old and new style display apps\n\t var displayApplicationsTemplate = BASE_MVC.wrapTemplate([\n\t '<% _.each( apps, function( app ){ %>',\n\t '
    ',\n\t '<%- app.label %> ',\n\t '',\n\t '<% _.each( app.links, function( link ){ %>',\n\t '\" href=\"<%- link.href %>\">',\n\t '<% print( _l( link.text ) ); %>',\n\t ' ',\n\t '<% }); %>',\n\t '',\n\t '
    ',\n\t '<% }); %>'\n\t ], 'apps' );\n\t\n\t return _.extend( {}, _super.prototype.templates, {\n\t warnings : warnings,\n\t details : detailsTemplate,\n\t noAccess : noAccessTemplate,\n\t summaries : summaryTemplates,\n\t detailMessages : detailMessageTemplates,\n\t displayApplications : displayApplicationsTemplate\n\t });\n\t}());\n\t\n\t\n\t// ============================================================================\n\t return {\n\t DatasetListItemView : DatasetListItemView\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(1), __webpack_require__(3), __webpack_require__(1), __webpack_require__(2)))\n\n/***/ },\n/* 33 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, $, _) {/* This class maps the form dom to an api compatible javascript dictionary. */\n\t!(__WEBPACK_AMD_DEFINE_ARRAY__ = [ __webpack_require__(4) ], __WEBPACK_AMD_DEFINE_RESULT__ = function( Utils ) {\n\t var Manager = Backbone.Model.extend({\n\t initialize: function( app ) {\n\t this.app = app;\n\t },\n\t\n\t /** Creates a checksum. */\n\t checksum: function() {\n\t var sum = '';\n\t var self = this;\n\t this.app.section.$el.find( '.section-row' ).each( function() {\n\t var id = $(this).attr( 'id' );\n\t var field = self.app.field_list[ id ];\n\t if ( field ) {\n\t sum += id + ':' + JSON.stringify( field.value && field.value() ) + ':' + field.collapsed + ';';\n\t }\n\t });\n\t return sum;\n\t },\n\t\n\t /** Convert dom into a dictionary of flat id/value pairs used e.g. on job submission. */\n\t create: function() {\n\t var self = this;\n\t\n\t // get raw dictionary from dom\n\t var dict = {};\n\t this._iterate( this.app.section.$el, dict );\n\t\n\t // add to result dictionary, label elements\n\t var result_dict = {};\n\t this.flat_dict = {};\n\t function add( flat_id, input_id, input_value ) {\n\t self.flat_dict[ flat_id ] = input_id;\n\t result_dict[ flat_id ] = input_value;\n\t self.app.element_list[ input_id ] && self.app.element_list[ input_id ].$el.attr( 'tour_id', flat_id );\n\t }\n\t // converter between raw dictionary and job dictionary\n\t function convert( identifier, head ) {\n\t for ( var index in head ) {\n\t var node = head[ index ];\n\t if ( node.input ) {\n\t var input = node.input;\n\t var flat_id = identifier;\n\t if ( identifier != '' ) {\n\t flat_id += '|';\n\t }\n\t flat_id += input.name;\n\t switch ( input.type ) {\n\t case 'repeat':\n\t var section_label = 'section-';\n\t var block_indices = [];\n\t var block_prefix = null;\n\t for ( var block_label in node ) {\n\t var pos = block_label.indexOf( section_label );\n\t if ( pos != -1 ) {\n\t pos += section_label.length;\n\t block_indices.push( parseInt( block_label.substr( pos ) ));\n\t if ( !block_prefix ) {\n\t block_prefix = block_label.substr( 0, pos );\n\t }\n\t }\n\t }\n\t block_indices.sort( function( a, b ) { return a - b; });\n\t var index = 0;\n\t for ( var i in block_indices ) {\n\t convert( flat_id + '_' + index++, node[ block_prefix + block_indices[ i ] ]);\n\t }\n\t break;\n\t case 'conditional':\n\t var value = self.app.field_list[ input.id ].value();\n\t add( flat_id + '|' + input.test_param.name, input.id, value );\n\t var selectedCase = matchCase( input, value );\n\t if ( selectedCase != -1 ) {\n\t convert( flat_id, head[ input.id + '-section-' + selectedCase ] );\n\t }\n\t break;\n\t case 'section':\n\t convert( !input.flat && flat_id || '', node );\n\t break;\n\t default:\n\t var field = self.app.field_list[ input.id ];\n\t if ( field && field.value ) {\n\t var value = field.value();\n\t if ( input.ignore === undefined || input.ignore != value ) {\n\t if ( field.collapsed && input.collapsible_value ) {\n\t value = input.collapsible_value;\n\t }\n\t add( flat_id, input.id, value );\n\t if ( input.payload ) {\n\t for ( var p_id in input.payload ) {\n\t add( p_id, input.id, input.payload[ p_id ] );\n\t }\n\t }\n\t }\n\t }\n\t }\n\t }\n\t }\n\t }\n\t convert( '', dict );\n\t return result_dict;\n\t },\n\t\n\t /** Matches flat ids to corresponding input element\n\t * @param{string} flat_id - Flat input id to be looked up.\n\t */\n\t match: function ( flat_id ) {\n\t return this.flat_dict && this.flat_dict[ flat_id ];\n\t },\n\t\n\t /** Match conditional values to selected cases\n\t */\n\t matchCase: function( input, value ) {\n\t return matchCase( input, value );\n\t },\n\t\n\t /** Matches a new tool model to the current input elements e.g. used to update dynamic options\n\t */\n\t matchModel: function( model, callback ) {\n\t var self = this;\n\t visitInputs( model.inputs, function( input, name ) {\n\t self.flat_dict[ name ] && callback ( input, self.flat_dict[ name ] );\n\t });\n\t },\n\t\n\t /** Matches identifier from api response to input elements e.g. used to display validation errors\n\t */\n\t matchResponse: function( response ) {\n\t var result = {};\n\t var self = this;\n\t function search ( id, head ) {\n\t if ( typeof head === 'string' ) {\n\t var input_id = self.flat_dict[ id ];\n\t input_id && ( result[ input_id ] = head );\n\t } else {\n\t for ( var i in head ) {\n\t var new_id = i;\n\t if ( id !== '' ) {\n\t var separator = '|';\n\t if ( head instanceof Array ) {\n\t separator = '_';\n\t }\n\t new_id = id + separator + new_id;\n\t }\n\t search ( new_id, head[ i ] );\n\t }\n\t }\n\t }\n\t search( '', response );\n\t return result;\n\t },\n\t\n\t /** Map dom tree to dictionary tree with input elements.\n\t */\n\t _iterate: function( parent, dict ) {\n\t var self = this;\n\t var children = $( parent ).children();\n\t children.each( function() {\n\t var child = this;\n\t var id = $( child ).attr( 'id' );\n\t if ( $( child ).hasClass( 'section-row' ) ) {\n\t var input = self.app.input_list[ id ];\n\t dict[ id ] = ( input && { input : input } ) || {};\n\t self._iterate( child, dict[ id ] );\n\t } else {\n\t self._iterate( child, dict );\n\t }\n\t });\n\t }\n\t });\n\t\n\t /** Match conditional values to selected cases\n\t * @param{dict} input - Definition of conditional input parameter\n\t * @param{dict} value - Current value\n\t */\n\t var matchCase = function( input, value ) {\n\t if ( input.test_param.type == 'boolean' ) {\n\t if ( value == 'true' ) {\n\t value = input.test_param.truevalue || 'true';\n\t } else {\n\t value = input.test_param.falsevalue || 'false';\n\t }\n\t }\n\t for ( var i in input.cases ) {\n\t if ( input.cases[ i ].value == value ) {\n\t return i;\n\t }\n\t }\n\t return -1;\n\t };\n\t\n\t /** Visits tool inputs\n\t * @param{dict} inputs - Nested dictionary of input elements\n\t * @param{dict} callback - Called with the mapped dictionary object and corresponding model node\n\t */\n\t var visitInputs = function( inputs, callback, prefix, context ) {\n\t context = $.extend( true, {}, context );\n\t _.each( inputs, function ( input ) {\n\t if ( input && input.type && input.name ) {\n\t context[ input.name ] = input;\n\t }\n\t });\n\t for ( var key in inputs ) {\n\t var node = inputs[ key ];\n\t node.name = node.name || key;\n\t var name = prefix ? prefix + '|' + node.name : node.name;\n\t switch ( node.type ) {\n\t case 'repeat':\n\t _.each( node.cache, function( cache, j ) {\n\t visitInputs( cache, callback, name + '_' + j, context );\n\t });\n\t break;\n\t case 'conditional':\n\t if ( node.test_param ) {\n\t callback( node.test_param, name + '|' + node.test_param.name, context );\n\t var selectedCase = matchCase( node, node.test_param.value );\n\t if ( selectedCase != -1 ) {\n\t visitInputs( node.cases[ selectedCase ].inputs, callback, name, context );\n\t } else {\n\t Galaxy.emit.debug( 'form-data::visitInputs() - Invalid case for ' + name + '.' );\n\t }\n\t } else {\n\t Galaxy.emit.debug( 'form-data::visitInputs() - Conditional test parameter missing for ' + name + '.' );\n\t }\n\t break;\n\t case 'section':\n\t visitInputs( node.inputs, callback, name, context )\n\t break;\n\t default:\n\t callback( node, name, context );\n\t }\n\t }\n\t };\n\t\n\t return {\n\t Manager : Manager,\n\t visitInputs : visitInputs\n\t }\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(1), __webpack_require__(2)))\n\n/***/ },\n/* 34 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, $, _) {/**\n\t This class creates a form input element wrapper\n\t*/\n\t!(__WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_RESULT__ = function() {\n\t return Backbone.View.extend({\n\t initialize: function( app, options ) {\n\t this.app = app;\n\t this.app_options = app.options || {};\n\t this.field = options && options.field || new Backbone.View();\n\t this.model = options && options.model || new Backbone.Model({\n\t text_enable : this.app_options.text_enable || 'Enable',\n\t text_disable : this.app_options.text_disable || 'Disable',\n\t cls_enable : this.app_options.cls_enable || 'fa fa-caret-square-o-down',\n\t cls_disable : this.app_options.cls_disable || 'fa fa-caret-square-o-up'\n\t }).set( options );\n\t\n\t // set element and link components\n\t this.setElement( this._template() );\n\t this.$field = this.$( '.ui-form-field' );\n\t this.$info = this.$( '.ui-form-info' );\n\t this.$preview = this.$( '.ui-form-preview' );\n\t this.$collapsible = this.$( '.ui-form-collapsible' );\n\t this.$collapsible_text = this.$( '.ui-form-collapsible-text' );\n\t this.$collapsible_icon = this.$( '.ui-form-collapsible-icon' );\n\t this.$title = this.$( '.ui-form-title' );\n\t this.$title_text = this.$( '.ui-form-title-text' );\n\t this.$error_text = this.$( '.ui-form-error-text' );\n\t this.$error = this.$( '.ui-form-error' );\n\t this.$backdrop = this.$( '.ui-form-backdrop' );\n\t\n\t // add field element\n\t this.$field.prepend( this.field.$el );\n\t\n\t // decide wether to expand or collapse fields\n\t var collapsible_value = this.model.get( 'collapsible_value' );\n\t this.field.collapsed = collapsible_value !== undefined && JSON.stringify( this.model.get( 'value' ) ) == JSON.stringify( collapsible_value );\n\t this.listenTo( this.model, 'change', this.render, this );\n\t this.render();\n\t\n\t // add click handler\n\t var self = this;\n\t this.$collapsible.on( 'click', function() {\n\t self.field.collapsed = !self.field.collapsed;\n\t app.trigger && app.trigger( 'change' );\n\t self.render();\n\t });\n\t },\n\t\n\t /** Set backdrop for input element\n\t */\n\t backdrop: function() {\n\t this.model.set( 'backdrop', true );\n\t },\n\t\n\t /** Set error text\n\t */\n\t error: function( text ) {\n\t this.model.set( 'error_text', text );\n\t },\n\t\n\t /** Reset this view\n\t */\n\t reset: function() {\n\t this.model.set( 'error_text', null );\n\t },\n\t\n\t render: function() {\n\t // render help\n\t $( '.tooltip' ).hide();\n\t var help_text = this.model.get( 'help', '' );\n\t var help_argument = this.model.get( 'argument' );\n\t if ( help_argument && help_text.indexOf( '(' + help_argument + ')' ) == -1 ) {\n\t help_text += ' (' + help_argument + ')';\n\t }\n\t this.$info.html( help_text );\n\t // render visibility\n\t this.$el[ this.model.get( 'hidden' ) ? 'hide' : 'show' ]();\n\t // render preview view for collapsed fields\n\t this.$preview[ ( this.field.collapsed && this.model.get( 'collapsible_preview' ) || this.model.get( 'disabled' ) ) ? 'show' : 'hide' ]()\n\t .html( _.escape( this.model.get( 'text_value' ) ) );\n\t // render error messages\n\t var error_text = this.model.get( 'error_text' );\n\t this.$error[ error_text ? 'show' : 'hide' ]();\n\t this.$el[ error_text ? 'addClass' : 'removeClass' ]( 'ui-error' );\n\t this.$error_text.html( error_text );\n\t // render backdrop\n\t this.$backdrop[ this.model.get( 'backdrop' ) ? 'show' : 'hide' ]();\n\t // render input field\n\t this.field.collapsed || this.model.get( 'disabled' ) ? this.$field.hide() : this.$field.show();\n\t // render input field color and style\n\t this.field.model && this.field.model.set( { 'color': this.model.get( 'color' ), 'style': this.model.get( 'style' ) } );\n\t // render collapsible options\n\t if ( !this.model.get( 'disabled' ) && this.model.get( 'collapsible_value' ) !== undefined ) {\n\t var collapsible_state = this.field.collapsed ? 'enable' : 'disable';\n\t this.$title_text.hide();\n\t this.$collapsible.show();\n\t this.$collapsible_text.text( this.model.get( 'label' ) );\n\t this.$collapsible_icon.removeClass().addClass( 'icon' )\n\t .addClass( this.model.get( 'cls_' + collapsible_state ) )\n\t .attr( 'data-original-title', this.model.get( 'text_' + collapsible_state ) )\n\t .tooltip( { placement: 'bottom' } );\n\t } else {\n\t this.$title_text.show().text( this.model.get( 'label' ) );\n\t this.$collapsible.hide();\n\t }\n\t },\n\t\n\t _template: function() {\n\t return $( '
    ' ).addClass( 'ui-form-element' )\n\t .append( $( '
    ' ).addClass( 'ui-form-error ui-error' )\n\t .append( $( '' ).addClass( 'fa fa-arrow-down' ) )\n\t .append( $( '' ).addClass( 'ui-form-error-text' ) )\n\t )\n\t .append( $( '
    ' ).addClass( 'ui-form-title' )\n\t .append( $( '
    ' ).addClass( 'ui-form-collapsible' )\n\t .append( $( '' ).addClass( 'ui-form-collapsible-icon' ) )\n\t .append( $( '' ).addClass( 'ui-form-collapsible-text' ) )\n\t )\n\t .append( $( '' ).addClass( 'ui-form-title-text' ) )\n\t )\n\t .append( $( '
    ' ).addClass( 'ui-form-field' )\n\t .append( $( '' ).addClass( 'ui-form-info' ) )\n\t .append( $( '
    ' ).addClass( 'ui-form-backdrop' ) )\n\t )\n\t .append( $( '
    ' ).addClass( 'ui-form-preview' ) );\n\t }\n\t });\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(1), __webpack_require__(2)))\n\n/***/ },\n/* 35 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, _, $) {/**\n\t This class creates input elements. New input parameter types should be added to the types dictionary.\n\t*/\n\t!(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(4),\n\t __webpack_require__(7),\n\t __webpack_require__(48),\n\t __webpack_require__(50),\n\t __webpack_require__(49),\n\t __webpack_require__(46)], __WEBPACK_AMD_DEFINE_RESULT__ = function( Utils, Ui, SelectContent, SelectLibrary, SelectFtp, ColorPicker ) {\n\t\n\t // create form view\n\t return Backbone.Model.extend({\n\t /** Available parameter types */\n\t types: {\n\t 'text' : '_fieldText',\n\t 'select' : '_fieldSelect',\n\t 'data_column' : '_fieldSelect',\n\t 'genomebuild' : '_fieldSelect',\n\t 'data' : '_fieldData',\n\t 'data_collection' : '_fieldData',\n\t 'integer' : '_fieldSlider',\n\t 'float' : '_fieldSlider',\n\t 'boolean' : '_fieldBoolean',\n\t 'drill_down' : '_fieldDrilldown',\n\t 'color' : '_fieldColor',\n\t 'hidden' : '_fieldHidden',\n\t 'hidden_data' : '_fieldHidden',\n\t 'baseurl' : '_fieldHidden',\n\t 'library_data' : '_fieldLibrary',\n\t 'ftpfile' : '_fieldFtp'\n\t },\n\t\n\t /** Returns an input field for a given field type */\n\t create: function( input_def ) {\n\t var fieldClass = this.types[ input_def.type ];\n\t var field = typeof( this[ fieldClass ] ) === 'function' ? this[ fieldClass ].call( this, input_def ) : null;\n\t if ( !field ) {\n\t field = input_def.options ? this._fieldSelect( input_def ) : this._fieldText( input_def );\n\t Galaxy.emit.debug('form-parameters::_addRow()', 'Auto matched field type (' + input_def.type + ').');\n\t }\n\t input_def.value === undefined && ( input_def.value = null );\n\t field.value( input_def.value );\n\t return field;\n\t },\n\t\n\t /** Data input field */\n\t _fieldData: function( input_def ) {\n\t return new SelectContent.View({\n\t id : 'field-' + input_def.id,\n\t extensions : input_def.extensions,\n\t optional : input_def.optional,\n\t multiple : input_def.multiple,\n\t type : input_def.type,\n\t flavor : input_def.flavor,\n\t data : input_def.options,\n\t onchange : input_def.onchange\n\t });\n\t },\n\t\n\t /** Select/Checkbox/Radio options field */\n\t _fieldSelect: function ( input_def ) {\n\t // show text field e.g. in workflow editor\n\t if( input_def.is_workflow ) {\n\t return this._fieldText( input_def );\n\t }\n\t\n\t // customize properties\n\t if ( input_def.type == 'data_column' ) {\n\t input_def.error_text = 'Missing columns in referenced dataset.'\n\t }\n\t\n\t // identify available options\n\t var data = input_def.data;\n\t if( !data ) {\n\t data = [];\n\t _.each( input_def.options, function( option ) {\n\t data.push( { label: option[ 0 ], value: option[ 1 ] } );\n\t });\n\t }\n\t\n\t // identify display type\n\t var SelectClass = Ui.Select;\n\t switch ( input_def.display ) {\n\t case 'checkboxes':\n\t SelectClass = Ui.Checkbox;\n\t break;\n\t case 'radio':\n\t SelectClass = Ui.Radio;\n\t break;\n\t case 'radiobutton':\n\t SelectClass = Ui.RadioButton;\n\t break;\n\t }\n\t\n\t // create select field\n\t return new SelectClass.View({\n\t id : 'field-' + input_def.id,\n\t data : data,\n\t error_text : input_def.error_text || 'No options available',\n\t multiple : input_def.multiple,\n\t optional : input_def.optional,\n\t onchange : input_def.onchange,\n\t searchable : input_def.flavor !== 'workflow'\n\t });\n\t },\n\t\n\t /** Drill down options field */\n\t _fieldDrilldown: function ( input_def ) {\n\t // show text field e.g. in workflow editor\n\t if( input_def.is_workflow ) {\n\t return this._fieldText( input_def );\n\t }\n\t\n\t // create drill down field\n\t return new Ui.Drilldown.View({\n\t id : 'field-' + input_def.id,\n\t data : input_def.options,\n\t display : input_def.display,\n\t optional : input_def.optional,\n\t onchange : input_def.onchange\n\t });\n\t },\n\t\n\t /** Text input field */\n\t _fieldText: function( input_def ) {\n\t // field replaces e.g. a select field\n\t if ( input_def.options && input_def.data ) {\n\t input_def.area = input_def.multiple;\n\t if ( Utils.isEmpty( input_def.value ) ) {\n\t input_def.value = null;\n\t } else {\n\t if ( $.isArray( input_def.value ) ) {\n\t var str_value = '';\n\t for ( var i in input_def.value ) {\n\t str_value += String( input_def.value[ i ] );\n\t if ( !input_def.multiple ) {\n\t break;\n\t }\n\t str_value += '\\n';\n\t }\n\t input_def.value = str_value;\n\t }\n\t }\n\t }\n\t // create input element\n\t return new Ui.Input({\n\t id : 'field-' + input_def.id,\n\t area : input_def.area,\n\t placeholder : input_def.placeholder,\n\t onchange : input_def.onchange\n\t });\n\t },\n\t\n\t /** Slider field */\n\t _fieldSlider: function( input_def ) {\n\t return new Ui.Slider.View({\n\t id : 'field-' + input_def.id,\n\t precise : input_def.type == 'float',\n\t is_workflow : input_def.is_workflow,\n\t min : input_def.min,\n\t max : input_def.max,\n\t onchange : input_def.onchange\n\t });\n\t },\n\t\n\t /** Hidden field */\n\t _fieldHidden: function( input_def ) {\n\t return new Ui.Hidden({\n\t id : 'field-' + input_def.id,\n\t info : input_def.info\n\t });\n\t },\n\t\n\t /** Boolean field */\n\t _fieldBoolean: function( input_def ) {\n\t return new Ui.RadioButton.View({\n\t id : 'field-' + input_def.id,\n\t data : [ { label : 'Yes', value : 'true' },\n\t { label : 'No', value : 'false' }],\n\t onchange : input_def.onchange\n\t });\n\t },\n\t\n\t /** Color picker field */\n\t _fieldColor: function( input_def ) {\n\t return new ColorPicker({\n\t id : 'field-' + input_def.id,\n\t onchange : input_def.onchange\n\t });\n\t },\n\t\n\t /** Library dataset field */\n\t _fieldLibrary: function( input_def ) {\n\t return new SelectLibrary.View({\n\t id : 'field-' + input_def.id,\n\t optional : input_def.optional,\n\t multiple : input_def.multiple,\n\t onchange : input_def.onchange\n\t });\n\t },\n\t\n\t /** FTP file field */\n\t _fieldFtp: function( input_def ) {\n\t return new SelectFtp.View({\n\t id : 'field-' + input_def.id,\n\t optional : input_def.optional,\n\t multiple : input_def.multiple,\n\t onchange : input_def.onchange\n\t });\n\t }\n\t });\n\t\n\t return {\n\t View: View\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(2), __webpack_require__(1)))\n\n/***/ },\n/* 36 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, $, _) {/** This class creates a ui component which enables the dynamic creation of portlets */\n\t!(__WEBPACK_AMD_DEFINE_ARRAY__ = [ __webpack_require__(4), __webpack_require__(9), __webpack_require__(7) ], __WEBPACK_AMD_DEFINE_RESULT__ = function( Utils, Portlet, Ui ) {\n\t var View = Backbone.View.extend({\n\t initialize: function( options ) {\n\t this.list = {};\n\t this.options = Utils.merge( options, {\n\t title : 'Repeat',\n\t empty_text : 'Not available.',\n\t max : null,\n\t min : null\n\t });\n\t this.button_new = new Ui.ButtonIcon({\n\t icon : 'fa-plus',\n\t title : 'Insert ' + this.options.title,\n\t tooltip : 'Add new ' + this.options.title + ' block',\n\t floating: 'clear',\n\t cls : 'ui-button-icon form-repeat-add',\n\t onclick : function() { options.onnew && options.onnew() }\n\t });\n\t this.setElement( $( '
    ' ).append( this.$list = $( '
    ' ) )\n\t .append( $( '
    ' ).append( this.button_new.$el ) ) );\n\t },\n\t\n\t /** Number of repeat blocks */\n\t size: function() {\n\t return _.size( this.list );\n\t },\n\t\n\t /** Add new repeat block */\n\t add: function( options ) {\n\t if ( !options.id || this.list[ options.id ] ) {\n\t Galaxy.emit.debug( 'form-repeat::add()', 'Duplicate or invalid repeat block id.' );\n\t return;\n\t }\n\t var button_delete = new Ui.ButtonIcon({\n\t icon : 'fa-trash-o',\n\t tooltip : 'Delete this repeat block',\n\t cls : 'ui-button-icon-plain form-repeat-delete',\n\t onclick : function() { options.ondel && options.ondel() }\n\t });\n\t var portlet = new Portlet.View({\n\t id : options.id,\n\t title : 'placeholder',\n\t cls : options.cls || 'ui-portlet-repeat',\n\t operations : { button_delete: button_delete }\n\t });\n\t portlet.append( options.$el );\n\t portlet.$el.addClass( 'section-row' ).hide();\n\t this.list[ options.id ] = portlet;\n\t this.$list.append( portlet.$el.fadeIn( 'fast' ) );\n\t this.options.max > 0 && this.size() >= this.options.max && this.button_new.disable();\n\t this._refresh();\n\t },\n\t\n\t /** Delete repeat block */\n\t del: function( id ) {\n\t if ( !this.list[ id ] ) {\n\t Galaxy.emit.debug( 'form-repeat::del()', 'Invalid repeat block id.' );\n\t return;\n\t }\n\t this.$list.find( '#' + id ).remove();\n\t delete this.list[ id ];\n\t this.button_new.enable();\n\t this._refresh();\n\t },\n\t\n\t /** Remove all */\n\t delAll: function() {\n\t for( var id in this.list ) {\n\t this.del( id );\n\t }\n\t },\n\t\n\t /** Hides add/del options */\n\t hideOptions: function() {\n\t this.button_new.$el.hide();\n\t _.each( this.list, function( portlet ) { portlet.hideOperation( 'button_delete' ) } );\n\t _.isEmpty( this.list ) && this.$el.append( $( '
    ' ).addClass( 'ui-form-info' ).html( this.options.empty_text ) );\n\t },\n\t\n\t /** Refresh view */\n\t _refresh: function() {\n\t var index = 0;\n\t for ( var id in this.list ) {\n\t var portlet = this.list[ id ];\n\t portlet.title( ++index + ': ' + this.options.title );\n\t portlet[ this.size() > this.options.min ? 'showOperation' : 'hideOperation' ]( 'button_delete' );\n\t }\n\t }\n\t });\n\t\n\t return {\n\t View : View\n\t }\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(1), __webpack_require__(2)))\n\n/***/ },\n/* 37 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, $, _, jQuery) {/**\n\t This class creates a form section and populates it with input elements. It also handles repeat blocks and conditionals by recursively creating new sub sections.\n\t*/\n\t!(__WEBPACK_AMD_DEFINE_ARRAY__ = [ __webpack_require__(4), __webpack_require__(7), __webpack_require__(9), __webpack_require__(36), __webpack_require__(34), __webpack_require__(35) ], __WEBPACK_AMD_DEFINE_RESULT__ = function( Utils, Ui, Portlet, Repeat, InputElement, Parameters ) {\n\t var View = Backbone.View.extend({\n\t initialize: function( app, options ) {\n\t this.app = app;\n\t this.inputs = options.inputs;\n\t this.parameters = new Parameters();\n\t this.setElement( $( '
    ' ) );\n\t this.render();\n\t },\n\t\n\t /** Render section view */\n\t render: function() {\n\t var self = this;\n\t this.$el.empty();\n\t _.each( this.inputs, function( input ) { self.add( input ) } );\n\t },\n\t\n\t /** Add a new input element */\n\t add: function( input ) {\n\t var input_def = jQuery.extend( true, {}, input );\n\t input_def.id = input.id = Utils.uid();\n\t this.app.input_list[ input_def.id ] = input_def;\n\t switch( input_def.type ) {\n\t case 'conditional':\n\t this._addConditional( input_def );\n\t break;\n\t case 'repeat':\n\t this._addRepeat( input_def );\n\t break;\n\t case 'section':\n\t this._addSection( input_def );\n\t break;\n\t default:\n\t this._addRow( input_def );\n\t }\n\t },\n\t\n\t /** Add a conditional block */\n\t _addConditional: function( input_def ) {\n\t var self = this;\n\t input_def.test_param.id = input_def.id;\n\t this.app.options.sustain_conditionals && ( input_def.test_param.disabled = true );\n\t var field = this._addRow( input_def.test_param );\n\t\n\t // set onchange event for test parameter\n\t field.model && field.model.set( 'onchange', function( value ) {\n\t var selectedCase = self.app.data.matchCase( input_def, value );\n\t for ( var i in input_def.cases ) {\n\t var case_def = input_def.cases[ i ];\n\t var section_row = self.$( '#' + input_def.id + '-section-' + i );\n\t var nonhidden = false;\n\t for ( var j in case_def.inputs ) {\n\t if ( !case_def.inputs[ j ].hidden ) {\n\t nonhidden = true;\n\t break;\n\t }\n\t }\n\t if ( i == selectedCase && nonhidden ) {\n\t section_row.fadeIn( 'fast' );\n\t } else {\n\t section_row.hide();\n\t }\n\t }\n\t self.app.trigger( 'change' );\n\t });\n\t\n\t // add conditional sub sections\n\t for ( var i in input_def.cases ) {\n\t var sub_section = new View( this.app, { inputs: input_def.cases[ i ].inputs } );\n\t this._append( sub_section.$el.addClass( 'ui-form-section' ), input_def.id + '-section-' + i );\n\t }\n\t\n\t // trigger refresh on conditional input field after all input elements have been created\n\t field.trigger( 'change' );\n\t },\n\t\n\t /** Add a repeat block */\n\t _addRepeat: function( input_def ) {\n\t var self = this;\n\t var block_index = 0;\n\t\n\t // create repeat block element\n\t var repeat = new Repeat.View({\n\t title : input_def.title || 'Repeat',\n\t min : input_def.min,\n\t max : input_def.max,\n\t onnew : function() { create( input_def.inputs ); self.app.trigger( 'change' ); }\n\t });\n\t\n\t // helper function to create new repeat blocks\n\t function create ( inputs ) {\n\t var sub_section_id = input_def.id + '-section-' + ( block_index++ );\n\t var sub_section = new View( self.app, { inputs: inputs } );\n\t repeat.add( { id : sub_section_id,\n\t $el : sub_section.$el,\n\t ondel : function() { repeat.del( sub_section_id ); self.app.trigger( 'change' ); } } );\n\t }\n\t\n\t //\n\t // add parsed/minimum number of repeat blocks\n\t //\n\t var n_cache = _.size( input_def.cache );\n\t for ( var i = 0; i < Math.max( Math.max( n_cache, input_def.min ), input_def.default || 0 ); i++ ) {\n\t create( i < n_cache ? input_def.cache[ i ] : input_def.inputs );\n\t }\n\t\n\t // hide options\n\t this.app.options.sustain_repeats && repeat.hideOptions();\n\t\n\t // create input field wrapper\n\t var input_element = new InputElement( this.app, {\n\t label : input_def.title || input_def.name,\n\t help : input_def.help,\n\t field : repeat\n\t });\n\t this._append( input_element.$el, input_def.id );\n\t },\n\t\n\t /** Add a customized section */\n\t _addSection: function( input_def ) {\n\t var portlet = new Portlet.View({\n\t title : input_def.title || input_def.name,\n\t cls : 'ui-portlet-section',\n\t collapsible : true,\n\t collapsible_button : true,\n\t collapsed : !input_def.expanded\n\t });\n\t portlet.append( new View( this.app, { inputs: input_def.inputs } ).$el );\n\t portlet.append( $( '
    ' ).addClass( 'ui-form-info' ).html( input_def.help ) );\n\t this.app.on( 'expand', function( input_id ) { ( portlet.$( '#' + input_id ).length > 0 ) && portlet.expand(); } );\n\t this._append( portlet.$el, input_def.id );\n\t },\n\t\n\t /** Add a single input field element */\n\t _addRow: function( input_def ) {\n\t var self = this;\n\t var id = input_def.id;\n\t input_def.onchange = function() { self.app.trigger( 'change', id ) };\n\t var field = this.parameters.create( input_def );\n\t this.app.field_list[ id ] = field;\n\t var input_element = new InputElement( this.app, {\n\t name : input_def.name,\n\t label : input_def.label || input_def.name,\n\t value : input_def.value,\n\t text_value : input_def.text_value,\n\t collapsible_value : input_def.collapsible_value,\n\t collapsible_preview : input_def.collapsible_preview,\n\t help : input_def.help,\n\t argument : input_def.argument,\n\t disabled : input_def.disabled,\n\t color : input_def.color,\n\t style : input_def.style,\n\t backdrop : input_def.backdrop,\n\t hidden : input_def.hidden,\n\t field : field\n\t });\n\t this.app.element_list[ id ] = input_element;\n\t this._append( input_element.$el, input_def.id );\n\t return field;\n\t },\n\t\n\t /** Append a new element to the form i.e. input element, repeat block, conditionals etc. */\n\t _append: function( $el, id ) {\n\t this.$el.append( $el.addClass( 'section-row' ).attr( 'id', id ) );\n\t }\n\t });\n\t\n\t return {\n\t View: View\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(1), __webpack_require__(2), __webpack_require__(1)))\n\n/***/ },\n/* 38 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, _, $) {/**\n\t This is the main class of the form plugin. It is referenced as 'app' in lower level modules.\n\t*/\n\t!(__WEBPACK_AMD_DEFINE_ARRAY__ = [ __webpack_require__(4), __webpack_require__(9), __webpack_require__(7), __webpack_require__(37), __webpack_require__(33) ], __WEBPACK_AMD_DEFINE_RESULT__ = function( Utils, Portlet, Ui, FormSection, FormData ) {\n\t return Backbone.View.extend({\n\t initialize: function( options ) {\n\t this.options = Utils.merge( options, {\n\t initial_errors : false,\n\t cls : 'ui-portlet-limited',\n\t icon : null,\n\t always_refresh : true\n\t });\n\t this.setElement( '
    ' );\n\t this.render();\n\t },\n\t\n\t /** Update available options */\n\t update: function( new_model ){\n\t var self = this;\n\t this.data.matchModel( new_model, function( node, input_id ) {\n\t var input = self.input_list[ input_id ];\n\t if ( input && input.options ) {\n\t if ( !_.isEqual( input.options, node.options ) ) {\n\t input.options = node.options;\n\t var field = self.field_list[ input_id ];\n\t if ( field.update ) {\n\t var new_options = [];\n\t if ( ( [ 'data', 'data_collection', 'drill_down' ] ).indexOf( input.type ) != -1 ) {\n\t new_options = input.options;\n\t } else {\n\t for ( var i in node.options ) {\n\t var opt = node.options[ i ];\n\t if ( opt.length > 2 ) {\n\t new_options.push( { label: opt[ 0 ], value: opt[ 1 ] } );\n\t }\n\t }\n\t }\n\t field.update( new_options );\n\t field.trigger( 'change' );\n\t Galaxy.emit.debug( 'form-view::update()', 'Updating options for ' + input_id );\n\t }\n\t }\n\t }\n\t });\n\t },\n\t\n\t /** Set form into wait mode */\n\t wait: function( active ) {\n\t for ( var i in this.input_list ) {\n\t var field = this.field_list[ i ];\n\t var input = this.input_list[ i ];\n\t if ( input.is_dynamic && field.wait && field.unwait ) {\n\t field[ active ? 'wait' : 'unwait' ]();\n\t }\n\t }\n\t },\n\t\n\t /** Highlight and scroll to input element (currently only used for error notifications) */\n\t highlight: function ( input_id, message, silent ) {\n\t var input_element = this.element_list[ input_id ];\n\t if ( input_element ) {\n\t input_element.error( message || 'Please verify this parameter.' );\n\t this.portlet.expand();\n\t this.trigger( 'expand', input_id );\n\t if ( !silent ) {\n\t var $panel = this.$el.parents().filter(function() {\n\t return [ 'auto', 'scroll' ].indexOf( $( this ).css( 'overflow' ) ) != -1;\n\t }).first();\n\t $panel.animate( { scrollTop : $panel.scrollTop() + input_element.$el.offset().top - 120 }, 500 );\n\t }\n\t }\n\t },\n\t\n\t /** Highlights errors */\n\t errors: function( options ) {\n\t this.trigger( 'reset' );\n\t if ( options && options.errors ) {\n\t var error_messages = this.data.matchResponse( options.errors );\n\t for ( var input_id in this.element_list ) {\n\t var input = this.element_list[ input_id ];\n\t if ( error_messages[ input_id ] ) {\n\t this.highlight( input_id, error_messages[ input_id ], true );\n\t }\n\t }\n\t }\n\t },\n\t\n\t /** Render tool form */\n\t render: function() {\n\t var self = this;\n\t this.off('change');\n\t this.off('reset');\n\t // contains the dom field elements as created by the parameter factory i.e. form-parameters\n\t this.field_list = {};\n\t // contains input definitions/dictionaries as provided by the parameters to_dict() function through the api\n\t this.input_list = {};\n\t // contains the dom elements of each input element i.e. form-input which wraps the actual input field\n\t this.element_list = {};\n\t // converts the form into a json data structure\n\t this.data = new FormData.Manager( this );\n\t this._renderForm();\n\t this.data.create();\n\t this.options.initial_errors && this.errors( this.options );\n\t // add listener which triggers on checksum change, and reset the form input wrappers\n\t var current_check = this.data.checksum();\n\t this.on('change', function( input_id ) {\n\t var input = self.input_list[ input_id ];\n\t if ( !input || input.refresh_on_change || self.options.always_refresh ) {\n\t var new_check = self.data.checksum();\n\t if ( new_check != current_check ) {\n\t current_check = new_check;\n\t self.options.onchange && self.options.onchange();\n\t }\n\t }\n\t });\n\t this.on('reset', function() {\n\t _.each( self.element_list, function( input_element ) { input_element.reset() } );\n\t });\n\t return this;\n\t },\n\t\n\t /** Renders/appends dom elements of the form */\n\t _renderForm: function() {\n\t $( '.tooltip' ).remove();\n\t this.message = new Ui.Message();\n\t this.section = new FormSection.View( this, { inputs: this.options.inputs } );\n\t this.portlet = new Portlet.View({\n\t icon : this.options.icon,\n\t title : this.options.title,\n\t cls : this.options.cls,\n\t operations : this.options.operations,\n\t buttons : this.options.buttons,\n\t collapsible : this.options.collapsible,\n\t collapsed : this.options.collapsed\n\t });\n\t this.portlet.append( this.message.$el );\n\t this.portlet.append( this.section.$el );\n\t this.$el.empty();\n\t this.options.inputs && this.$el.append( this.portlet.$el );\n\t this.options.message && this.message.update( { persistent: true, status: 'warning', message: this.options.message } );\n\t Galaxy.emit.debug( 'form-view::initialize()', 'Completed' );\n\t }\n\t });\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(2), __webpack_require__(1)))\n\n/***/ },\n/* 39 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(_) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(30),\n\t __webpack_require__(74),\n\t __webpack_require__(5)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( DC_MODEL, HISTORY_CONTENT, _l ){\n\t\n\t'use strict';\n\t\n\t/*==============================================================================\n\t\n\tModels for DatasetCollections contained within a history.\n\t\n\tTODO:\n\t these might be compactable to one class if some duplication with\n\t collection-model is used.\n\t\n\t==============================================================================*/\n\tvar hcontentMixin = HISTORY_CONTENT.HistoryContentMixin,\n\t ListDC = DC_MODEL.ListDatasetCollection,\n\t PairDC = DC_MODEL.PairDatasetCollection,\n\t ListPairedDC = DC_MODEL.ListPairedDatasetCollection,\n\t ListOfListsDC = DC_MODEL.ListOfListsDatasetCollection;\n\t\n\t//==============================================================================\n\t/** Override to post to contents route w/o id. */\n\tfunction buildHDCASave( _super ){\n\t return function _save( attributes, options ){\n\t if( this.isNew() ){\n\t options = options || {};\n\t options.url = this.urlRoot + this.get( 'history_id' ) + '/contents';\n\t attributes = attributes || {};\n\t attributes.type = 'dataset_collection';\n\t }\n\t return _super.call( this, attributes, options );\n\t };\n\t}\n\t\n\t\n\t//==============================================================================\n\t/** @class Backbone model for List Dataset Collection within a History.\n\t */\n\tvar HistoryListDatasetCollection = ListDC.extend( hcontentMixin ).extend(\n\t/** @lends HistoryListDatasetCollection.prototype */{\n\t\n\t defaults : _.extend( _.clone( ListDC.prototype.defaults ), {\n\t history_content_type: 'dataset_collection',\n\t collection_type : 'list',\n\t model_class : 'HistoryDatasetCollectionAssociation'\n\t }),\n\t\n\t /** Override to post to contents route w/o id. */\n\t save : buildHDCASave( ListDC.prototype.save ),\n\t\n\t /** String representation. */\n\t toString : function(){\n\t return 'History' + ListDC.prototype.toString.call( this );\n\t }\n\t});\n\t\n\t\n\t//==============================================================================\n\t/** @class Backbone model for Pair Dataset Collection within a History.\n\t * @constructs\n\t */\n\tvar HistoryPairDatasetCollection = PairDC.extend( hcontentMixin ).extend(\n\t/** @lends HistoryPairDatasetCollection.prototype */{\n\t\n\t defaults : _.extend( _.clone( PairDC.prototype.defaults ), {\n\t history_content_type: 'dataset_collection',\n\t collection_type : 'paired',\n\t model_class : 'HistoryDatasetCollectionAssociation'\n\t }),\n\t\n\t /** Override to post to contents route w/o id. */\n\t save : buildHDCASave( PairDC.prototype.save ),\n\t\n\t /** String representation. */\n\t toString : function(){\n\t return 'History' + PairDC.prototype.toString.call( this );\n\t }\n\t});\n\t\n\t\n\t//==============================================================================\n\t/** @class Backbone model for List of Pairs Dataset Collection within a History. */\n\tvar HistoryListPairedDatasetCollection = ListPairedDC.extend( hcontentMixin ).extend({\n\t\n\t defaults : _.extend( _.clone( ListPairedDC.prototype.defaults ), {\n\t history_content_type: 'dataset_collection',\n\t collection_type : 'list:paired',\n\t model_class : 'HistoryDatasetCollectionAssociation'\n\t }),\n\t\n\t /** Override to post to contents route w/o id. */\n\t save : buildHDCASave( ListPairedDC.prototype.save ),\n\t\n\t /** String representation. */\n\t toString : function(){\n\t return 'History' + ListPairedDC.prototype.toString.call( this );\n\t }\n\t});\n\t\n\t\n\t//==============================================================================\n\t/** @class Backbone model for List of Lists Dataset Collection within a History. */\n\tvar HistoryListOfListsDatasetCollection = ListOfListsDC.extend( hcontentMixin ).extend({\n\t\n\t defaults : _.extend( _.clone( ListOfListsDC.prototype.defaults ), {\n\t history_content_type: 'dataset_collection',\n\t collection_type : 'list:list',\n\t model_class : 'HistoryDatasetCollectionAssociation'\n\t }),\n\t\n\t /** Override to post to contents route w/o id. */\n\t save : buildHDCASave( ListOfListsDC.prototype.save ),\n\t\n\t /** String representation. */\n\t toString : function(){\n\t return ([ 'HistoryListOfListsDatasetCollection(', this.get( 'name' ), ')' ].join( '' ));\n\t }\n\t});\n\t\n\t\n\t//==============================================================================\n\t return {\n\t HistoryListDatasetCollection : HistoryListDatasetCollection,\n\t HistoryPairDatasetCollection : HistoryPairDatasetCollection,\n\t HistoryListPairedDatasetCollection : HistoryListPairedDatasetCollection,\n\t HistoryListOfListsDatasetCollection : HistoryListOfListsDatasetCollection\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(2)))\n\n/***/ },\n/* 40 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(_, jQuery, Backbone) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(67),\n\t __webpack_require__(72),\n\t __webpack_require__(39),\n\t __webpack_require__(41),\n\t __webpack_require__(6),\n\t __webpack_require__(129)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( CONTROLLED_FETCH_COLLECTION, HDA_MODEL, HDCA_MODEL, HISTORY_PREFS, BASE_MVC, AJAX_QUEUE ){\n\t'use strict';\n\t\n\t//==============================================================================\n\tvar _super = CONTROLLED_FETCH_COLLECTION.PaginatedCollection;\n\t/** @class Backbone collection for history content.\n\t * NOTE: history content seems like a dataset collection, but differs in that it is mixed:\n\t * each element can be either an HDA (dataset) or a DatasetCollection and co-exist on\n\t * the same level.\n\t * Dataset collections on the other hand are not mixed and (so far) can only contain either\n\t * HDAs or child dataset collections on one level.\n\t * This is why this does not inherit from any of the DatasetCollections (currently).\n\t */\n\tvar HistoryContents = _super.extend( BASE_MVC.LoggableMixin ).extend({\n\t _logNamespace : 'history',\n\t\n\t // ........................................................................ composite collection\n\t /** since history content is a mix, override model fn into a factory, creating based on history_content_type */\n\t model : function( attrs, options ) {\n\t if( attrs.history_content_type === \"dataset\" ) {\n\t return new HDA_MODEL.HistoryDatasetAssociation( attrs, options );\n\t\n\t } else if( attrs.history_content_type === \"dataset_collection\" ) {\n\t switch( attrs.collection_type ){\n\t case 'list':\n\t return new HDCA_MODEL.HistoryListDatasetCollection( attrs, options );\n\t case 'paired':\n\t return new HDCA_MODEL.HistoryPairDatasetCollection( attrs, options );\n\t case 'list:paired':\n\t return new HDCA_MODEL.HistoryListPairedDatasetCollection( attrs, options );\n\t case 'list:list':\n\t return new HDCA_MODEL.HistoryListOfListsDatasetCollection( attrs, options );\n\t }\n\t // This is a hack inside a hack:\n\t // Raise a plain object with validationError to fake a model.validationError\n\t // (since we don't have a model to use validate with)\n\t // (the outer hack being the mixed content/model function in this collection)\n\t var msg = 'Unknown collection_type: ' + attrs.collection_type;\n\t console.warn( msg, attrs );\n\t return { validationError : msg };\n\t }\n\t return { validationError : 'Unknown history_content_type: ' + attrs.history_content_type };\n\t },\n\t\n\t // ........................................................................ set up\n\t limitPerPage : 500,\n\t\n\t /** @type {Integer} how many contents per call to fetch when using progressivelyFetchDetails */\n\t limitPerProgressiveFetch : 500,\n\t\n\t /** @type {String} order used here and when fetching from server */\n\t order : 'hid',\n\t\n\t /** root api url */\n\t urlRoot : Galaxy.root + 'api/histories',\n\t\n\t /** complete api url */\n\t url : function(){\n\t return this.urlRoot + '/' + this.historyId + '/contents';\n\t },\n\t\n\t /** Set up */\n\t initialize : function( models, options ){\n\t options = options || {};\n\t _super.prototype.initialize.call( this, models, options );\n\t\n\t this.history = options.history || null;\n\t this.setHistoryId( options.historyId || null );\n\t /** @type {Boolean} does this collection contain and fetch deleted elements */\n\t this.includeDeleted = options.includeDeleted || this.includeDeleted;\n\t /** @type {Boolean} does this collection contain and fetch non-visible elements */\n\t this.includeHidden = options.includeHidden || this.includeHidden;\n\t\n\t // backbonejs uses collection.model.prototype.idAttribute to determine if a model is *already* in a collection\n\t // and either merged or replaced. In this case, our 'model' is a function so we need to add idAttribute\n\t // manually here - if we don't, contents will not merge but be replaced/swapped.\n\t this.model.prototype.idAttribute = 'type_id';\n\t },\n\t\n\t setHistoryId : function( newId ){\n\t this.historyId = newId;\n\t this._setUpWebStorage();\n\t },\n\t\n\t /** Set up client side storage. Currently PersistanStorage keyed under 'history:' */\n\t _setUpWebStorage : function( initialSettings ){\n\t // TODO: use initialSettings\n\t if( !this.historyId ){ return; }\n\t this.storage = new HISTORY_PREFS.HistoryPrefs({\n\t id: HISTORY_PREFS.HistoryPrefs.historyStorageKey( this.historyId )\n\t });\n\t this.trigger( 'new-storage', this.storage, this );\n\t\n\t this.on({\n\t 'include-deleted' : function( newVal ){\n\t this.storage.includeDeleted( newVal );\n\t },\n\t 'include-hidden' : function( newVal ){\n\t this.storage.includeHidden( newVal );\n\t }\n\t });\n\t\n\t this.includeDeleted = this.storage.includeDeleted() || false;\n\t this.includeHidden = this.storage.includeHidden() || false;\n\t return this;\n\t },\n\t\n\t // ........................................................................ common queries\n\t /** @type {Object} map of collection available sorting orders containing comparator fns */\n\t comparators : _.extend( _.clone( _super.prototype.comparators ), {\n\t 'name' : BASE_MVC.buildComparator( 'name', { ascending: true }),\n\t 'name-dsc' : BASE_MVC.buildComparator( 'name', { ascending: false }),\n\t 'hid' : BASE_MVC.buildComparator( 'hid', { ascending: false }),\n\t 'hid-asc' : BASE_MVC.buildComparator( 'hid', { ascending: true }),\n\t }),\n\t\n\t /** Get every model in this collection not in a 'ready' state (running). */\n\t running : function(){\n\t return this.filter( function( c ){ return !c.inReadyState(); });\n\t },\n\t\n\t /** return contents that are not ready and not deleted/hidden */\n\t runningAndActive : function(){\n\t return this.filter( function( c ){\n\t return ( !c.inReadyState() )\n\t && ( c.get( 'visible' ) )\n\t // TODO: deletedOrPurged?\n\t && ( !c.get( 'deleted' ) );\n\t });\n\t },\n\t\n\t /** Get the model with the given hid\n\t * @param {Int} hid the hid to search for\n\t * @returns {HistoryDatasetAssociation} the model with the given hid or undefined if not found\n\t */\n\t getByHid : function( hid ){\n\t // note: there *can* be more than one content with a given hid, this finds the first based on order\n\t return this.findWhere({ hid: hid });\n\t },\n\t\n\t /** return true if all contents have details */\n\t haveDetails : function(){\n\t return this.all( function( c ){ return c.hasDetails(); });\n\t },\n\t\n\t // ........................................................................ hidden / deleted\n\t /** return a new contents collection of only hidden items */\n\t hidden : function(){\n\t return this.filter( function( c ){ return c.hidden(); });\n\t },\n\t\n\t /** return a new contents collection of only hidden items */\n\t deleted : function(){\n\t return this.filter( function( c ){ return c.get( 'deleted' ); });\n\t },\n\t\n\t /** return a new contents collection of only hidden items */\n\t visibleAndUndeleted : function(){\n\t return this.filter( function( c ){\n\t return ( c.get( 'visible' ) )\n\t // TODO: deletedOrPurged?\n\t && ( !c.get( 'deleted' ) );\n\t });\n\t },\n\t\n\t /** create a setter in order to publish the change */\n\t setIncludeDeleted : function( setting, options ){\n\t if( _.isBoolean( setting ) && setting !== this.includeDeleted ){\n\t this.includeDeleted = setting;\n\t if( _.result( options, 'silent' ) ){ return; }\n\t this.trigger( 'include-deleted', setting, this );\n\t }\n\t },\n\t\n\t /** create a setter in order to publish the change */\n\t setIncludeHidden : function( setting, options ){\n\t if( _.isBoolean( setting ) && setting !== this.includeHidden ){\n\t this.includeHidden = setting;\n\t options = options || {};\n\t if( _.result( options, 'silent' ) ){ return; }\n\t this.trigger( 'include-hidden', setting, this );\n\t }\n\t },\n\t\n\t // ........................................................................ ajax\n\t // ............ controlled fetch collection\n\t /** override to get expanded ids from sessionStorage and pass to API as details */\n\t fetch : function( options ){\n\t options = options || {};\n\t if( this.historyId && !options.details ){\n\t var prefs = HISTORY_PREFS.HistoryPrefs.get( this.historyId ).toJSON();\n\t if( !_.isEmpty( prefs.expandedIds ) ){\n\t options.details = _.values( prefs.expandedIds ).join( ',' );\n\t }\n\t }\n\t return _super.prototype.fetch.call( this, options );\n\t },\n\t\n\t // ............. ControlledFetch stuff\n\t /** override to include the API versioning flag */\n\t _buildFetchData : function( options ){\n\t return _.extend( _super.prototype._buildFetchData.call( this, options ), {\n\t v : 'dev'\n\t });\n\t },\n\t\n\t /** Extend to include details and version */\n\t _fetchParams : _super.prototype._fetchParams.concat([\n\t // TODO: remove (the need for) both\n\t /** version */\n\t 'v',\n\t /** dataset ids to get full details of */\n\t 'details',\n\t ]),\n\t\n\t /** override to add deleted/hidden filters */\n\t _buildFetchFilters : function( options ){\n\t var superFilters = _super.prototype._buildFetchFilters.call( this, options ) || {};\n\t var filters = {};\n\t if( !this.includeDeleted ){\n\t filters.deleted = false;\n\t filters.purged = false;\n\t }\n\t if( !this.includeHidden ){\n\t filters.visible = true;\n\t }\n\t return _.defaults( superFilters, filters );\n\t },\n\t\n\t // ............ paginated collection\n\t getTotalItemCount : function(){\n\t return this.history.contentsShown();\n\t },\n\t\n\t // ............ history contents specific ajax\n\t /** override to filter requested contents to those updated after the Date 'since' */\n\t fetchUpdated : function( since, options ){\n\t if( since ){\n\t options = options || { filters: {} };\n\t options.remove = false;\n\t options.filters = {\n\t 'update_time-ge' : since.toISOString(),\n\t // workflows will produce hidden datasets (non-output datasets) that still\n\t // need to be updated in the collection or they'll update forever\n\t // we can remove the default visible filter by using an 'empty' value\n\t visible : ''\n\t };\n\t }\n\t return this.fetch( options );\n\t },\n\t\n\t /** fetch all the deleted==true contents of this collection */\n\t fetchDeleted : function( options ){\n\t options = options || {};\n\t var self = this;\n\t options.filters = _.extend( options.filters, {\n\t // all deleted, purged or not\n\t deleted : true,\n\t purged : undefined\n\t });\n\t options.remove = false;\n\t\n\t self.trigger( 'fetching-deleted', self );\n\t return self.fetch( options )\n\t .always( function(){ self.trigger( 'fetching-deleted-done', self ); });\n\t },\n\t\n\t /** fetch all the visible==false contents of this collection */\n\t fetchHidden : function( options ){\n\t options = options || {};\n\t var self = this;\n\t options.filters = _.extend( options.filters, {\n\t visible : false\n\t });\n\t options.remove = false;\n\t\n\t self.trigger( 'fetching-hidden', self );\n\t return self.fetch( options )\n\t .always( function(){ self.trigger( 'fetching-hidden-done', self ); });\n\t },\n\t\n\t /** fetch detailed model data for all contents in this collection */\n\t fetchAllDetails : function( options ){\n\t options = options || {};\n\t var detailsFlag = { details: 'all' };\n\t options.data = _.extend( options.data || {}, detailsFlag );\n\t return this.fetch( options );\n\t },\n\t\n\t /** specialty fetch method for retrieving the element_counts of all hdcas in the history */\n\t fetchCollectionCounts : function( options ){\n\t options = options || {};\n\t options.keys = [ 'type_id', 'element_count' ].join( ',' );\n\t options.filters = _.extend( options.filters || {}, {\n\t history_content_type: 'dataset_collection',\n\t });\n\t options.remove = false;\n\t return this.fetch( options );\n\t },\n\t\n\t // ............. quasi-batch ops\n\t // TODO: to batch\n\t /** helper that fetches using filterParams then calls save on each fetched using updateWhat as the save params */\n\t _filterAndUpdate : function( filterParams, updateWhat ){\n\t var self = this;\n\t var idAttribute = self.model.prototype.idAttribute;\n\t var updateArgs = [ updateWhat ];\n\t\n\t return self.fetch({ filters: filterParams, remove: false })\n\t .then( function( fetched ){\n\t // convert filtered json array to model array\n\t fetched = fetched.reduce( function( modelArray, currJson, i ){\n\t var model = self.get( currJson[ idAttribute ] );\n\t return model? modelArray.concat( model ) : modelArray;\n\t }, []);\n\t return self.ajaxQueue( 'save', updateArgs, fetched );\n\t });\n\t },\n\t\n\t /** using a queue, perform ajaxFn on each of the models in this collection */\n\t ajaxQueue : function( ajaxFn, args, collection ){\n\t collection = collection || this.models;\n\t return new AJAX_QUEUE.AjaxQueue( collection.slice().reverse().map( function( content, i ){\n\t var fn = _.isString( ajaxFn )? content[ ajaxFn ] : ajaxFn;\n\t return function(){ return fn.apply( content, args ); };\n\t })).deferred;\n\t },\n\t\n\t /** fetch contents' details in batches of limitPerCall - note: only get searchable details here */\n\t progressivelyFetchDetails : function( options ){\n\t options = options || {};\n\t var deferred = jQuery.Deferred();\n\t var self = this;\n\t var limit = options.limitPerCall || self.limitPerProgressiveFetch;\n\t // TODO: only fetch tags and annotations if specifically requested\n\t var searchAttributes = HDA_MODEL.HistoryDatasetAssociation.prototype.searchAttributes;\n\t var detailKeys = searchAttributes.join( ',' );\n\t\n\t function _recursivelyFetch( offset ){\n\t offset = offset || 0;\n\t var _options = _.extend( _.clone( options ), {\n\t view : 'summary',\n\t keys : detailKeys,\n\t limit : limit,\n\t offset : offset,\n\t reset : offset === 0,\n\t remove : false\n\t });\n\t\n\t _.defer( function(){\n\t self.fetch.call( self, _options )\n\t .fail( deferred.reject )\n\t .done( function( response ){\n\t deferred.notify( response, limit, offset );\n\t if( response.length !== limit ){\n\t self.allFetched = true;\n\t deferred.resolve( response, limit, offset );\n\t\n\t } else {\n\t _recursivelyFetch( offset + limit );\n\t }\n\t });\n\t });\n\t }\n\t _recursivelyFetch();\n\t return deferred;\n\t },\n\t\n\t /** does some bit of JSON represent something that can be copied into this contents collection */\n\t isCopyable : function( contentsJSON ){\n\t var copyableModelClasses = [\n\t 'HistoryDatasetAssociation',\n\t 'HistoryDatasetCollectionAssociation'\n\t ];\n\t return ( ( _.isObject( contentsJSON ) && contentsJSON.id )\n\t && ( _.contains( copyableModelClasses, contentsJSON.model_class ) ) );\n\t },\n\t\n\t /** copy an existing, accessible hda into this collection */\n\t copy : function( json ){\n\t // TODO: somehow showhorn all this into 'save'\n\t var id, type, contentType;\n\t if( _.isString( json ) ){\n\t id = json;\n\t contentType = 'hda';\n\t type = 'dataset';\n\t } else {\n\t id = json.id;\n\t contentType = ({\n\t 'HistoryDatasetAssociation' : 'hda',\n\t 'LibraryDatasetDatasetAssociation' : 'ldda',\n\t 'HistoryDatasetCollectionAssociation' : 'hdca'\n\t })[ json.model_class ] || 'hda';\n\t type = ( contentType === 'hdca'? 'dataset_collection' : 'dataset' );\n\t }\n\t var collection = this,\n\t xhr = jQuery.ajax( this.url(), {\n\t method: 'POST',\n\t contentType: 'application/json',\n\t data: JSON.stringify({\n\t content : id,\n\t source : contentType,\n\t type : type\n\t })\n\t })\n\t .done( function( response ){\n\t collection.add([ response ], { parse: true });\n\t })\n\t .fail( function( error, status, message ){\n\t collection.trigger( 'error', collection, xhr, {},\n\t 'Error copying contents', { type: type, id: id, source: contentType });\n\t });\n\t return xhr;\n\t },\n\t\n\t /** create a new HDCA in this collection */\n\t createHDCA : function( elementIdentifiers, collectionType, name, options ){\n\t // normally collection.create returns the new model, but we need the promise from the ajax, so we fake create\n\t //precondition: elementIdentifiers is an array of plain js objects\n\t // in the proper form to create the collectionType\n\t var hdca = this.model({\n\t history_content_type: 'dataset_collection',\n\t collection_type : collectionType,\n\t history_id : this.historyId,\n\t name : name,\n\t // should probably be able to just send in a bunch of json here and restruct per class\n\t // note: element_identifiers is now (incorrectly) an attribute\n\t element_identifiers : elementIdentifiers\n\t // do not create the model on the client until the ajax returns\n\t });\n\t return hdca.save( options );\n\t },\n\t\n\t // ........................................................................ searching\n\t /** return true if all contents have the searchable attributes */\n\t haveSearchDetails : function(){\n\t return this.allFetched && this.all( function( content ){\n\t // null (which is a valid returned annotation value)\n\t // will return false when using content.has( 'annotation' )\n\t //TODO: a bit hacky - formalize\n\t return _.has( content.attributes, 'annotation' );\n\t });\n\t },\n\t\n\t /** return a new collection of contents whose attributes contain the substring matchesWhat */\n\t matches : function( matchesWhat ){\n\t return this.filter( function( content ){\n\t return content.matches( matchesWhat );\n\t });\n\t },\n\t\n\t // ........................................................................ misc\n\t /** In this override, copy the historyId to the clone */\n\t clone : function(){\n\t var clone = Backbone.Collection.prototype.clone.call( this );\n\t clone.historyId = this.historyId;\n\t return clone;\n\t },\n\t\n\t /** String representation. */\n\t toString : function(){\n\t return ([ 'HistoryContents(', [ this.historyId, this.length ].join(), ')' ].join( '' ));\n\t }\n\t});\n\t\n\t\n\t//==============================================================================\n\t return {\n\t HistoryContents : HistoryContents\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(2), __webpack_require__(1), __webpack_require__(3)))\n\n/***/ },\n/* 41 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(_) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(6)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( BASE_MVC ){\n\t\n\t'use strict';\n\t\n\tvar logNamespace = 'history';\n\t\n\t// ============================================================================\n\t/** session storage for individual history preferences */\n\tvar HistoryPrefs = BASE_MVC.SessionStorageModel.extend(\n\t/** @lends HistoryPrefs.prototype */{\n\t //TODO:?? move to user prefs?\n\t defaults : {\n\t //TODO:?? expandedIds to array?\n\t expandedIds : {},\n\t show_deleted : false,\n\t show_hidden : false\n\t },\n\t\n\t /** add an hda id to the hash of expanded hdas */\n\t addExpanded : function( model ){\n\t//TODO: use type_id and not model\n\t var current = this.get( 'expandedIds' );\n\t current[ model.id ] = model.get( 'id' );\n\t this.save( 'expandedIds', current );\n\t },\n\t\n\t /** remove an hda id from the hash of expanded hdas */\n\t removeExpanded : function( model ){\n\t var current = this.get( 'expandedIds' );\n\t delete current[ model.id ];\n\t this.save( 'expandedIds', current );\n\t },\n\t\n\t isExpanded : function( contentId ){\n\t return _.result( this.get( 'expandedIds' ), contentId, false );\n\t },\n\t\n\t allExpanded : function(){\n\t return _.values( this.get( 'expandedIds' ) );\n\t },\n\t\n\t clearExpanded : function(){\n\t this.set( 'expandedIds', {} );\n\t },\n\t\n\t includeDeleted : function( val ){\n\t // moving the invocation here so other components don't need to know the key\n\t // TODO: change this key later\n\t if( !_.isUndefined( val ) ){ this.set( 'show_deleted', val ); }\n\t return this.get( 'show_deleted' );\n\t },\n\t\n\t includeHidden : function( val ){\n\t // TODO: change this key later\n\t if( !_.isUndefined( val ) ){ this.set( 'show_hidden', val ); }\n\t return this.get( 'show_hidden' );\n\t },\n\t\n\t toString : function(){\n\t return 'HistoryPrefs(' + this.id + ')';\n\t }\n\t\n\t}, {\n\t // ........................................................................ class vars\n\t // class lvl for access w/o instantiation\n\t storageKeyPrefix : 'history:',\n\t\n\t /** key string to store each histories settings under */\n\t historyStorageKey : function historyStorageKey( historyId ){\n\t if( !historyId ){\n\t throw new Error( 'HistoryPrefs.historyStorageKey needs valid id: ' + historyId );\n\t }\n\t // single point of change\n\t return ( HistoryPrefs.storageKeyPrefix + historyId );\n\t },\n\t\n\t /** return the existing storage for the history with the given id (or create one if it doesn't exist) */\n\t get : function get( historyId ){\n\t return new HistoryPrefs({ id: HistoryPrefs.historyStorageKey( historyId ) });\n\t },\n\t\n\t /** clear all history related items in sessionStorage */\n\t clearAll : function clearAll( historyId ){\n\t for( var key in sessionStorage ){\n\t if( key.indexOf( HistoryPrefs.storageKeyPrefix ) === 0 ){\n\t sessionStorage.removeItem( key );\n\t }\n\t }\n\t }\n\t});\n\t\n\t//==============================================================================\n\t return {\n\t HistoryPrefs: HistoryPrefs\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(2)))\n\n/***/ },\n/* 42 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, $, _, jQuery) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(6),\n\t __webpack_require__(5)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( BASE_MVC, _l ){\n\t\n\t'use strict';\n\t\n\tvar logNamespace = 'list';\n\t//==============================================================================\n\t/** A view which, when first rendered, shows only summary data/attributes, but\n\t * can be expanded to show further details (and optionally fetch those\n\t * details from the server).\n\t */\n\tvar ExpandableView = Backbone.View.extend( BASE_MVC.LoggableMixin ).extend({\n\t _logNamespace : logNamespace,\n\t\n\t //TODO: Although the reasoning behind them is different, this shares a lot with HiddenUntilActivated above: combine them\n\t //PRECONDITION: model must have method hasDetails\n\t //PRECONDITION: subclasses must have templates.el and templates.details\n\t\n\t initialize : function( attributes ){\n\t /** are the details of this view expanded/shown or not? */\n\t this.expanded = attributes.expanded || false;\n\t this.log( '\\t expanded:', this.expanded );\n\t this.fxSpeed = attributes.fxSpeed !== undefined? attributes.fxSpeed : this.fxSpeed;\n\t },\n\t\n\t // ........................................................................ render main\n\t /** jq fx speed */\n\t fxSpeed : 'fast',\n\t\n\t /** Render this content, set up ui.\n\t * @param {Number or String} speed the speed of the render\n\t */\n\t render : function( speed ){\n\t var $newRender = this._buildNewRender();\n\t this._setUpBehaviors( $newRender );\n\t this._queueNewRender( $newRender, speed );\n\t return this;\n\t },\n\t\n\t /** Build a temp div containing the new children for the view's $el.\n\t * If the view is already expanded, build the details as well.\n\t */\n\t _buildNewRender : function(){\n\t // create a new render using a skeleton template, render title buttons, render body, and set up events, etc.\n\t var $newRender = $( this.templates.el( this.model.toJSON(), this ) );\n\t if( this.expanded ){\n\t this.$details( $newRender ).replaceWith( this._renderDetails().show() );\n\t }\n\t return $newRender;\n\t },\n\t\n\t /** Fade out the old el, swap in the new contents, then fade in.\n\t * @param {Number or String} speed jq speed to use for rendering effects\n\t * @fires rendered when rendered\n\t */\n\t _queueNewRender : function( $newRender, speed ) {\n\t speed = ( speed === undefined )?( this.fxSpeed ):( speed );\n\t var view = this;\n\t\n\t if( speed === 0 ){\n\t view._swapNewRender( $newRender );\n\t view.trigger( 'rendered', view );\n\t\n\t } else {\n\t $( view ).queue( 'fx', [\n\t function( next ){\n\t view.$el.fadeOut( speed, next );\n\t },\n\t function( next ){\n\t view._swapNewRender( $newRender );\n\t next();\n\t },\n\t function( next ){\n\t view.$el.fadeIn( speed, next );\n\t },\n\t function( next ){\n\t view.trigger( 'rendered', view );\n\t next();\n\t }\n\t ]);\n\t }\n\t },\n\t\n\t /** empty out the current el, move the $newRender's children in */\n\t _swapNewRender : function( $newRender ){\n\t return this.$el.empty()\n\t .attr( 'class', _.isFunction( this.className )? this.className(): this.className )\n\t .append( $newRender.children() );\n\t },\n\t\n\t /** set up js behaviors, event handlers for elements within the given container\n\t * @param {jQuery} $container jq object that contains the elements to process (defaults to this.$el)\n\t */\n\t _setUpBehaviors : function( $where ){\n\t $where = $where || this.$el;\n\t // set up canned behavior on children (bootstrap, popupmenus, editable_text, etc.)\n\t //make_popup_menus( $where );\n\t $where.find( '[title]' ).tooltip({ placement : 'bottom' });\n\t },\n\t\n\t // ......................................................................... details\n\t /** shortcut to details DOM (as jQ) */\n\t $details : function( $where ){\n\t $where = $where || this.$el;\n\t return $where.find( '> .details' );\n\t },\n\t\n\t /** build the DOM for the details and set up behaviors on it */\n\t _renderDetails : function(){\n\t var $newDetails = $( this.templates.details( this.model.toJSON(), this ) );\n\t this._setUpBehaviors( $newDetails );\n\t return $newDetails;\n\t },\n\t\n\t // ......................................................................... expansion/details\n\t /** Show or hide the details\n\t * @param {Boolean} expand if true, expand; if false, collapse\n\t */\n\t toggleExpanded : function( expand ){\n\t expand = ( expand === undefined )?( !this.expanded ):( expand );\n\t if( expand ){\n\t this.expand();\n\t } else {\n\t this.collapse();\n\t }\n\t return this;\n\t },\n\t\n\t /** Render and show the full, detailed body of this view including extra data and controls.\n\t * note: if the model does not have detailed data, fetch that data before showing the body\n\t * @fires expanded when a body has been expanded\n\t */\n\t expand : function(){\n\t var view = this;\n\t return view._fetchModelDetails().always( function(){\n\t view._expand();\n\t });\n\t },\n\t\n\t /** Check for model details and, if none, fetch them.\n\t * @returns {jQuery.promise} the model.fetch.xhr if details are being fetched, an empty promise if not\n\t */\n\t _fetchModelDetails : function(){\n\t if( !this.model.hasDetails() ){\n\t return this.model.fetch();\n\t }\n\t return jQuery.when();\n\t },\n\t\n\t /** Inner fn called when expand (public) has fetched the details */\n\t _expand : function(){\n\t var view = this,\n\t $newDetails = view._renderDetails();\n\t view.$details().replaceWith( $newDetails );\n\t // needs to be set after the above or the slide will not show\n\t view.expanded = true;\n\t view.$details().slideDown( view.fxSpeed, function(){\n\t view.trigger( 'expanded', view );\n\t });\n\t },\n\t\n\t /** Hide the body/details of an HDA.\n\t * @fires collapsed when a body has been collapsed\n\t */\n\t collapse : function(){\n\t this.debug( this + '(ExpandableView).collapse' );\n\t var view = this;\n\t view.expanded = false;\n\t this.$details().slideUp( view.fxSpeed, function(){\n\t view.trigger( 'collapsed', view );\n\t });\n\t }\n\t\n\t});\n\t\n\t\n\t//==============================================================================\n\t/** A view that is displayed in some larger list/grid/collection.\n\t * Inherits from Expandable, Selectable, Draggable.\n\t * The DOM contains warnings, a title bar, and a series of primary action controls.\n\t * Primary actions are meant to be easily accessible item functions (such as delete)\n\t * that are rendered in the title bar.\n\t *\n\t * Details are rendered when the user clicks the title bar or presses enter/space when\n\t * the title bar is in focus.\n\t *\n\t * Designed as a base class for history panel contents - but usable elsewhere (I hope).\n\t */\n\tvar ListItemView = ExpandableView.extend(\n\t BASE_MVC.mixin( BASE_MVC.SelectableViewMixin, BASE_MVC.DraggableViewMixin, {\n\t\n\t tagName : 'div',\n\t className : 'list-item',\n\t\n\t /** Set up the base class and all mixins */\n\t initialize : function( attributes ){\n\t ExpandableView.prototype.initialize.call( this, attributes );\n\t BASE_MVC.SelectableViewMixin.initialize.call( this, attributes );\n\t BASE_MVC.DraggableViewMixin.initialize.call( this, attributes );\n\t this._setUpListeners();\n\t },\n\t\n\t /** event listeners */\n\t _setUpListeners : function(){\n\t // hide the primary actions in the title bar when selectable and narrow\n\t this.on( 'selectable', function( isSelectable ){\n\t if( isSelectable ){\n\t this.$( '.primary-actions' ).hide();\n\t } else {\n\t this.$( '.primary-actions' ).show();\n\t }\n\t }, this );\n\t return this;\n\t },\n\t\n\t // ........................................................................ rendering\n\t /** In this override, call methods to build warnings, titlebar and primary actions */\n\t _buildNewRender : function(){\n\t var $newRender = ExpandableView.prototype._buildNewRender.call( this );\n\t $newRender.children( '.warnings' ).replaceWith( this._renderWarnings() );\n\t $newRender.children( '.title-bar' ).replaceWith( this._renderTitleBar() );\n\t $newRender.children( '.primary-actions' ).append( this._renderPrimaryActions() );\n\t $newRender.find( '> .title-bar .subtitle' ).replaceWith( this._renderSubtitle() );\n\t return $newRender;\n\t },\n\t\n\t /** In this override, render the selector controls and set up dragging before the swap */\n\t _swapNewRender : function( $newRender ){\n\t ExpandableView.prototype._swapNewRender.call( this, $newRender );\n\t if( this.selectable ){ this.showSelector( 0 ); }\n\t if( this.draggable ){ this.draggableOn(); }\n\t return this.$el;\n\t },\n\t\n\t /** Render any warnings the item may need to show (e.g. \"I'm deleted\") */\n\t _renderWarnings : function(){\n\t var view = this,\n\t $warnings = $( '
    ' ),\n\t json = view.model.toJSON();\n\t //TODO:! unordered (map)\n\t _.each( view.templates.warnings, function( templateFn ){\n\t $warnings.append( $( templateFn( json, view ) ) );\n\t });\n\t return $warnings;\n\t },\n\t\n\t /** Render the title bar (the main/exposed SUMMARY dom element) */\n\t _renderTitleBar : function(){\n\t return $( this.templates.titleBar( this.model.toJSON(), this ) );\n\t },\n\t\n\t /** Return an array of jQ objects containing common/easily-accessible item controls */\n\t _renderPrimaryActions : function(){\n\t // override this\n\t return [];\n\t },\n\t\n\t /** Render the title bar (the main/exposed SUMMARY dom element) */\n\t _renderSubtitle : function(){\n\t return $( this.templates.subtitle( this.model.toJSON(), this ) );\n\t },\n\t\n\t // ......................................................................... events\n\t /** event map */\n\t events : {\n\t // expand the body when the title is clicked or when in focus and space or enter is pressed\n\t 'click .title-bar' : '_clickTitleBar',\n\t 'keydown .title-bar' : '_keyDownTitleBar',\n\t 'click .selector' : 'toggleSelect'\n\t },\n\t\n\t /** expand when the title bar is clicked */\n\t _clickTitleBar : function( event ){\n\t event.stopPropagation();\n\t if( event.altKey ){\n\t this.toggleSelect( event );\n\t if( !this.selectable ){\n\t this.showSelector();\n\t }\n\t } else {\n\t this.toggleExpanded();\n\t }\n\t },\n\t\n\t /** expand when the title bar is in focus and enter or space is pressed */\n\t _keyDownTitleBar : function( event ){\n\t // bail (with propagation) if keydown and not space or enter\n\t var KEYCODE_SPACE = 32, KEYCODE_RETURN = 13;\n\t if( event && ( event.type === 'keydown' )\n\t &&( event.keyCode === KEYCODE_SPACE || event.keyCode === KEYCODE_RETURN ) ){\n\t this.toggleExpanded();\n\t event.stopPropagation();\n\t return false;\n\t }\n\t return true;\n\t },\n\t\n\t // ......................................................................... misc\n\t /** String representation */\n\t toString : function(){\n\t var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n\t return 'ListItemView(' + modelString + ')';\n\t }\n\t}));\n\t\n\t// ............................................................................ TEMPLATES\n\t/** underscore templates */\n\tListItemView.prototype.templates = (function(){\n\t\n\t var elTemplato = BASE_MVC.wrapTemplate([\n\t '
    ',\n\t // errors, messages, etc.\n\t '
    ',\n\t\n\t // multi-select checkbox\n\t '
    ',\n\t '',\n\t '
    ',\n\t // space for title bar buttons - gen. floated to the right\n\t '
    ',\n\t '
    ',\n\t\n\t // expandable area for more details\n\t '
    ',\n\t '
    '\n\t ]);\n\t\n\t var warnings = {};\n\t\n\t var titleBarTemplate = BASE_MVC.wrapTemplate([\n\t // adding a tabindex here allows focusing the title bar and the use of keydown to expand the dataset display\n\t '
    ',\n\t //TODO: prob. belongs in dataset-list-item\n\t '',\n\t '
    ',\n\t '<%- element.name %>',\n\t '
    ',\n\t '
    ',\n\t '
    '\n\t ], 'element' );\n\t\n\t var subtitleTemplate = BASE_MVC.wrapTemplate([\n\t // override this\n\t '
    '\n\t ]);\n\t\n\t var detailsTemplate = BASE_MVC.wrapTemplate([\n\t // override this\n\t '
    '\n\t ]);\n\t\n\t return {\n\t el : elTemplato,\n\t warnings : warnings,\n\t titleBar : titleBarTemplate,\n\t subtitle : subtitleTemplate,\n\t details : detailsTemplate\n\t };\n\t}());\n\t\n\t\n\t//==============================================================================\n\t/** A view that is displayed in some larger list/grid/collection.\n\t * *AND* can display some sub-list of it's own when expanded (e.g. dataset collections).\n\t * This list will 'foldout' when the item is expanded depending on this.foldoutStyle:\n\t * If 'foldout': will expand vertically to show the nested list\n\t * If 'drilldown': will overlay the parent list\n\t *\n\t * Inherits from ListItemView.\n\t *\n\t * _renderDetails does the work of creating this.details: a sub-view that shows the nested list\n\t */\n\tvar FoldoutListItemView = ListItemView.extend({\n\t\n\t /** If 'foldout': show the sub-panel inside the expanded item\n\t * If 'drilldown': only fire events and handle by pub-sub\n\t * (allow the panel containing this item to attach it, hide itself, etc.)\n\t */\n\t foldoutStyle : 'foldout',\n\t /** Panel view class to instantiate for the sub-panel */\n\t foldoutPanelClass : null,\n\t\n\t /** override to:\n\t * add attributes foldoutStyle and foldoutPanelClass for config poly\n\t * disrespect attributes.expanded if drilldown\n\t */\n\t initialize : function( attributes ){\n\t if( this.foldoutStyle === 'drilldown' ){ this.expanded = false; }\n\t this.foldoutStyle = attributes.foldoutStyle || this.foldoutStyle;\n\t this.foldoutPanelClass = attributes.foldoutPanelClass || this.foldoutPanelClass;\n\t\n\t ListItemView.prototype.initialize.call( this, attributes );\n\t this.foldout = this._createFoldoutPanel();\n\t },\n\t\n\t /** in this override, attach the foldout panel when rendering details */\n\t _renderDetails : function(){\n\t if( this.foldoutStyle === 'drilldown' ){ return $(); }\n\t var $newDetails = ListItemView.prototype._renderDetails.call( this );\n\t return this._attachFoldout( this.foldout, $newDetails );\n\t },\n\t\n\t /** In this override, handle collection expansion. */\n\t _createFoldoutPanel : function(){\n\t var model = this.model;\n\t var FoldoutClass = this._getFoldoutPanelClass( model ),\n\t options = this._getFoldoutPanelOptions( model ),\n\t foldout = new FoldoutClass( _.extend( options, {\n\t model : model\n\t }));\n\t return foldout;\n\t },\n\t\n\t /** Stub to return proper foldout panel class */\n\t _getFoldoutPanelClass : function(){\n\t // override\n\t return this.foldoutPanelClass;\n\t },\n\t\n\t /** Stub to return proper foldout panel options */\n\t _getFoldoutPanelOptions : function(){\n\t return {\n\t // propagate foldout style down\n\t foldoutStyle : this.foldoutStyle,\n\t fxSpeed : this.fxSpeed\n\t };\n\t },\n\t\n\t /** Render the foldout panel inside the view, hiding controls */\n\t _attachFoldout : function( foldout, $whereTo ){\n\t $whereTo = $whereTo || this.$( '> .details' );\n\t this.foldout = foldout.render( 0 );\n\t foldout.$( '> .controls' ).hide();\n\t return $whereTo.append( foldout.$el );\n\t },\n\t\n\t /** In this override, branch on foldoutStyle to show expanded */\n\t expand : function(){\n\t var view = this;\n\t return view._fetchModelDetails()\n\t .always(function(){\n\t if( view.foldoutStyle === 'foldout' ){\n\t view._expand();\n\t } else if( view.foldoutStyle === 'drilldown' ){\n\t view._expandByDrilldown();\n\t }\n\t });\n\t },\n\t\n\t /** For drilldown, set up close handler and fire expanded:drilldown\n\t * containing views can listen to this and handle other things\n\t * (like hiding themselves) by listening for expanded/collapsed:drilldown\n\t */\n\t _expandByDrilldown : function(){\n\t var view = this;\n\t // attachment and rendering done by listener\n\t view.listenTo( view.foldout, 'close', function(){\n\t view.trigger( 'collapsed:drilldown', view, view.foldout );\n\t });\n\t view.trigger( 'expanded:drilldown', view, view.foldout );\n\t }\n\t\n\t});\n\t\n\t// ............................................................................ TEMPLATES\n\t/** underscore templates */\n\tFoldoutListItemView.prototype.templates = (function(){\n\t\n\t var detailsTemplate = BASE_MVC.wrapTemplate([\n\t '
    ',\n\t // override with more info (that goes above the panel)\n\t '
    '\n\t ], 'collection' );\n\t\n\t return _.extend( {}, ListItemView.prototype.templates, {\n\t details : detailsTemplate\n\t });\n\t}());\n\t\n\t\n\t//==============================================================================\n\t return {\n\t ExpandableView : ExpandableView,\n\t ListItemView : ListItemView,\n\t FoldoutListItemView : FoldoutListItemView\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(1), __webpack_require__(2), __webpack_require__(1)))\n\n/***/ },\n/* 43 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function($, _) {/**\n\t This is the base class of the tool form plugin. This class is e.g. inherited by the regular and the workflow tool form.\n\t*/\n\t!(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(4), __webpack_require__(55), __webpack_require__(7), __webpack_require__(38),\n\t __webpack_require__(17), __webpack_require__(28)], __WEBPACK_AMD_DEFINE_RESULT__ = function(Utils, Deferred, Ui, FormBase, CitationModel, CitationView) {\n\t return FormBase.extend({\n\t initialize: function(options) {\n\t var self = this;\n\t FormBase.prototype.initialize.call(this, options);\n\t this.deferred = new Deferred();\n\t if (options.inputs) {\n\t this._buildForm(options);\n\t } else {\n\t this.deferred.execute(function(process) {\n\t self._buildModel(process, options, true);\n\t });\n\t }\n\t // listen to history panel\n\t if ( options.listen_to_history && parent.Galaxy && parent.Galaxy.currHistoryPanel ) {\n\t this.listenTo( parent.Galaxy.currHistoryPanel.collection, 'change', function() {\n\t this.refresh();\n\t });\n\t }\n\t // destroy dom elements\n\t this.$el.on( 'remove', function() { self.remove() } );\n\t },\n\t\n\t /** Listen to history panel changes and update the tool form */\n\t refresh: function() {\n\t var self = this;\n\t self.deferred.reset();\n\t this.deferred.execute( function (process){\n\t self._updateModel( process)\n\t });\n\t },\n\t\n\t /** Wait for deferred build processes before removal */\n\t remove: function() {\n\t var self = this;\n\t this.$el.hide();\n\t this.deferred.execute(function(){\n\t FormBase.prototype.remove.call(self);\n\t Galaxy.emit.debug('tool-form-base::remove()', 'Destroy view.');\n\t });\n\t },\n\t\n\t /** Build form */\n\t _buildForm: function(options) {\n\t var self = this;\n\t this.options = Utils.merge(options, this.options);\n\t this.options = Utils.merge({\n\t icon : options.icon,\n\t title : '' + options.name + ' ' + options.description + ' (Galaxy Version ' + options.version + ')',\n\t operations : !this.options.hide_operations && this._operations(),\n\t onchange : function() {\n\t self.refresh();\n\t }\n\t }, this.options);\n\t this.options.customize && this.options.customize( this.options );\n\t this.render();\n\t if ( !this.options.collapsible ) {\n\t this.$el.append( $( '
    ' ).addClass( 'ui-margin-top-large' ).append( this._footer() ) );\n\t }\n\t },\n\t\n\t /** Builds a new model through api call and recreates the entire form\n\t */\n\t _buildModel: function(process, options, hide_message) {\n\t var self = this;\n\t this.options.id = options.id;\n\t this.options.version = options.version;\n\t\n\t // build request url\n\t var build_url = '';\n\t var build_data = {};\n\t if ( options.job_id ) {\n\t build_url = Galaxy.root + 'api/jobs/' + options.job_id + '/build_for_rerun';\n\t } else {\n\t build_url = Galaxy.root + 'api/tools/' + options.id + '/build';\n\t if ( Galaxy.params && Galaxy.params.tool_id == options.id ) {\n\t build_data = $.extend( {}, Galaxy.params );\n\t options.version && ( build_data[ 'tool_version' ] = options.version );\n\t }\n\t }\n\t\n\t // get initial model\n\t Utils.get({\n\t url : build_url,\n\t data : build_data,\n\t success : function(new_model) {\n\t new_model = new_model.tool_model || new_model;\n\t if( !new_model.display ) {\n\t window.location = Galaxy.root;\n\t return;\n\t }\n\t self._buildForm(new_model);\n\t !hide_message && self.message.update({\n\t status : 'success',\n\t message : 'Now you are using \\'' + self.options.name + '\\' version ' + self.options.version + ', id \\'' + self.options.id + '\\'.',\n\t persistent : false\n\t });\n\t Galaxy.emit.debug('tool-form-base::initialize()', 'Initial tool model ready.', new_model);\n\t process.resolve();\n\t },\n\t error : function(response, status) {\n\t var error_message = ( response && response.err_msg ) || 'Uncaught error.';\n\t if ( status == 401 ) {\n\t window.location = Galaxy.root + 'user/login?' + $.param({ redirect : Galaxy.root + '?tool_id=' + self.options.id });\n\t } else if ( self.$el.is(':empty') ) {\n\t self.$el.prepend((new Ui.Message({\n\t message : error_message,\n\t status : 'danger',\n\t persistent : true,\n\t large : true\n\t })).$el);\n\t } else {\n\t Galaxy.modal && Galaxy.modal.show({\n\t title : 'Tool request failed',\n\t body : error_message,\n\t buttons : {\n\t 'Close' : function() {\n\t Galaxy.modal.hide();\n\t }\n\t }\n\t });\n\t }\n\t Galaxy.emit.debug('tool-form::initialize()', 'Initial tool model request failed.', response);\n\t process.reject();\n\t }\n\t });\n\t },\n\t\n\t /** Request a new model for an already created tool form and updates the form inputs\n\t */\n\t _updateModel: function(process) {\n\t // link this\n\t var self = this;\n\t var model_url = this.options.update_url || Galaxy.root + 'api/tools/' + this.options.id + '/build';\n\t var current_state = {\n\t tool_id : this.options.id,\n\t tool_version : this.options.version,\n\t inputs : $.extend(true, {}, self.data.create())\n\t }\n\t this.wait(true);\n\t\n\t // log tool state\n\t Galaxy.emit.debug('tool-form-base::_updateModel()', 'Sending current state.', current_state);\n\t\n\t // post job\n\t Utils.request({\n\t type : 'POST',\n\t url : model_url,\n\t data : current_state,\n\t success : function(new_model) {\n\t self.update(new_model['tool_model'] || new_model);\n\t self.options.update && self.options.update(new_model);\n\t self.wait(false);\n\t Galaxy.emit.debug('tool-form-base::_updateModel()', 'Received new model.', new_model);\n\t process.resolve();\n\t },\n\t error : function(response) {\n\t Galaxy.emit.debug('tool-form-base::_updateModel()', 'Refresh request failed.', response);\n\t process.reject();\n\t }\n\t });\n\t },\n\t\n\t /** Create tool operation menu\n\t */\n\t _operations: function() {\n\t var self = this;\n\t var options = this.options;\n\t\n\t // button for version selection\n\t var versions_button = new Ui.ButtonMenu({\n\t icon : 'fa-cubes',\n\t title : (!options.narrow && 'Versions') || null,\n\t tooltip : 'Select another tool version'\n\t });\n\t if (!options.sustain_version && options.versions && options.versions.length > 1) {\n\t for (var i in options.versions) {\n\t var version = options.versions[i];\n\t if (version != options.version) {\n\t versions_button.addMenu({\n\t title : 'Switch to ' + version,\n\t version : version,\n\t icon : 'fa-cube',\n\t onclick : function() {\n\t // here we update the tool version (some tools encode the version also in the id)\n\t var id = options.id.replace(options.version, this.version);\n\t var version = this.version;\n\t // queue model request\n\t self.deferred.reset();\n\t self.deferred.execute(function(process) {\n\t self._buildModel(process, {id: id, version: version})\n\t });\n\t }\n\t });\n\t }\n\t }\n\t } else {\n\t versions_button.$el.hide();\n\t }\n\t\n\t // button for options e.g. search, help\n\t var menu_button = new Ui.ButtonMenu({\n\t icon : 'fa-caret-down',\n\t title : (!options.narrow && 'Options') || null,\n\t tooltip : 'View available options'\n\t });\n\t if(options.biostar_url) {\n\t menu_button.addMenu({\n\t icon : 'fa-question-circle',\n\t title : 'Question?',\n\t tooltip : 'Ask a question about this tool (Biostar)',\n\t onclick : function() {\n\t window.open(options.biostar_url + '/p/new/post/');\n\t }\n\t });\n\t menu_button.addMenu({\n\t icon : 'fa-search',\n\t title : 'Search',\n\t tooltip : 'Search help for this tool (Biostar)',\n\t onclick : function() {\n\t window.open(options.biostar_url + '/local/search/page/?q=' + options.name);\n\t }\n\t });\n\t };\n\t menu_button.addMenu({\n\t icon : 'fa-share',\n\t title : 'Share',\n\t tooltip : 'Share this tool',\n\t onclick : function() {\n\t prompt('Copy to clipboard: Ctrl+C, Enter', window.location.origin + Galaxy.root + 'root?tool_id=' + options.id);\n\t }\n\t });\n\t\n\t // add admin operations\n\t if (Galaxy.user && Galaxy.user.get('is_admin')) {\n\t menu_button.addMenu({\n\t icon : 'fa-download',\n\t title : 'Download',\n\t tooltip : 'Download this tool',\n\t onclick : function() {\n\t window.location.href = Galaxy.root + 'api/tools/' + options.id + '/download';\n\t }\n\t });\n\t }\n\t\n\t // button for version selection\n\t if (options.requirements && options.requirements.length > 0) {\n\t menu_button.addMenu({\n\t icon : 'fa-info-circle',\n\t title : 'Requirements',\n\t tooltip : 'Display tool requirements',\n\t onclick : function() {\n\t if (!this.visible || self.portlet.collapsed ) {\n\t this.visible = true;\n\t self.portlet.expand();\n\t self.message.update({\n\t persistent : true,\n\t message : self._templateRequirements(options),\n\t status : 'info'\n\t });\n\t } else {\n\t this.visible = false;\n\t self.message.update({\n\t message : ''\n\t });\n\t }\n\t }\n\t });\n\t }\n\t\n\t // add toolshed url\n\t if (options.sharable_url) {\n\t menu_button.addMenu({\n\t icon : 'fa-external-link',\n\t title : 'See in Tool Shed',\n\t tooltip : 'Access the repository',\n\t onclick : function() {\n\t window.open(options.sharable_url);\n\t }\n\t });\n\t }\n\t\n\t return {\n\t menu : menu_button,\n\t versions : versions_button\n\t }\n\t },\n\t\n\t /** Create footer\n\t */\n\t _footer: function() {\n\t var options = this.options;\n\t var $el = $( '
    ' ).append( this._templateHelp( options ) );\n\t if ( options.citations ) {\n\t var $citations = $( '
    ' );\n\t var citations = new CitationModel.ToolCitationCollection();\n\t citations.tool_id = options.id;\n\t var citation_list_view = new CitationView.CitationListView({ el: $citations, collection: citations });\n\t citation_list_view.render();\n\t citations.fetch();\n\t $el.append( $citations );\n\t }\n\t return $el;\n\t },\n\t\n\t /** Templates\n\t */\n\t _templateHelp: function( options ) {\n\t var $tmpl = $( '
    ' ).addClass( 'ui-form-help' ).append( options.help );\n\t $tmpl.find( 'a' ).attr( 'target', '_blank' );\n\t return $tmpl;\n\t },\n\t\n\t _templateRequirements: function( options ) {\n\t var nreq = options.requirements.length;\n\t if ( nreq > 0 ) {\n\t var requirements_message = 'This tool requires ';\n\t _.each( options.requirements, function( req, i ) {\n\t requirements_message += req.name + ( req.version ? ' (Version ' + req.version + ')' : '' ) + ( i < nreq - 2 ? ', ' : ( i == nreq - 2 ? ' and ' : '' ) );\n\t });\n\t var requirements_link = $( '' ).attr( 'target', '_blank' ).attr( 'href', 'https://wiki.galaxyproject.org/Tools/Requirements' ).text( 'here' );\n\t return $( '' ).append( requirements_message + '. Click ' ).append( requirements_link ).append( ' for more information.' );\n\t }\n\t return 'No requirements found.';\n\t }\n\t });\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(1), __webpack_require__(2)))\n\n/***/ },\n/* 44 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, $) {/**\n\t * Model, view, and controller objects for Galaxy tools and tool panel.\n\t */\n\t\n\t !(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(2),\n\t __webpack_require__(16),\n\t __webpack_require__(11),\n\t __webpack_require__(18)\n\t\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function(_, util, data, ToolForm) {\n\t 'use strict';\n\t\n\t/**\n\t * Mixin for tracking model visibility.\n\t */\n\tvar VisibilityMixin = {\n\t hidden: false,\n\t\n\t show: function() {\n\t this.set(\"hidden\", false);\n\t },\n\t\n\t hide: function() {\n\t this.set(\"hidden\", true);\n\t },\n\t\n\t toggle: function() {\n\t this.set(\"hidden\", !this.get(\"hidden\"));\n\t },\n\t\n\t is_visible: function() {\n\t return !this.attributes.hidden;\n\t }\n\t\n\t};\n\t\n\t/**\n\t * A tool parameter.\n\t */\n\tvar ToolParameter = Backbone.Model.extend({\n\t defaults: {\n\t name: null,\n\t label: null,\n\t type: null,\n\t value: null,\n\t html: null,\n\t num_samples: 5\n\t },\n\t\n\t initialize: function(options) {\n\t this.attributes.html = unescape(this.attributes.html);\n\t },\n\t\n\t copy: function() {\n\t return new ToolParameter(this.toJSON());\n\t },\n\t\n\t set_value: function(value) {\n\t this.set('value', value || '');\n\t }\n\t});\n\t\n\tvar ToolParameterCollection = Backbone.Collection.extend({\n\t model: ToolParameter\n\t});\n\t\n\t/**\n\t * A data tool parameter.\n\t */\n\tvar DataToolParameter = ToolParameter.extend({});\n\t\n\t/**\n\t * An integer tool parameter.\n\t */\n\tvar IntegerToolParameter = ToolParameter.extend({\n\t set_value: function(value) {\n\t this.set('value', parseInt(value, 10));\n\t },\n\t\n\t /**\n\t * Returns samples from a tool input.\n\t */\n\t get_samples: function() {\n\t return d3.scale.linear()\n\t .domain([this.get('min'), this.get('max')])\n\t .ticks(this.get('num_samples'));\n\t }\n\t});\n\t\n\tvar FloatToolParameter = IntegerToolParameter.extend({\n\t set_value: function(value) {\n\t this.set('value', parseFloat(value));\n\t }\n\t});\n\t\n\t/**\n\t * A select tool parameter.\n\t */\n\tvar SelectToolParameter = ToolParameter.extend({\n\t /**\n\t * Returns tool options.\n\t */\n\t get_samples: function() {\n\t return _.map(this.get('options'), function(option) {\n\t return option[0];\n\t });\n\t }\n\t});\n\t\n\t// Set up dictionary of parameter types.\n\tToolParameter.subModelTypes = {\n\t 'integer': IntegerToolParameter,\n\t 'float': FloatToolParameter,\n\t 'data': DataToolParameter,\n\t 'select': SelectToolParameter\n\t};\n\t\n\t/**\n\t * A Galaxy tool.\n\t */\n\tvar Tool = Backbone.Model.extend({\n\t // Default attributes.\n\t defaults: {\n\t id: null,\n\t name: null,\n\t description: null,\n\t target: null,\n\t inputs: [],\n\t outputs: []\n\t },\n\t\n\t urlRoot: Galaxy.root + 'api/tools',\n\t\n\t initialize: function(options) {\n\t\n\t // Set parameters.\n\t this.set('inputs', new ToolParameterCollection(_.map(options.inputs, function(p) {\n\t var p_class = ToolParameter.subModelTypes[p.type] || ToolParameter;\n\t return new p_class(p);\n\t })));\n\t },\n\t\n\t /**\n\t *\n\t */\n\t toJSON: function() {\n\t var rval = Backbone.Model.prototype.toJSON.call(this);\n\t\n\t // Convert inputs to JSON manually.\n\t rval.inputs = this.get('inputs').map(function(i) { return i.toJSON(); });\n\t return rval;\n\t },\n\t\n\t /**\n\t * Removes inputs of a particular type; this is useful because not all inputs can be handled by\n\t * client and server yet.\n\t */\n\t remove_inputs: function(types) {\n\t var tool = this,\n\t incompatible_inputs = tool.get('inputs').filter( function(input) {\n\t return ( types.indexOf( input.get('type') ) !== -1);\n\t });\n\t tool.get('inputs').remove(incompatible_inputs);\n\t },\n\t\n\t /**\n\t * Returns object copy, optionally including only inputs that can be sampled.\n\t */\n\t copy: function(only_samplable_inputs) {\n\t var copy = new Tool(this.toJSON());\n\t\n\t // Return only samplable inputs if flag is set.\n\t if (only_samplable_inputs) {\n\t var valid_inputs = new Backbone.Collection();\n\t copy.get('inputs').each(function(input) {\n\t if (input.get_samples()) {\n\t valid_inputs.push(input);\n\t }\n\t });\n\t copy.set('inputs', valid_inputs);\n\t }\n\t\n\t return copy;\n\t },\n\t\n\t apply_search_results: function(results) {\n\t ( _.indexOf(results, this.attributes.id) !== -1 ? this.show() : this.hide() );\n\t return this.is_visible();\n\t },\n\t\n\t /**\n\t * Set a tool input's value.\n\t */\n\t set_input_value: function(name, value) {\n\t this.get('inputs').find(function(input) {\n\t return input.get('name') === name;\n\t }).set('value', value);\n\t },\n\t\n\t /**\n\t * Set many input values at once.\n\t */\n\t set_input_values: function(inputs_dict) {\n\t var self = this;\n\t _.each(_.keys(inputs_dict), function(input_name) {\n\t self.set_input_value(input_name, inputs_dict[input_name]);\n\t });\n\t },\n\t\n\t /**\n\t * Run tool; returns a Deferred that resolves to the tool's output(s).\n\t */\n\t run: function() {\n\t return this._run();\n\t },\n\t\n\t /**\n\t * Rerun tool using regions and a target dataset.\n\t */\n\t rerun: function(target_dataset, regions) {\n\t return this._run({\n\t action: 'rerun',\n\t target_dataset_id: target_dataset.id,\n\t regions: regions\n\t });\n\t },\n\t\n\t /**\n\t * Returns input dict for tool's inputs.\n\t */\n\t get_inputs_dict: function() {\n\t var input_dict = {};\n\t this.get('inputs').each(function(input) {\n\t input_dict[input.get('name')] = input.get('value');\n\t });\n\t return input_dict;\n\t },\n\t\n\t /**\n\t * Run tool; returns a Deferred that resolves to the tool's output(s).\n\t * NOTE: this method is a helper method and should not be called directly.\n\t */\n\t _run: function(additional_params) {\n\t // Create payload.\n\t var payload = _.extend({\n\t tool_id: this.id,\n\t inputs: this.get_inputs_dict()\n\t }, additional_params);\n\t\n\t // Because job may require indexing datasets, use server-side\n\t // deferred to ensure that job is run. Also use deferred that\n\t // resolves to outputs from tool.\n\t var run_deferred = $.Deferred(),\n\t ss_deferred = new util.ServerStateDeferred({\n\t ajax_settings: {\n\t url: this.urlRoot,\n\t data: JSON.stringify(payload),\n\t dataType: \"json\",\n\t contentType: 'application/json',\n\t type: \"POST\"\n\t },\n\t interval: 2000,\n\t success_fn: function(response) {\n\t return response !== \"pending\";\n\t }\n\t });\n\t\n\t // Run job and resolve run_deferred to tool outputs.\n\t $.when(ss_deferred.go()).then(function(result) {\n\t run_deferred.resolve(new data.DatasetCollection(result));\n\t });\n\t return run_deferred;\n\t }\n\t});\n\t_.extend(Tool.prototype, VisibilityMixin);\n\t\n\t/**\n\t * Tool view.\n\t */\n\tvar ToolView = Backbone.View.extend({\n\t\n\t});\n\t\n\t/**\n\t * Wrap collection of tools for fast access/manipulation.\n\t */\n\tvar ToolCollection = Backbone.Collection.extend({\n\t model: Tool\n\t});\n\t\n\t/**\n\t * Label or section header in tool panel.\n\t */\n\tvar ToolSectionLabel = Backbone.Model.extend(VisibilityMixin);\n\t\n\t/**\n\t * Section of tool panel with elements (labels and tools).\n\t */\n\tvar ToolSection = Backbone.Model.extend({\n\t defaults: {\n\t elems: [],\n\t open: false\n\t },\n\t\n\t clear_search_results: function() {\n\t _.each(this.attributes.elems, function(elt) {\n\t elt.show();\n\t });\n\t\n\t this.show();\n\t this.set(\"open\", false);\n\t },\n\t\n\t apply_search_results: function(results) {\n\t var all_hidden = true,\n\t cur_label;\n\t _.each(this.attributes.elems, function(elt) {\n\t if (elt instanceof ToolSectionLabel) {\n\t cur_label = elt;\n\t cur_label.hide();\n\t }\n\t else if (elt instanceof Tool) {\n\t if (elt.apply_search_results(results)) {\n\t all_hidden = false;\n\t if (cur_label) {\n\t cur_label.show();\n\t }\n\t }\n\t }\n\t });\n\t\n\t if (all_hidden) {\n\t this.hide();\n\t }\n\t else {\n\t this.show();\n\t this.set(\"open\", true);\n\t }\n\t }\n\t});\n\t_.extend(ToolSection.prototype, VisibilityMixin);\n\t\n\t/**\n\t * Tool search that updates results when query is changed. Result value of null\n\t * indicates that query was not run; if not null, results are from search using\n\t * query.\n\t */\n\tvar ToolSearch = Backbone.Model.extend({\n\t defaults: {\n\t search_hint_string: \"search tools\",\n\t min_chars_for_search: 3,\n\t clear_btn_url: \"\",\n\t search_url: \"\",\n\t visible: true,\n\t query: \"\",\n\t results: null,\n\t // ESC (27) will clear the input field and tool search filters\n\t clear_key: 27\n\t },\n\t\n\t urlRoot: Galaxy.root + 'api/tools',\n\t\n\t initialize: function() {\n\t this.on(\"change:query\", this.do_search);\n\t },\n\t\n\t /**\n\t * Do the search and update the results.\n\t */\n\t do_search: function() {\n\t var query = this.attributes.query;\n\t\n\t // If query is too short, do not search.\n\t if (query.length < this.attributes.min_chars_for_search) {\n\t this.set(\"results\", null);\n\t return;\n\t }\n\t\n\t // Do search via AJAX.\n\t var q = query;\n\t // Stop previous ajax-request\n\t if (this.timer) {\n\t clearTimeout(this.timer);\n\t }\n\t // Start a new ajax-request in X ms\n\t $(\"#search-clear-btn\").hide();\n\t $(\"#search-spinner\").show();\n\t var self = this;\n\t this.timer = setTimeout(function () {\n\t // log the search to analytics if present\n\t if ( typeof ga !== 'undefined' ) {\n\t ga( 'send', 'pageview', Galaxy.root + '?q=' + q );\n\t }\n\t $.get( self.urlRoot, { q: q }, function (data) {\n\t self.set(\"results\", data);\n\t $(\"#search-spinner\").hide();\n\t $(\"#search-clear-btn\").show();\n\t }, \"json\" );\n\t }, 400 );\n\t },\n\t\n\t clear_search: function() {\n\t this.set(\"query\", \"\");\n\t this.set(\"results\", null);\n\t }\n\t\n\t});\n\t_.extend(ToolSearch.prototype, VisibilityMixin);\n\t\n\t/**\n\t * Tool Panel.\n\t */\n\tvar ToolPanel = Backbone.Model.extend({\n\t\n\t initialize: function(options) {\n\t this.attributes.tool_search = options.tool_search;\n\t this.attributes.tool_search.on(\"change:results\", this.apply_search_results, this);\n\t this.attributes.tools = options.tools;\n\t this.attributes.layout = new Backbone.Collection( this.parse(options.layout) );\n\t },\n\t\n\t /**\n\t * Parse tool panel dictionary and return collection of tool panel elements.\n\t */\n\t parse: function(response) {\n\t // Recursive function to parse tool panel elements.\n\t var self = this,\n\t // Helper to recursively parse tool panel.\n\t parse_elt = function(elt_dict) {\n\t var type = elt_dict.model_class;\n\t // There are many types of tools; for now, anything that ends in 'Tool'\n\t // is treated as a generic tool.\n\t if ( type.indexOf('Tool') === type.length - 4 ) {\n\t return self.attributes.tools.get(elt_dict.id);\n\t }\n\t else if (type === 'ToolSection') {\n\t // Parse elements.\n\t var elems = _.map(elt_dict.elems, parse_elt);\n\t elt_dict.elems = elems;\n\t return new ToolSection(elt_dict);\n\t }\n\t else if (type === 'ToolSectionLabel') {\n\t return new ToolSectionLabel(elt_dict);\n\t }\n\t };\n\t\n\t return _.map(response, parse_elt);\n\t },\n\t\n\t clear_search_results: function() {\n\t this.get('layout').each(function(panel_elt) {\n\t if (panel_elt instanceof ToolSection) {\n\t panel_elt.clear_search_results();\n\t }\n\t else {\n\t // Label or tool, so just show.\n\t panel_elt.show();\n\t }\n\t });\n\t },\n\t\n\t apply_search_results: function() {\n\t var results = this.get('tool_search').get('results');\n\t if (results === null) {\n\t this.clear_search_results();\n\t return;\n\t }\n\t\n\t var cur_label = null;\n\t this.get('layout').each(function(panel_elt) {\n\t if (panel_elt instanceof ToolSectionLabel) {\n\t cur_label = panel_elt;\n\t cur_label.hide();\n\t }\n\t else if (panel_elt instanceof Tool) {\n\t if (panel_elt.apply_search_results(results)) {\n\t if (cur_label) {\n\t cur_label.show();\n\t }\n\t }\n\t }\n\t else {\n\t // Starting new section, so clear current label.\n\t cur_label = null;\n\t panel_elt.apply_search_results(results);\n\t }\n\t });\n\t }\n\t});\n\t\n\t/**\n\t * View classes for Galaxy tools and tool panel.\n\t *\n\t * Views use the templates defined below for rendering. Views update as needed\n\t * based on (a) model/collection events and (b) user interactions; in this sense,\n\t * they are controllers are well and the HTML is the real view in the MVC architecture.\n\t */\n\t\n\t/**\n\t * Base view that handles visibility based on model's hidden attribute.\n\t */\n\tvar BaseView = Backbone.View.extend({\n\t initialize: function() {\n\t this.model.on(\"change:hidden\", this.update_visible, this);\n\t this.update_visible();\n\t },\n\t update_visible: function() {\n\t ( this.model.attributes.hidden ? this.$el.hide() : this.$el.show() );\n\t }\n\t});\n\t\n\t/**\n\t * Link to a tool.\n\t */\n\tvar ToolLinkView = BaseView.extend({\n\t tagName: 'div',\n\t\n\t render: function() {\n\t // create element\n\t var $link = $('
    ');\n\t $link.append(templates.tool_link(this.model.toJSON()));\n\t\n\t var formStyle = this.model.get( 'form_style', null );\n\t // open upload dialog for upload tool\n\t if (this.model.id === 'upload1') {\n\t $link.find('a').on('click', function(e) {\n\t e.preventDefault();\n\t Galaxy.upload.show();\n\t });\n\t }\n\t else if ( formStyle === 'regular' ) { // regular tools\n\t var self = this;\n\t $link.find('a').on('click', function(e) {\n\t e.preventDefault();\n\t var form = new ToolForm.View( { id : self.model.id, version : self.model.get('version') } );\n\t form.deferred.execute(function() {\n\t Galaxy.app.display( form );\n\t });\n\t });\n\t }\n\t\n\t // add element\n\t this.$el.append($link);\n\t return this;\n\t }\n\t});\n\t\n\t/**\n\t * Panel label/section header.\n\t */\n\tvar ToolSectionLabelView = BaseView.extend({\n\t tagName: 'div',\n\t className: 'toolPanelLabel',\n\t\n\t render: function() {\n\t this.$el.append( $(\"\").text(this.model.attributes.text) );\n\t return this;\n\t }\n\t});\n\t\n\t/**\n\t * Panel section.\n\t */\n\tvar ToolSectionView = BaseView.extend({\n\t tagName: 'div',\n\t className: 'toolSectionWrapper',\n\t\n\t initialize: function() {\n\t BaseView.prototype.initialize.call(this);\n\t this.model.on(\"change:open\", this.update_open, this);\n\t },\n\t\n\t render: function() {\n\t // Build using template.\n\t this.$el.append( templates.panel_section(this.model.toJSON()) );\n\t\n\t // Add tools to section.\n\t var section_body = this.$el.find(\".toolSectionBody\");\n\t _.each(this.model.attributes.elems, function(elt) {\n\t if (elt instanceof Tool) {\n\t var tool_view = new ToolLinkView({model: elt, className: \"toolTitle\"});\n\t tool_view.render();\n\t section_body.append(tool_view.$el);\n\t }\n\t else if (elt instanceof ToolSectionLabel) {\n\t var label_view = new ToolSectionLabelView({model: elt});\n\t label_view.render();\n\t section_body.append(label_view.$el);\n\t }\n\t else {\n\t // TODO: handle nested section bodies?\n\t }\n\t });\n\t return this;\n\t },\n\t\n\t events: {\n\t 'click .toolSectionTitle > a': 'toggle'\n\t },\n\t\n\t /**\n\t * Toggle visibility of tool section.\n\t */\n\t toggle: function() {\n\t this.model.set(\"open\", !this.model.attributes.open);\n\t },\n\t\n\t /**\n\t * Update whether section is open or close.\n\t */\n\t update_open: function() {\n\t (this.model.attributes.open ?\n\t this.$el.children(\".toolSectionBody\").slideDown(\"fast\") :\n\t this.$el.children(\".toolSectionBody\").slideUp(\"fast\")\n\t );\n\t }\n\t});\n\t\n\tvar ToolSearchView = Backbone.View.extend({\n\t tagName: 'div',\n\t id: 'tool-search',\n\t className: 'bar',\n\t\n\t events: {\n\t 'click': 'focus_and_select',\n\t 'keyup :input': 'query_changed',\n\t 'click #search-clear-btn': 'clear'\n\t },\n\t\n\t render: function() {\n\t this.$el.append( templates.tool_search(this.model.toJSON()) );\n\t if (!this.model.is_visible()) {\n\t this.$el.hide();\n\t }\n\t this.$el.find('[title]').tooltip();\n\t return this;\n\t },\n\t\n\t focus_and_select: function() {\n\t this.$el.find(\":input\").focus().select();\n\t },\n\t\n\t clear: function() {\n\t this.model.clear_search();\n\t this.$el.find(\":input\").val('');\n\t this.focus_and_select();\n\t return false;\n\t },\n\t\n\t query_changed: function( evData ) {\n\t // check for the 'clear key' (ESC) first\n\t if( ( this.model.attributes.clear_key ) &&\n\t ( this.model.attributes.clear_key === evData.which ) ){\n\t this.clear();\n\t return false;\n\t }\n\t this.model.set(\"query\", this.$el.find(\":input\").val());\n\t }\n\t});\n\t\n\t/**\n\t * Tool panel view. Events triggered include:\n\t * tool_link_click(click event, tool_model)\n\t */\n\tvar ToolPanelView = Backbone.View.extend({\n\t tagName: 'div',\n\t className: 'toolMenu',\n\t\n\t /**\n\t * Set up view.\n\t */\n\t initialize: function() {\n\t this.model.get('tool_search').on(\"change:results\", this.handle_search_results, this);\n\t },\n\t\n\t render: function() {\n\t var self = this;\n\t\n\t // Render search.\n\t var search_view = new ToolSearchView( { model: this.model.get('tool_search') } );\n\t search_view.render();\n\t self.$el.append(search_view.$el);\n\t\n\t // Render panel.\n\t this.model.get('layout').each(function(panel_elt) {\n\t if (panel_elt instanceof ToolSection) {\n\t var section_title_view = new ToolSectionView({model: panel_elt});\n\t section_title_view.render();\n\t self.$el.append(section_title_view.$el);\n\t }\n\t else if (panel_elt instanceof Tool) {\n\t var tool_view = new ToolLinkView({model: panel_elt, className: \"toolTitleNoSection\"});\n\t tool_view.render();\n\t self.$el.append(tool_view.$el);\n\t }\n\t else if (panel_elt instanceof ToolSectionLabel) {\n\t var label_view = new ToolSectionLabelView({model: panel_elt});\n\t label_view.render();\n\t self.$el.append(label_view.$el);\n\t }\n\t });\n\t\n\t // Setup tool link click eventing.\n\t self.$el.find(\"a.tool-link\").click(function(e) {\n\t // Tool id is always the first class.\n\t var\n\t tool_id = $(this).attr('class').split(/\\s+/)[0],\n\t tool = self.model.get('tools').get(tool_id);\n\t\n\t self.trigger(\"tool_link_click\", e, tool);\n\t });\n\t\n\t return this;\n\t },\n\t\n\t handle_search_results: function() {\n\t var results = this.model.get('tool_search').get('results');\n\t if (results && results.length === 0) {\n\t $(\"#search-no-results\").show();\n\t }\n\t else {\n\t $(\"#search-no-results\").hide();\n\t }\n\t }\n\t});\n\t\n\t/**\n\t * View for working with a tool: setting parameters and inputs and executing the tool.\n\t */\n\tvar ToolFormView = Backbone.View.extend({\n\t className: 'toolForm',\n\t\n\t render: function() {\n\t this.$el.children().remove();\n\t this.$el.append( templates.tool_form(this.model.toJSON()) );\n\t }\n\t});\n\t\n\t/**\n\t * Integrated tool menu + tool execution.\n\t */\n\tvar IntegratedToolMenuAndView = Backbone.View.extend({\n\t className: 'toolMenuAndView',\n\t\n\t initialize: function() {\n\t this.tool_panel_view = new ToolPanelView({collection: this.collection});\n\t this.tool_form_view = new ToolFormView();\n\t },\n\t\n\t render: function() {\n\t // Render and append tool panel.\n\t this.tool_panel_view.render();\n\t this.tool_panel_view.$el.css(\"float\", \"left\");\n\t this.$el.append(this.tool_panel_view.$el);\n\t\n\t // Append tool form view.\n\t this.tool_form_view.$el.hide();\n\t this.$el.append(this.tool_form_view.$el);\n\t\n\t // On tool link click, show tool.\n\t var self = this;\n\t this.tool_panel_view.on(\"tool_link_click\", function(e, tool) {\n\t // Prevents click from activating link:\n\t e.preventDefault();\n\t // Show tool that was clicked on:\n\t self.show_tool(tool);\n\t });\n\t },\n\t\n\t /**\n\t * Fetch and display tool.\n\t */\n\t show_tool: function(tool) {\n\t var self = this;\n\t tool.fetch().done( function() {\n\t self.tool_form_view.model = tool;\n\t self.tool_form_view.render();\n\t self.tool_form_view.$el.show();\n\t $('#left').width(\"650px\");\n\t });\n\t }\n\t});\n\t\n\t// TODO: move into relevant views\n\tvar templates = {\n\t // the search bar at the top of the tool panel\n\t tool_search : _.template([\n\t '\" autocomplete=\"off\" type=\"text\" />',\n\t ' ',\n\t //TODO: replace with icon\n\t '',\n\t ].join('')),\n\t\n\t // the category level container in the tool panel (e.g. 'Get Data', 'Text Manipulation')\n\t panel_section : _.template([\n\t '
    \">',\n\t '<%- name %>',\n\t '
    ',\n\t '
    \" class=\"toolSectionBody\" style=\"display: none;\">',\n\t '
    ',\n\t '
    '\n\t ].join('')),\n\t\n\t // a single tool's link in the tool panel; will load the tool form in the center panel\n\t tool_link : _.template([\n\t '',\n\t '<% _.each( labels, function( label ){ %>',\n\t '\">',\n\t '<%- label %>',\n\t '',\n\t '<% }); %>',\n\t '',\n\t ' tool-link\" href=\"<%= link %>\" target=\"<%- target %>\" minsizehint=\"<%- min_width %>\">',\n\t '<%- name %>',\n\t '',\n\t ' <%- description %>'\n\t ].join('')),\n\t\n\t // the tool form for entering tool parameters, viewing help and executing the tool\n\t // loaded when a tool link is clicked in the tool panel\n\t tool_form : _.template([\n\t '
    <%- tool.name %> (version <%- tool.version %>)
    ',\n\t '
    ',\n\t '<% _.each( tool.inputs, function( input ){ %>',\n\t '
    ',\n\t '',\n\t '
    ',\n\t '<%= input.html %>',\n\t '
    ',\n\t '
    ',\n\t '<%- input.help %>',\n\t '
    ',\n\t '
    ',\n\t '
    ',\n\t '<% }); %>',\n\t '
    ',\n\t '
    ',\n\t '',\n\t '
    ',\n\t '
    ',\n\t '
    <% tool.help %>
    ',\n\t '
    ',\n\t // TODO: we need scoping here because 'help' is the dom for the help menu in the masthead\n\t // which implies a leaky variable that I can't find\n\t ].join(''), { variable: 'tool' }),\n\t};\n\t\n\t\n\t// Exports\n\treturn {\n\t ToolParameter: ToolParameter,\n\t IntegerToolParameter: IntegerToolParameter,\n\t SelectToolParameter: SelectToolParameter,\n\t Tool: Tool,\n\t ToolCollection: ToolCollection,\n\t ToolSearch: ToolSearch,\n\t ToolPanel: ToolPanel,\n\t ToolPanelView: ToolPanelView,\n\t ToolFormView: ToolFormView\n\t};\n\t\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(1)))\n\n/***/ },\n/* 45 */,\n/* 46 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, $) {/** Renders the color picker used e.g. in the tool form **/\n\t!(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(4)], __WEBPACK_AMD_DEFINE_RESULT__ = function( Utils ) {\n\t return Backbone.View.extend({\n\t colors: {\n\t standard: ['c00000','ff0000','ffc000','ffff00','92d050','00b050','00b0f0','0070c0','002060','7030a0'],\n\t base : ['ffffff','000000','eeece1','1f497d','4f81bd','c0504d','9bbb59','8064a2','4bacc6','f79646'],\n\t theme :[['f2f2f2','7f7f7f','ddd9c3','c6d9f0','dbe5f1','f2dcdb','ebf1dd','e5e0ec','dbeef3','fdeada'],\n\t ['d8d8d8','595959','c4bd97','8db3e2','b8cce4','e5b9b7','d7e3bc','ccc1d9','b7dde8','fbd5b5'],\n\t ['bfbfbf','3f3f3f','938953','548dd4','95b3d7','d99694','c3d69b','b2a2c7','92cddc','fac08f'],\n\t ['a5a5a5','262626','494429','17365d','366092','953734','76923c','5f497a','31859b','e36c09'],\n\t ['7f7f7e','0c0c0c','1d1b10','0f243e','244061','632423','4f6128','3f3151','205867','974806']]\n\t },\n\t\n\t initialize : function( options ) {\n\t this.options = Utils.merge( options, {} );\n\t this.setElement( this._template() );\n\t this.$panel = this.$( '.ui-color-picker-panel' );\n\t this.$view = this.$( '.ui-color-picker-view' );\n\t this.$value = this.$( '.ui-color-picker-value' );\n\t this.$header = this.$( '.ui-color-picker-header' );\n\t this._build();\n\t this.visible = false;\n\t this.value( this.options.value );\n\t this.$boxes = this.$( '.ui-color-picker-box' );\n\t var self = this;\n\t this.$boxes.on( 'click', function() {\n\t self.value( $( this ).css( 'background-color' ) );\n\t self.$header.trigger( 'click' );\n\t } );\n\t this.$header.on( 'click', function() {\n\t self.visible = !self.visible;\n\t if ( self.visible ) {\n\t self.$view.fadeIn( 'fast' );\n\t } else {\n\t self.$view.fadeOut( 'fast' );\n\t }\n\t } );\n\t },\n\t\n\t /** Get/set value */\n\t value : function ( new_val ) {\n\t if ( new_val !== undefined && new_val !== null ) {\n\t this.$value.css( 'background-color', new_val );\n\t this.$( '.ui-color-picker-box' ).empty();\n\t this.$( this._getValue() ).html( this._templateCheck() );\n\t this.options.onchange && this.options.onchange( new_val );\n\t }\n\t return this._getValue();\n\t },\n\t\n\t /** Get value from dom */\n\t _getValue: function() {\n\t var rgb = this.$value.css( 'background-color' );\n\t rgb = rgb.match(/^rgb\\((\\d+),\\s*(\\d+),\\s*(\\d+)\\)$/);\n\t if ( rgb ) {\n\t function hex( x ) {\n\t return ( '0' + parseInt( x ).toString( 16 ) ).slice( -2 );\n\t }\n\t return '#' + hex( rgb[ 1] ) + hex( rgb[ 2 ] ) + hex( rgb[ 3 ] );\n\t } else {\n\t return null;\n\t }\n\t },\n\t\n\t /** Build color panel */\n\t _build: function() {\n\t var $content = this._content({\n\t label : 'Theme Colors',\n\t colors : this.colors.base,\n\t padding : 10\n\t });\n\t for ( var i in this.colors.theme ) {\n\t var line_def = {};\n\t if ( i == 0 ) {\n\t line_def[ 'bottom' ] = true;\n\t } else {\n\t if ( i != this.colors.theme.length - 1 ) {\n\t line_def[ 'top' ] = true;\n\t line_def[ 'bottom' ] = true;\n\t } else {\n\t line_def[ 'top' ] = true;\n\t line_def[ 'padding' ] = 5;\n\t }\n\t }\n\t line_def[ 'colors' ] = this.colors.theme[ i ];\n\t this._content( line_def );\n\t }\n\t this._content({\n\t label : 'Standard Colors',\n\t colors : this.colors.standard,\n\t padding : 5\n\t });\n\t },\n\t\n\t /** Create content */\n\t _content: function( options ) {\n\t var label = options.label;\n\t var colors = options.colors;\n\t var padding = options.padding;\n\t var top = options.top;\n\t var bottom = options.bottom;\n\t var $content = $( this._templateContent() );\n\t var $label = $content.find( '.label' );\n\t if ( options.label ) {\n\t $label.html( options.label );\n\t } else {\n\t $label.hide();\n\t }\n\t var $line = $content.find( '.line' );\n\t this.$panel.append( $content );\n\t for ( var i in colors ) {\n\t var $box = $( this._templateBox( colors[ i ] ) );\n\t if ( top ) {\n\t $box.css( 'border-top', 'none' );\n\t $box.css( 'border-top-left-radius', '0px' );\n\t $box.css( 'border-top-right-radius', '0px' );\n\t }\n\t if ( bottom ) {\n\t $box.css( 'border-bottom', 'none' );\n\t $box.css( 'border-bottom-left-radius', '0px' );\n\t $box.css( 'border-bottom-right-radius', '0px' );\n\t }\n\t $line.append( $box );\n\t }\n\t if (padding) {\n\t $line.css( 'padding-bottom', padding );\n\t }\n\t return $content;\n\t },\n\t\n\t /** Check icon */\n\t _templateCheck: function() {\n\t return '
    ';\n\t },\n\t\n\t /** Content template */\n\t _templateContent: function() {\n\t return '
    ' +\n\t '
    ' +\n\t '
    ' +\n\t '
    ';\n\t },\n\t\n\t /** Box template */\n\t _templateBox: function( color ) {\n\t return '
    ';\n\t },\n\t\n\t /** Main template */\n\t _template: function() {\n\t return '
    ' +\n\t '
    ' +\n\t '
    ' +\n\t '
    Select a color
    ' +\n\t '
    ' +\n\t '
    ' +\n\t '
    ' +\n\t '
    '\n\t '
    ';\n\t }\n\t });\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(1)))\n\n/***/ },\n/* 47 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function($, _) {/** This class creates/wraps a drill down element. */\n\t!(__WEBPACK_AMD_DEFINE_ARRAY__ = [ __webpack_require__(4), __webpack_require__(20) ], __WEBPACK_AMD_DEFINE_RESULT__ = function( Utils, Options ) {\n\t\n\tvar View = Options.BaseIcons.extend({\n\t initialize: function( options ) {\n\t options.type = options.display || 'checkbox';\n\t options.multiple = ( options.type == 'checkbox' );\n\t Options.BaseIcons.prototype.initialize.call( this, options );\n\t },\n\t\n\t /** Set states for selected values */\n\t _setValue: function ( new_value ) {\n\t Options.BaseIcons.prototype._setValue.call( this, new_value );\n\t if ( new_value !== undefined && new_value !== null && this.header_index ) {\n\t var self = this;\n\t var values = $.isArray( new_value ) ? new_value : [ new_value ];\n\t _.each( values, function( v ) {\n\t var list = self.header_index[ v ];\n\t _.each( list, function( element ) {\n\t self._setState( element, true );\n\t });\n\t });\n\t }\n\t },\n\t\n\t /** Expand/collapse a sub group */\n\t _setState: function ( header_id, is_expanded ) {\n\t var $button = this.$( '.button-' + header_id );\n\t var $subgroup = this.$( '.subgroup-' + header_id );\n\t $button.data( 'is_expanded', is_expanded );\n\t if ( is_expanded ) {\n\t $subgroup.show();\n\t $button.removeClass( 'fa-plus-square' ).addClass( 'fa-minus-square' );\n\t } else {\n\t $subgroup.hide();\n\t $button.removeClass( 'fa-minus-square' ).addClass( 'fa-plus-square' );\n\t }\n\t },\n\t\n\t /** Template to create options tree */\n\t _templateOptions: function() {\n\t var self = this;\n\t this.header_index = {};\n\t\n\t // attach event handler\n\t function attach( $el, header_id ) {\n\t var $button = $el.find( '.button-' + header_id );\n\t $button.on( 'click', function() {\n\t self._setState( header_id, !$button.data( 'is_expanded' ) );\n\t });\n\t }\n\t\n\t // recursive function which iterates through options\n\t function iterate ( $tmpl, options, header ) {\n\t header = header || [];\n\t for ( i in options ) {\n\t var level = options[ i ];\n\t var has_options = level.options && level.options.length > 0;\n\t var new_header = header.slice( 0 );\n\t self.header_index[ level.value ] = new_header.slice( 0 );\n\t var $group = $( '
    ' );\n\t if ( has_options ) {\n\t var header_id = Utils.uid();\n\t var $button = $( '' ).addClass( 'button-' + header_id ).addClass( 'ui-drilldown-button fa fa-plus-square' );\n\t var $subgroup = $( '
    ' ).addClass( 'subgroup-' + header_id ).addClass( 'ui-drilldown-subgroup' );\n\t $group.append( $( '
    ' )\n\t .append( $button )\n\t .append( self._templateOption( { label: level.name, value: level.value } ) ) );\n\t new_header.push( header_id );\n\t iterate ( $subgroup, level.options, new_header );\n\t $group.append( $subgroup );\n\t attach( $group, header_id );\n\t } else {\n\t $group.append( self._templateOption( { label: level.name, value: level.value } ) );\n\t }\n\t $tmpl.append( $group );\n\t }\n\t }\n\t\n\t // iterate through options and create dom\n\t var $tmpl = $( '
    ' );\n\t iterate( $tmpl, this.model.get( 'data' ) );\n\t return $tmpl;\n\t },\n\t\n\t /** Template for drill down view */\n\t _template: function() {\n\t return $( '
    ' ).addClass( 'ui-options-list drilldown-container' ).attr( 'id', this.model.id );\n\t }\n\t});\n\t\n\treturn {\n\t View: View\n\t}\n\t\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(1), __webpack_require__(2)))\n\n/***/ },\n/* 48 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, $, _) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [ __webpack_require__(4), __webpack_require__(7), __webpack_require__(21) ], __WEBPACK_AMD_DEFINE_RESULT__ = function( Utils, Ui, Select ) {\n\t\n\t/** Batch mode variations */\n\tvar Batch = { DISABLED: 'disabled', ENABLED: 'enabled', LINKED: 'linked' };\n\t\n\t/** List of available content selectors options */\n\tvar Configurations = {\n\t data: [\n\t { src: 'hda', icon: 'fa-file-o', tooltip: 'Single dataset', multiple: false, batch: Batch.DISABLED },\n\t { src: 'hda', icon: 'fa-files-o', tooltip: 'Multiple datasets', multiple: true, batch: Batch.LINKED },\n\t { src: 'hdca', icon: 'fa-folder-o', tooltip: 'Dataset collection', multiple: false, batch: Batch.LINKED } ],\n\t data_multiple: [\n\t { src: 'hda', icon: 'fa-files-o', tooltip: 'Multiple datasets', multiple: true, batch: Batch.DISABLED },\n\t { src: 'hdca', icon: 'fa-folder-o', tooltip: 'Dataset collection', multiple: false, batch: Batch.DISABLED } ],\n\t data_collection: [\n\t { src: 'hdca', icon: 'fa-folder-o', tooltip: 'Dataset collection', multiple: false, batch: Batch.DISABLED } ],\n\t workflow_data: [\n\t { src: 'hda', icon: 'fa-file-o', tooltip: 'Single dataset', multiple: false, batch: Batch.DISABLED } ],\n\t workflow_data_multiple: [\n\t { src: 'hda', icon: 'fa-files-o', tooltip: 'Multiple datasets', multiple: true, batch: Batch.DISABLED } ],\n\t workflow_data_collection: [\n\t { src: 'hdca', icon: 'fa-folder-o', tooltip: 'Dataset collection', multiple: false, batch: Batch.DISABLED } ],\n\t module_data: [\n\t { src: 'hda', icon: 'fa-file-o', tooltip: 'Single dataset', multiple: false, batch: Batch.DISABLED },\n\t { src: 'hda', icon: 'fa-files-o', tooltip: 'Multiple datasets', multiple: true, batch: Batch.ENABLED } ],\n\t module_data_collection: [\n\t { src: 'hdca', icon: 'fa-folder-o', tooltip: 'Dataset collection', multiple: false, batch: Batch.DISABLED },\n\t { src: 'hdca', icon: 'fa-folder', tooltip: 'Multiple collections', multiple: true, batch: Batch.ENABLED } ]\n\t};\n\t\n\t/** View for hda and hdca content selector ui elements */\n\tvar View = Backbone.View.extend({\n\t initialize : function( options ) {\n\t var self = this;\n\t this.model = options && options.model || new Backbone.Model({\n\t src_labels : { 'hda' : 'dataset', 'hdca': 'dataset collection' },\n\t pagelimit : 100\n\t }).set( options );\n\t this.setElement( $( '
    ' ).addClass( 'ui-select-content' ) );\n\t this.button_product = new Ui.RadioButton.View( {\n\t value : 'false',\n\t data : [ { icon: 'fa fa-chain', value: 'false',\n\t tooltip: 'Linked inputs will be run in matched order with other datasets e.g. use this for matching forward and reverse reads.' },\n\t { icon: 'fa fa-chain-broken', value: 'true',\n\t tooltip: 'Unlinked dataset inputs will be run against *all* other inputs.' } ] } );\n\t var $batch_div = $( '
    ' ).addClass( 'ui-form-info' )\n\t .append( $( '' ).addClass( 'fa fa-sitemap' ) )\n\t .append( $( '' ).html( 'This is a batch mode input field. Separate jobs will be triggered for each dataset selection.' ) );\n\t this.$batch = {\n\t linked : $batch_div.clone(),\n\t enabled : $batch_div.clone().append( $( '
    ' )\n\t .append( $( '
    ' ).addClass( 'ui-form-title' ).html( 'Batch options:' ) )\n\t .append( this.button_product.$el ) )\n\t .append( $( '
    ' ).css( 'clear', 'both' ) )\n\t };\n\t\n\t // track current history elements\n\t this.history = {};\n\t\n\t // add listeners\n\t this.listenTo( this.model, 'change:data', this._changeData, this );\n\t this.listenTo( this.model, 'change:wait', this._changeWait, this );\n\t this.listenTo( this.model, 'change:current', this._changeCurrent, this );\n\t this.listenTo( this.model, 'change:value', this._changeValue, this );\n\t this.listenTo( this.model, 'change:type change:optional change:multiple change:extensions', this._changeType, this );\n\t this.render();\n\t\n\t // add change event\n\t this.on( 'change', function() { options.onchange && options.onchange( self.value() ) } );\n\t },\n\t\n\t render: function() {\n\t this._changeType();\n\t this._changeValue();\n\t this._changeWait();\n\t },\n\t\n\t /** Indicate that select fields are being updated */\n\t wait: function() {\n\t this.model.set( 'wait', true );\n\t },\n\t\n\t /** Indicate that the options update has been completed */\n\t unwait: function() {\n\t this.model.set( 'wait', false );\n\t },\n\t\n\t /** Update data representing selectable options */\n\t update: function( options ) {\n\t this.model.set( 'data', options );\n\t },\n\t\n\t /** Return the currently selected dataset values */\n\t value: function ( new_value ) {\n\t new_value !== undefined && this.model.set( 'value', new_value );\n\t var current = this.model.get( 'current' );\n\t if ( this.config[ current ] ) {\n\t var id_list = this.fields[ current ].value();\n\t if (id_list !== null) {\n\t id_list = $.isArray( id_list ) ? id_list : [ id_list ];\n\t if ( id_list.length > 0 ) {\n\t var result = this._batch( { values: [] } );\n\t for ( var i in id_list ) {\n\t var details = this.history[ id_list[ i ] + '_' + this.config[ current ].src ];\n\t if ( details ) {\n\t result.values.push( details );\n\t } else {\n\t Galaxy.emit.debug( 'ui-select-content::value()', 'Requested details not found for \\'' + id_list[ i ] + '\\'.' );\n\t return null;\n\t }\n\t }\n\t result.values.sort( function( a, b ) { return a.hid - b.hid } );\n\t return result;\n\t }\n\t }\n\t } else {\n\t Galaxy.emit.debug( 'ui-select-content::value()', 'Invalid value/source \\'' + new_value + '\\'.' );\n\t }\n\t return null;\n\t },\n\t\n\t /** Change of current select field */\n\t _changeCurrent: function() {\n\t var self = this;\n\t _.each( this.fields, function( field, i ) {\n\t if ( self.model.get( 'current' ) == i ) {\n\t field.$el.show();\n\t _.each( self.$batch, function( $batchfield, batchmode ) {\n\t $batchfield[ self.config[ i ].batch == batchmode ? 'show' : 'hide' ]();\n\t });\n\t self.button_type.value( i );\n\t } else {\n\t field.$el.hide();\n\t }\n\t });\n\t },\n\t\n\t /** Change of type */\n\t _changeType: function() {\n\t var self = this;\n\t\n\t // identify selector type identifier i.e. [ flavor ]_[ type ]_[ multiple ]\n\t var config_id = ( this.model.get( 'flavor' ) ? this.model.get( 'flavor' ) + '_' : '' ) +\n\t String( this.model.get( 'type' ) ) + ( this.model.get( 'multiple' ) ? '_multiple' : '' );\n\t if ( Configurations[ config_id ] ) {\n\t this.config = Configurations[ config_id ];\n\t } else {\n\t this.config = Configurations[ 'data' ];\n\t Galaxy.emit.debug( 'ui-select-content::_changeType()', 'Invalid configuration/type id \\'' + config_id + '\\'.' );\n\t }\n\t\n\t // prepare extension component of error message\n\t var data = self.model.get( 'data' );\n\t var extensions = Utils.textify( this.model.get( 'extensions' ) );\n\t var src_labels = this.model.get( 'src_labels' );\n\t\n\t // build views\n\t this.fields = [];\n\t this.button_data = [];\n\t _.each( this.config, function( c, i ) {\n\t self.button_data.push({\n\t value : i,\n\t icon : c.icon,\n\t tooltip : c.tooltip\n\t });\n\t self.fields.push(\n\t new Select.View({\n\t optional : self.model.get( 'optional' ),\n\t multiple : c.multiple,\n\t searchable : !c.multiple || ( data && data[ c.src ] && data[ c.src ].length > self.model.get( 'pagelimit' ) ),\n\t selectall : false,\n\t error_text : 'No ' + ( extensions ? extensions + ' ' : '' ) + ( src_labels[ c.src ] || 'content' ) + ' available.',\n\t onchange : function() {\n\t self.trigger( 'change' );\n\t }\n\t })\n\t );\n\t });\n\t this.button_type = new Ui.RadioButton.View({\n\t value : this.model.get( 'current' ),\n\t data : this.button_data,\n\t onchange: function( value ) {\n\t self.model.set( 'current', value );\n\t self.trigger( 'change' );\n\t }\n\t });\n\t\n\t // append views\n\t this.$el.empty();\n\t var button_width = 0;\n\t if ( this.fields.length > 1 ) {\n\t this.$el.append( this.button_type.$el );\n\t button_width = Math.max( 0, this.fields.length * 36 ) + 'px';\n\t }\n\t _.each( this.fields, function( field ) {\n\t self.$el.append( field.$el.css( { 'margin-left': button_width } ) );\n\t });\n\t _.each( this.$batch, function( $batchfield, batchmode ) {\n\t self.$el.append( $batchfield.css( { 'margin-left': button_width } ) );\n\t });\n\t this.model.set( 'current', 0 );\n\t this._changeCurrent();\n\t this._changeData();\n\t },\n\t\n\t /** Change of wait flag */\n\t _changeWait: function() {\n\t var self = this;\n\t _.each( this.fields, function( field ) { field[ self.model.get( 'wait' ) ? 'wait' : 'unwait' ]() } );\n\t },\n\t\n\t /** Change of available options */\n\t _changeData: function() {\n\t var options = this.model.get( 'data' );\n\t var self = this;\n\t var select_options = {};\n\t _.each( options, function( items, src ) {\n\t select_options[ src ] = [];\n\t _.each( items, function( item ) {\n\t select_options[ src ].push({\n\t hid : item.hid,\n\t keep : item.keep,\n\t label: item.hid + ': ' + item.name,\n\t value: item.id\n\t });\n\t self.history[ item.id + '_' + src ] = item;\n\t });\n\t });\n\t _.each( this.config, function( c, i ) {\n\t select_options[ c.src ] && self.fields[ i ].add( select_options[ c.src ], function( a, b ) { return b.hid - a.hid } );\n\t });\n\t },\n\t\n\t /** Change of incoming value */\n\t _changeValue: function () {\n\t var new_value = this.model.get( 'value' );\n\t if ( new_value && new_value.values && new_value.values.length > 0 ) {\n\t // create list with content ids\n\t var list = [];\n\t _.each( new_value.values, function( value ) {\n\t list.push( value.id );\n\t });\n\t // sniff first suitable field type from config list\n\t var src = new_value.values[ 0 ].src;\n\t var multiple = new_value.values.length > 1;\n\t for( var i = 0; i < this.config.length; i++ ) {\n\t var field = this.fields[ i ];\n\t var c = this.config[ i ];\n\t if ( c.src == src && [ multiple, true ].indexOf( c.multiple ) !== -1 ) {\n\t this.model.set( 'current', i );\n\t field.value( list );\n\t break;\n\t }\n\t }\n\t } else {\n\t _.each( this.fields, function( field ) {\n\t field.value( null );\n\t });\n\t }\n\t },\n\t\n\t /** Assists in identifying the batch mode */\n\t _batch: function( result ) {\n\t result[ 'batch' ] = false;\n\t var current = this.model.get( 'current' );\n\t var config = this.config[ current ];\n\t if ( config.src == 'hdca' && !config.multiple ) {\n\t var hdca = this.history[ this.fields[ current ].value() + '_hdca' ];\n\t if ( hdca && hdca.map_over_type ) {\n\t result[ 'batch' ] = true;\n\t }\n\t }\n\t if ( config.batch == Batch.LINKED || config.batch == Batch.ENABLED ) {\n\t result[ 'batch' ] = true;\n\t if ( config.batch == Batch.ENABLED && this.button_product.value() === 'true' ) {\n\t result[ 'product' ] = true;\n\t }\n\t }\n\t return result;\n\t }\n\t});\n\t\n\treturn {\n\t View: View\n\t}\n\t\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(1), __webpack_require__(2)))\n\n/***/ },\n/* 49 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone) {// dependencies\n\t!(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(4), __webpack_require__(19)], __WEBPACK_AMD_DEFINE_RESULT__ = function(Utils, List) {\n\t\n\t/**\n\t * FTP file selector\n\t */\n\tvar View = Backbone.View.extend({\n\t // initialize\n\t initialize : function(options) {\n\t // link this\n\t var self = this;\n\t\n\t // create ui-list view to keep track of selected ftp files\n\t this.ftpfile_list = new List.View({\n\t name : 'file',\n\t optional : options.optional,\n\t multiple : options.multiple,\n\t onchange : function() {\n\t options.onchange && options.onchange(self.value());\n\t }\n\t });\n\t\n\t // create elements\n\t this.setElement(this.ftpfile_list.$el);\n\t\n\t // initial fetch of ftps\n\t Utils.get({\n\t url : Galaxy.root + 'api/remote_files',\n\t success : function(response) {\n\t var data = [];\n\t for (var i in response) {\n\t data.push({\n\t value : response[i]['path'],\n\t label : response[i]['path']\n\t });\n\t }\n\t self.ftpfile_list.update(data);\n\t }\n\t });\n\t },\n\t\n\t /** Return/Set currently selected ftp datasets */\n\t value: function(val) {\n\t return this.ftpfile_list.value(val);\n\t }\n\t});\n\t\n\treturn {\n\t View: View\n\t}\n\t\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3)))\n\n/***/ },\n/* 50 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone) {// dependencies\n\t!(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(4), __webpack_require__(7), __webpack_require__(52), __webpack_require__(19)], __WEBPACK_AMD_DEFINE_RESULT__ = function(Utils, Ui, Table, List) {\n\t\n\t// collection of libraries\n\tvar Libraries = Backbone.Collection.extend({\n\t url: Galaxy.root + 'api/libraries?deleted=false'\n\t});\n\t\n\t// collection of dataset\n\tvar LibraryDatasets = Backbone.Collection.extend({\n\t initialize: function() {\n\t var self = this;\n\t this.config = new Backbone.Model({ library_id: null });\n\t this.config.on('change', function() {\n\t self.fetch({ reset: true });\n\t });\n\t },\n\t url: function() {\n\t return Galaxy.root + 'api/libraries/' + this.config.get('library_id') + '/contents';\n\t }\n\t});\n\t\n\t// hda/hdca content selector ui element\n\tvar View = Backbone.View.extend({\n\t // initialize\n\t initialize : function(options) {\n\t // link this\n\t var self = this;\n\t\n\t // collections\n\t this.libraries = new Libraries();\n\t this.datasets = new LibraryDatasets();\n\t\n\t // link app and options\n\t this.options = options;\n\t\n\t // select field for the library\n\t // TODO: Remove this once the library API supports searching for library datasets\n\t this.library_select = new Ui.Select.View({\n\t onchange : function(value) {\n\t self.datasets.config.set('library_id', value);\n\t }\n\t });\n\t\n\t // create ui-list view to keep track of selected data libraries\n\t this.dataset_list = new List.View({\n\t name : 'dataset',\n\t optional : options.optional,\n\t multiple : options.multiple,\n\t onchange : function() {\n\t self.trigger('change');\n\t }\n\t });\n\t\n\t // add reset handler for fetched libraries\n\t this.libraries.on('reset', function() {\n\t var data = [];\n\t self.libraries.each(function(model) {\n\t data.push({\n\t value : model.id,\n\t label : model.get('name')\n\t });\n\t });\n\t self.library_select.update(data);\n\t });\n\t\n\t // add reset handler for fetched library datasets\n\t this.datasets.on('reset', function() {\n\t var data = [];\n\t var library_current = self.library_select.text();\n\t if (library_current !== null) {\n\t self.datasets.each(function(model) {\n\t if (model.get('type') === 'file') {\n\t data.push({\n\t value : model.id,\n\t label : model.get('name')\n\t });\n\t }\n\t });\n\t }\n\t self.dataset_list.update(data);\n\t });\n\t\n\t // add change event. fires on trigger\n\t this.on('change', function() {\n\t options.onchange && options.onchange(self.value());\n\t });\n\t\n\t // create elements\n\t this.setElement(this._template());\n\t this.$('.library-select').append(this.library_select.$el);\n\t this.$el.append(this.dataset_list.$el);\n\t\n\t // initial fetch of libraries\n\t this.libraries.fetch({\n\t reset: true,\n\t success: function() {\n\t self.library_select.trigger('change');\n\t if (self.options.value !== undefined) {\n\t self.value(self.options.value);\n\t }\n\t }\n\t });\n\t },\n\t\n\t /** Return/Set currently selected library datasets */\n\t value: function(val) {\n\t return this.dataset_list.value(val);\n\t },\n\t\n\t /** Template */\n\t _template: function() {\n\t return '
    ' +\n\t '
    ' +\n\t 'Select Library' +\n\t '' +\n\t '
    ' +\n\t '
    ';\n\t }\n\t});\n\t\n\treturn {\n\t View: View\n\t}\n\t\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3)))\n\n/***/ },\n/* 51 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, $) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [ __webpack_require__(4) ], __WEBPACK_AMD_DEFINE_RESULT__ = function( Utils ) {\n\tvar View = Backbone.View.extend({\n\t initialize : function( options ) {\n\t var self = this;\n\t this.options = Utils.merge( options, {\n\t id : Utils.uid(),\n\t min : null,\n\t max : null,\n\t step : null,\n\t precise : false,\n\t split : 10000\n\t } );\n\t\n\t // create new element\n\t this.setElement( this._template( this.options ) );\n\t\n\t // determine wether to use the slider\n\t this.useslider = this.options.max !== null && this.options.min !== null && this.options.max > this.options.min;\n\t\n\t // set default step size\n\t if ( this.options.step === null ) {\n\t this.options.step = 1.0;\n\t if ( this.options.precise && this.useslider ) {\n\t this.options.step = ( this.options.max - this.options.min ) / this.options.split;\n\t }\n\t }\n\t\n\t // create slider if min and max are defined properly\n\t if ( this.useslider ) {\n\t this.$slider = this.$( '#slider' );\n\t this.$slider.slider( this.options );\n\t this.$slider.on( 'slide', function ( event, ui ) {\n\t self.value( ui.value );\n\t });\n\t } else {\n\t this.$( '.ui-form-slider-text' ).css( 'width', '100%' );\n\t }\n\t\n\t // link text input field\n\t this.$text = this.$( '#text' );\n\t\n\t // set initial value\n\t this.options.value !== undefined && ( this.value( this.options.value ) );\n\t\n\t // add text field event\n\t var pressed = [];\n\t this.$text.on( 'change', function () {\n\t self.value( $( this ).val() );\n\t });\n\t this.$text.on( 'keyup', function( e ) {\n\t pressed[e.which] = false;\n\t self.options.onchange && self.options.onchange( $( this ).val() );\n\t });\n\t this.$text.on( 'keydown', function ( e ) {\n\t var v = e.which;\n\t pressed[ v ] = true;\n\t if ( self.options.is_workflow && pressed[ 16 ] && v == 52 ) {\n\t self.value( '$' )\n\t event.preventDefault();\n\t } else if (!( v == 8 || v == 9 || v == 13 || v == 37 || v == 39 || ( v >= 48 && v <= 57 && !pressed[ 16 ] ) || ( v >= 96 && v <= 105 )\n\t || ( ( v == 190 || v == 110 ) && $( this ).val().indexOf( '.' ) == -1 && self.options.precise )\n\t || ( ( v == 189 || v == 109 ) && $( this ).val().indexOf( '-' ) == -1 )\n\t || self._isParameter( $( this ).val() )\n\t || pressed[ 91 ] || pressed[ 17 ] ) ) {\n\t event.preventDefault();\n\t }\n\t });\n\t },\n\t\n\t /** Set and Return the current value\n\t */\n\t value : function ( new_val ) {\n\t if ( new_val !== undefined ) {\n\t if ( new_val !== null && new_val !== '' && !this._isParameter( new_val ) ) {\n\t isNaN( new_val ) && ( new_val = 0 );\n\t this.options.max !== null && ( new_val = Math.min( new_val, this.options.max ) );\n\t this.options.min !== null && ( new_val = Math.max( new_val, this.options.min ) );\n\t }\n\t this.$slider && this.$slider.slider( 'value', new_val );\n\t this.$text.val( new_val );\n\t this.options.onchange && this.options.onchange( new_val );\n\t }\n\t return this.$text.val();\n\t },\n\t\n\t /** Return true if the field contains a workflow parameter i.e. $('name')\n\t */\n\t _isParameter: function( value ) {\n\t return this.options.is_workflow && String( value ).substring( 0, 1 ) === '$';\n\t },\n\t\n\t /** Slider template\n\t */\n\t _template: function( options ) {\n\t return '
    ' +\n\t '' +\n\t '
    ' +\n\t '
    ';\n\t }\n\t});\n\t\n\treturn {\n\t View : View\n\t};\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(1)))\n\n/***/ },\n/* 52 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, $) {// dependencies\n\t!(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(4)], __WEBPACK_AMD_DEFINE_RESULT__ = function(Utils) {\n\t\n\t/**\n\t * This class creates a ui table element.\n\t */\n\tvar View = Backbone.View.extend({\n\t // current row\n\t row: null,\n\t \n\t // count rows\n\t row_count: 0,\n\t \n\t // defaults options\n\t optionsDefault: {\n\t content : 'No content available.',\n\t onchange : null,\n\t ondblclick : null,\n\t onconfirm : null,\n\t cls : 'ui-table',\n\t cls_tr : ''\n\t },\n\t \n\t // events\n\t events : {\n\t 'click' : '_onclick',\n\t 'dblclick' : '_ondblclick'\n\t },\n\t \n\t // initialize\n\t initialize : function(options) {\n\t // configure options\n\t this.options = Utils.merge(options, this.optionsDefault);\n\t \n\t // create new element\n\t var $el = $(this._template(this.options));\n\t \n\t // link sub-elements\n\t this.$thead = $el.find('thead');\n\t this.$tbody = $el.find('tbody');\n\t this.$tmessage = $el.find('tmessage');\n\t \n\t // set element\n\t this.setElement($el);\n\t \n\t // initialize row\n\t this.row = this._row();\n\t },\n\t \n\t // add header cell\n\t addHeader: function($el) {\n\t var wrapper = $('
    ' +\n\t '' +\n\t '' +\n\t '
    ' +\n\t '' + options.content + '' +\n\t '
    ';\n\t }\n\t});\n\t\n\treturn {\n\t View: View\n\t}\n\t\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(1)))\n\n/***/ },\n/* 53 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_RESULT__ = function() {\n\t var Model = Backbone.Model.extend({\n\t defaults: {\n\t extension : 'auto',\n\t genome : '?',\n\t url_paste : '',\n\t status : 'init',\n\t info : null,\n\t file_name : '',\n\t file_mode : '',\n\t file_size : 0,\n\t file_type : null,\n\t file_path : '',\n\t file_data : null,\n\t percentage : 0,\n\t space_to_tab : false,\n\t to_posix_lines : true,\n\t enabled : true\n\t },\n\t reset: function( attr ) {\n\t this.clear().set( this.defaults ).set( attr );\n\t }\n\t });\n\t var Collection = Backbone.Collection.extend( { model: Model } );\n\t return { Model: Model, Collection : Collection };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3)))\n\n/***/ },\n/* 54 */,\n/* 55 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, $) {/**\n\t * This class defines a queue to ensure that multiple deferred callbacks are executed sequentially.\n\t */\n\t!(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(4)], __WEBPACK_AMD_DEFINE_RESULT__ = function( Utils ) {\n\treturn Backbone.Model.extend({\n\t initialize: function(){\n\t this.active = {};\n\t this.last = null;\n\t },\n\t\n\t /** Adds a callback to the queue. Upon execution a deferred object is parsed to the callback i.e. callback( deferred ).\n\t * If the callback does not take any arguments, the deferred is resolved instantly.\n\t */\n\t execute: function( callback ) {\n\t var self = this;\n\t var id = Utils.uid();\n\t var has_deferred = callback.length > 0;\n\t\n\t // register process\n\t this.active[ id ] = true;\n\t\n\t // deferred process\n\t var process = $.Deferred();\n\t process.promise().always(function() {\n\t delete self.active[ id ];\n\t has_deferred && Galaxy.emit.debug( 'deferred::execute()', this.state().charAt(0).toUpperCase() + this.state().slice(1) + ' ' + id );\n\t });\n\t\n\t // deferred queue\n\t $.when( this.last ).always(function() {\n\t if ( self.active[ id ] ) {\n\t has_deferred && Galaxy.emit.debug( 'deferred::execute()', 'Running ' + id );\n\t callback( process );\n\t !has_deferred && process.resolve();\n\t } else {\n\t process.reject();\n\t }\n\t });\n\t this.last = process.promise();\n\t },\n\t\n\t /** Resets the promise queue. All currently queued but unexecuted callbacks/promises will be rejected.\n\t */\n\t reset: function() {\n\t Galaxy.emit.debug('deferred::execute()', 'Reset');\n\t for ( var i in this.active ) {\n\t this.active[ i ] = false;\n\t }\n\t },\n\t\n\t /** Returns true if all processes are done.\n\t */\n\t ready: function() {\n\t return $.isEmptyObject( this.active );\n\t }\n\t});\n\t\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(1)))\n\n/***/ },\n/* 56 */,\n/* 57 */,\n/* 58 */,\n/* 59 */,\n/* 60 */,\n/* 61 */,\n/* 62 */,\n/* 63 */,\n/* 64 */,\n/* 65 */,\n/* 66 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, _) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(6),\n\t __webpack_require__(5),\n\t __webpack_require__(15),\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( baseMVC, _l ){\n\t// =============================================================================\n\t/** A view on any model that has a 'annotation' attribute\n\t */\n\tvar AnnotationEditor = Backbone.View\n\t .extend( baseMVC.LoggableMixin )\n\t .extend( baseMVC.HiddenUntilActivatedViewMixin ).extend({\n\t\n\t tagName : 'div',\n\t className : 'annotation-display',\n\t\n\t /** Set up listeners, parse options */\n\t initialize : function( options ){\n\t options = options || {};\n\t this.tooltipConfig = options.tooltipConfig || { placement: 'bottom' };\n\t //console.debug( this, options );\n\t // only listen to the model only for changes to annotations\n\t this.listenTo( this.model, 'change:annotation', function(){\n\t this.render();\n\t });\n\t this.hiddenUntilActivated( options.$activator, options );\n\t },\n\t\n\t /** Build the DOM elements, call select to on the created input, and set up behaviors */\n\t render : function(){\n\t var view = this;\n\t this.$el.html( this._template() );\n\t\n\t //TODO: handle empties better\n\t this.$annotation().make_text_editable({\n\t use_textarea: true,\n\t on_finish: function( newAnnotation ){\n\t view.$annotation().text( newAnnotation );\n\t view.model.save({ annotation: newAnnotation }, { silent: true })\n\t .fail( function(){\n\t view.$annotation().text( view.model.previous( 'annotation' ) );\n\t });\n\t }\n\t });\n\t return this;\n\t },\n\t\n\t /** @returns {String} the html text used to build the view's DOM */\n\t _template : function(){\n\t var annotation = this.model.get( 'annotation' );\n\t return [\n\t //TODO: make prompt optional\n\t '',\n\t // set up initial tags by adding as CSV to input vals (necc. to init select2)\n\t '
    ',\n\t _.escape( annotation ),\n\t '
    '\n\t ].join( '' );\n\t },\n\t\n\t /** @returns {jQuery} the main element for this view */\n\t $annotation : function(){\n\t return this.$el.find( '.annotation' );\n\t },\n\t\n\t /** shut down event listeners and remove this view's DOM */\n\t remove : function(){\n\t this.$annotation.off();\n\t this.stopListening( this.model );\n\t Backbone.View.prototype.remove.call( this );\n\t },\n\t\n\t /** string rep */\n\t toString : function(){ return [ 'AnnotationEditor(', this.model + '', ')' ].join(''); }\n\t});\n\t// =============================================================================\n\treturn {\n\t AnnotationEditor : AnnotationEditor\n\t};\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(2)))\n\n/***/ },\n/* 67 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(jQuery) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(2),\n\t __webpack_require__(3),\n\t __webpack_require__(6),\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( _, Backbone, BASE_MVC ){\n\t'use strict';\n\t\n\t//=============================================================================\n\t/**\n\t * A Collection that can be limited/offset/re-ordered/filtered.\n\t * @type {Backbone.Collection}\n\t */\n\tvar ControlledFetchCollection = Backbone.Collection.extend({\n\t\n\t /** call setOrder on initialization to build the comparator based on options */\n\t initialize : function( models, options ){\n\t Backbone.Collection.prototype.initialize.call( this, models, options );\n\t this.setOrder( options.order || this.order, { silent: true });\n\t },\n\t\n\t /** set up to track order changes and re-sort when changed */\n\t _setUpListeners : function(){\n\t return this.on({\n\t 'changed-order' : this.sort\n\t });\n\t },\n\t\n\t /** override to provide order and offsets based on instance vars, set limit if passed,\n\t * and set allFetched/fire 'all-fetched' when xhr returns\n\t */\n\t fetch : function( options ){\n\t options = this._buildFetchOptions( options );\n\t // console.log( 'fetch options:', options );\n\t return Backbone.Collection.prototype.fetch.call( this, options );\n\t },\n\t\n\t /** build ajax data/parameters from options */\n\t _buildFetchOptions : function( options ){\n\t // note: we normally want options passed in to override the defaults built here\n\t // so most of these fns will generate defaults\n\t options = _.clone( options ) || {};\n\t var self = this;\n\t\n\t // jquery ajax option; allows multiple q/qv for filters (instead of 'q[]')\n\t options.traditional = true;\n\t\n\t // options.data\n\t // we keep limit, offset, etc. in options *as well as move it into data* because:\n\t // - it makes fetch calling convenient to add it to a single options map (instead of as mult. args)\n\t // - it allows the std. event handlers (for fetch, etc.) to have access\n\t // to the pagination options too\n\t // (i.e. this.on( 'sync', function( options ){ if( options.limit ){ ... } }))\n\t // however, when we send to xhr/jquery we copy them to data also so that they become API query params\n\t options.data = options.data || self._buildFetchData( options );\n\t // console.log( 'data:', options.data );\n\t\n\t // options.data.filters --> options.data.q, options.data.qv\n\t var filters = this._buildFetchFilters( options );\n\t // console.log( 'filters:', filters );\n\t if( !_.isEmpty( filters ) ){\n\t _.extend( options.data, this._fetchFiltersToAjaxData( filters ) );\n\t }\n\t // console.log( 'data:', options.data );\n\t return options;\n\t },\n\t\n\t /** Build the dictionary to send to fetch's XHR as data */\n\t _buildFetchData : function( options ){\n\t var defaults = {};\n\t if( this.order ){ defaults.order = this.order; }\n\t return _.defaults( _.pick( options, this._fetchParams ), defaults );\n\t },\n\t\n\t /** These attribute keys are valid params to fetch/API-index */\n\t _fetchParams : [\n\t /** model dependent string to control the order of models returned */\n\t 'order',\n\t /** limit the number of models returned from a fetch */\n\t 'limit',\n\t /** skip this number of models when fetching */\n\t 'offset',\n\t /** what series of attributes to return (model dependent) */\n\t 'view',\n\t /** individual keys to return for the models (see api/histories.index) */\n\t 'keys'\n\t ],\n\t\n\t /** add any needed filters here based on collection state */\n\t _buildFetchFilters : function( options ){\n\t // override\n\t return _.clone( options.filters || {} );\n\t },\n\t\n\t /** Convert dictionary filters to qqv style arrays */\n\t _fetchFiltersToAjaxData : function( filters ){\n\t // return as a map so ajax.data can extend from it\n\t var filterMap = {\n\t q : [],\n\t qv : []\n\t };\n\t _.each( filters, function( v, k ){\n\t // don't send if filter value is empty\n\t if( v === undefined || v === '' ){ return; }\n\t // json to python\n\t if( v === true ){ v = 'True'; }\n\t if( v === false ){ v = 'False'; }\n\t if( v === null ){ v = 'None'; }\n\t // map to k/v arrays (q/qv)\n\t filterMap.q.push( k );\n\t filterMap.qv.push( v );\n\t });\n\t return filterMap;\n\t },\n\t\n\t /** override to reset allFetched flag to false */\n\t reset : function( models, options ){\n\t this.allFetched = false;\n\t return Backbone.Collection.prototype.reset.call( this, models, options );\n\t },\n\t\n\t // ........................................................................ order\n\t order : null,\n\t\n\t /** @type {Object} map of collection available sorting orders containing comparator fns */\n\t comparators : {\n\t 'update_time' : BASE_MVC.buildComparator( 'update_time', { ascending: false }),\n\t 'update_time-asc' : BASE_MVC.buildComparator( 'update_time', { ascending: true }),\n\t 'create_time' : BASE_MVC.buildComparator( 'create_time', { ascending: false }),\n\t 'create_time-asc' : BASE_MVC.buildComparator( 'create_time', { ascending: true }),\n\t },\n\t\n\t /** set the order and comparator for this collection then sort with the new order\n\t * @event 'changed-order' passed the new order and the collection\n\t */\n\t setOrder : function( order, options ){\n\t options = options || {};\n\t var collection = this;\n\t var comparator = collection.comparators[ order ];\n\t if( _.isUndefined( comparator ) ){ throw new Error( 'unknown order: ' + order ); }\n\t // if( _.isUndefined( comparator ) ){ return; }\n\t if( comparator === collection.comparator ){ return; }\n\t\n\t var oldOrder = collection.order;\n\t collection.order = order;\n\t collection.comparator = comparator;\n\t\n\t if( !options.silent ){\n\t collection.trigger( 'changed-order', options );\n\t }\n\t return collection;\n\t },\n\t\n\t});\n\t\n\t\n\t//=============================================================================\n\t/**\n\t *\n\t */\n\tvar PaginatedCollection = ControlledFetchCollection.extend({\n\t\n\t /** @type {Number} limit used for each page's fetch */\n\t limitPerPage : 500,\n\t\n\t initialize : function( models, options ){\n\t ControlledFetchCollection.prototype.initialize.call( this, models, options );\n\t this.currentPage = options.currentPage || 0;\n\t },\n\t\n\t getTotalItemCount : function(){\n\t return this.length;\n\t },\n\t\n\t shouldPaginate : function(){\n\t return this.getTotalItemCount() >= this.limitPerPage;\n\t },\n\t\n\t getLastPage : function(){\n\t return Math.floor( this.getTotalItemCount() / this.limitPerPage );\n\t },\n\t\n\t getPageCount : function(){\n\t return this.getLastPage() + 1;\n\t },\n\t\n\t getPageLimitOffset : function( pageNum ){\n\t pageNum = this.constrainPageNum( pageNum );\n\t return {\n\t limit : this.limitPerPage,\n\t offset: pageNum * this.limitPerPage\n\t };\n\t },\n\t\n\t constrainPageNum : function( pageNum ){\n\t return Math.max( 0, Math.min( pageNum, this.getLastPage() ));\n\t },\n\t\n\t /** fetch the next page of data */\n\t fetchPage : function( pageNum, options ){\n\t var self = this;\n\t pageNum = self.constrainPageNum( pageNum );\n\t self.currentPage = pageNum;\n\t options = _.defaults( options || {}, self.getPageLimitOffset( pageNum ) );\n\t\n\t self.trigger( 'fetching-more' );\n\t return self.fetch( options )\n\t .always( function(){\n\t self.trigger( 'fetching-more-done' );\n\t });\n\t },\n\t\n\t fetchCurrentPage : function( options ){\n\t return this.fetchPage( this.currentPage, options );\n\t },\n\t\n\t fetchPrevPage : function( options ){\n\t return this.fetchPage( this.currentPage - 1, options );\n\t },\n\t\n\t fetchNextPage : function( options ){\n\t return this.fetchPage( this.currentPage + 1, options );\n\t },\n\t});\n\t\n\t\n\t//=============================================================================\n\t/**\n\t * A Collection that will load more elements without reseting.\n\t */\n\tvar InfinitelyScrollingCollection = ControlledFetchCollection.extend({\n\t\n\t /** @type {Number} limit used for the first fetch (or a reset) */\n\t limitOnFirstFetch : null,\n\t /** @type {Number} limit used for each subsequent fetch */\n\t limitPerFetch : 100,\n\t\n\t initialize : function( models, options ){\n\t ControlledFetchCollection.prototype.initialize.call( this, models, options );\n\t /** @type {Integer} number of contents to return from the first fetch */\n\t this.limitOnFirstFetch = options.limitOnFirstFetch || this.limitOnFirstFetch;\n\t /** @type {Integer} limit for every fetch after the first */\n\t this.limitPerFetch = options.limitPerFetch || this.limitPerFetch;\n\t /** @type {Boolean} are all contents fetched? */\n\t this.allFetched = false;\n\t /** @type {Integer} what was the offset of the last content returned */\n\t this.lastFetched = options.lastFetched || 0;\n\t },\n\t\n\t /** build ajax data/parameters from options */\n\t _buildFetchOptions : function( options ){\n\t // options (options for backbone.fetch and jquery.ajax generally)\n\t // backbone option; false here to make fetching an addititive process\n\t options.remove = options.remove || false;\n\t return ControlledFetchCollection.prototype._buildFetchOptions.call( this, options );\n\t },\n\t\n\t /** fetch the first 'page' of data */\n\t fetchFirst : function( options ){\n\t // console.log( 'ControlledFetchCollection.fetchFirst:', options );\n\t options = options? _.clone( options ) : {};\n\t this.allFetched = false;\n\t this.lastFetched = 0;\n\t return this.fetchMore( _.defaults( options, {\n\t reset : true,\n\t limit : this.limitOnFirstFetch,\n\t }));\n\t },\n\t\n\t /** fetch the next page of data */\n\t fetchMore : function( options ){\n\t // console.log( 'ControlledFetchCollection.fetchMore:', options );\n\t options = _.clone( options || {} );\n\t var collection = this;\n\t\n\t // console.log( 'fetchMore, options.reset:', options.reset );\n\t if( ( !options.reset && collection.allFetched ) ){\n\t return jQuery.when();\n\t }\n\t\n\t // TODO: this fails in the edge case where\n\t // the first fetch offset === limit (limit 4, offset 4, collection.length 4)\n\t options.offset = options.reset? 0 : ( options.offset || collection.lastFetched );\n\t var limit = options.limit = options.limit || collection.limitPerFetch || null;\n\t // console.log( 'fetchMore, limit:', limit, 'offset:', options.offset );\n\t\n\t collection.trigger( 'fetching-more' );\n\t return collection.fetch( options )\n\t .always( function(){\n\t collection.trigger( 'fetching-more-done' );\n\t })\n\t // maintain allFetched flag and trigger if all were fetched this time\n\t .done( function _postFetchMore( fetchedData ){\n\t var numFetched = _.isArray( fetchedData )? fetchedData.length : 0;\n\t collection.lastFetched += numFetched;\n\t // console.log( 'fetchMore, lastFetched:', collection.lastFetched );\n\t // anything less than a full page means we got all there is to get\n\t if( !limit || numFetched < limit ){\n\t collection.allFetched = true;\n\t collection.trigger( 'all-fetched', this );\n\t }\n\t }\n\t );\n\t },\n\t\n\t /** fetch all the collection */\n\t fetchAll : function( options ){\n\t // whitelist options to prevent allowing limit/offset/filters\n\t // (use vanilla fetch instead)\n\t options = options || {};\n\t var self = this;\n\t options = _.pick( options, 'silent' );\n\t options.filters = {};\n\t return self.fetch( options ).done( function( fetchData ){\n\t self.allFetched = true;\n\t self.trigger( 'all-fetched', self );\n\t });\n\t },\n\t});\n\t\n\t\n\t//==============================================================================\n\t return {\n\t ControlledFetchCollection : ControlledFetchCollection,\n\t PaginatedCollection : PaginatedCollection,\n\t InfinitelyScrollingCollection : InfinitelyScrollingCollection,\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(1)))\n\n/***/ },\n/* 68 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(_) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(76),\n\t __webpack_require__(30),\n\t __webpack_require__(29),\n\t __webpack_require__(6),\n\t __webpack_require__(5)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( LIST_VIEW, DC_MODEL, DC_LI, BASE_MVC, _l ){\n\t\n\t'use strict';\n\t\n\tvar logNamespace = 'collections';\n\t/* =============================================================================\n\tTODO:\n\t\n\t============================================================================= */\n\t/** @class non-editable, read-only View/Controller for a dataset collection.\n\t */\n\tvar _super = LIST_VIEW.ModelListPanel;\n\tvar CollectionView = _super.extend(\n\t/** @lends CollectionView.prototype */{\n\t //MODEL is either a DatasetCollection (or subclass) or a DatasetCollectionElement (list of pairs)\n\t _logNamespace : logNamespace,\n\t\n\t className : _super.prototype.className + ' dataset-collection-panel',\n\t\n\t /** sub view class used for datasets */\n\t DatasetDCEViewClass : DC_LI.DatasetDCEListItemView,\n\t /** sub view class used for nested collections */\n\t NestedDCDCEViewClass: DC_LI.NestedDCDCEListItemView,\n\t /** key of attribute in model to assign to this.collection */\n\t modelCollectionKey : 'elements',\n\t\n\t // ......................................................................... SET UP\n\t /** Set up the view, set up storage, bind listeners to HistoryContents events\n\t * @param {Object} attributes optional settings for the panel\n\t */\n\t initialize : function( attributes ){\n\t _super.prototype.initialize.call( this, attributes );\n\t this.linkTarget = attributes.linkTarget || '_blank';\n\t\n\t this.hasUser = attributes.hasUser;\n\t /** A stack of panels that currently cover or hide this panel */\n\t this.panelStack = [];\n\t /** The text of the link to go back to the panel containing this one */\n\t this.parentName = attributes.parentName;\n\t /** foldout or drilldown */\n\t this.foldoutStyle = attributes.foldoutStyle || 'foldout';\n\t },\n\t\n\t _queueNewRender : function( $newRender, speed ) {\n\t speed = ( speed === undefined )?( this.fxSpeed ):( speed );\n\t var panel = this;\n\t panel.log( '_queueNewRender:', $newRender, speed );\n\t\n\t // TODO: jquery@1.12 doesn't change display when the elem has display: flex\n\t // this causes display: block for those elems after the use of show/hide animations\n\t // animations are removed from this view for now until fixed\n\t panel._swapNewRender( $newRender );\n\t panel.trigger( 'rendered', panel );\n\t },\n\t\n\t // ------------------------------------------------------------------------ sub-views\n\t /** In this override, use model.getVisibleContents */\n\t _filterCollection : function(){\n\t //TODO: should *not* be model.getVisibleContents - visibility is not model related\n\t return this.model.getVisibleContents();\n\t },\n\t\n\t /** override to return proper view class based on element_type */\n\t _getItemViewClass : function( model ){\n\t //this.debug( this + '._getItemViewClass:', model );\n\t //TODO: subclasses use DCEViewClass - but are currently unused - decide\n\t switch( model.get( 'element_type' ) ){\n\t case 'hda':\n\t return this.DatasetDCEViewClass;\n\t case 'dataset_collection':\n\t return this.NestedDCDCEViewClass;\n\t }\n\t throw new TypeError( 'Unknown element type:', model.get( 'element_type' ) );\n\t },\n\t\n\t /** override to add link target and anon */\n\t _getItemViewOptions : function( model ){\n\t var options = _super.prototype._getItemViewOptions.call( this, model );\n\t return _.extend( options, {\n\t linkTarget : this.linkTarget,\n\t hasUser : this.hasUser,\n\t //TODO: could move to only nested: list:paired\n\t foldoutStyle : this.foldoutStyle\n\t });\n\t },\n\t\n\t // ------------------------------------------------------------------------ collection sub-views\n\t /** In this override, add/remove expanded/collapsed model ids to/from web storage */\n\t _setUpItemViewListeners : function( view ){\n\t var panel = this;\n\t _super.prototype._setUpItemViewListeners.call( panel, view );\n\t\n\t // use pub-sub to: handle drilldown expansion and collapse\n\t panel.listenTo( view, {\n\t 'expanded:drilldown': function( v, drilldown ){\n\t this._expandDrilldownPanel( drilldown );\n\t },\n\t 'collapsed:drilldown': function( v, drilldown ){\n\t this._collapseDrilldownPanel( drilldown );\n\t }\n\t });\n\t return this;\n\t },\n\t\n\t /** Handle drill down by hiding this panels list and controls and showing the sub-panel */\n\t _expandDrilldownPanel : function( drilldown ){\n\t this.panelStack.push( drilldown );\n\t // hide this panel's controls and list, set the name for back navigation, and attach to the $el\n\t this.$( '> .controls' ).add( this.$list() ).hide();\n\t drilldown.parentName = this.model.get( 'name' );\n\t this.$el.append( drilldown.render().$el );\n\t },\n\t\n\t /** Handle drilldown close by freeing the panel and re-rendering this panel */\n\t _collapseDrilldownPanel : function( drilldown ){\n\t this.panelStack.pop();\n\t this.render();\n\t },\n\t\n\t // ------------------------------------------------------------------------ panel events\n\t /** event map */\n\t events : {\n\t 'click .navigation .back' : 'close'\n\t },\n\t\n\t /** close/remove this collection panel */\n\t close : function( event ){\n\t this.remove();\n\t this.trigger( 'close' );\n\t },\n\t\n\t // ........................................................................ misc\n\t /** string rep */\n\t toString : function(){\n\t return 'CollectionView(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';\n\t }\n\t});\n\t\n\t\n\t//------------------------------------------------------------------------------ TEMPLATES\n\tCollectionView.prototype.templates = (function(){\n\t\n\t var controlsTemplate = BASE_MVC.wrapTemplate([\n\t '
    ',\n\t '',\n\t\n\t '
    ',\n\t '
    <%- collection.name || collection.element_identifier %>
    ',\n\t '
    ',\n\t '<% if( collection.collection_type === \"list\" ){ %>',\n\t _l( 'a list of datasets' ),\n\t '<% } else if( collection.collection_type === \"paired\" ){ %>',\n\t _l( 'a pair of datasets' ),\n\t '<% } else if( collection.collection_type === \"list:paired\" ){ %>',\n\t _l( 'a list of paired datasets' ),\n\t '<% } else if( collection.collection_type === \"list:list\" ){ %>',\n\t _l( 'a list of dataset lists' ),\n\t '<% } %>',\n\t '
    ',\n\t '
    ',\n\t '
    '\n\t ], 'collection' );\n\t\n\t return _.extend( _.clone( _super.prototype.templates ), {\n\t controls : controlsTemplate\n\t });\n\t}());\n\t\n\t\n\t\n\t// =============================================================================\n\t/** @class non-editable, read-only View/Controller for a dataset collection. */\n\tvar ListCollectionView = CollectionView.extend(\n\t/** @lends ListCollectionView.prototype */{\n\t\n\t //TODO: not strictly needed - due to switch in CollectionView._getContentClass\n\t /** sub view class used for datasets */\n\t DatasetDCEViewClass : DC_LI.DatasetDCEListItemView,\n\t\n\t // ........................................................................ misc\n\t /** string rep */\n\t toString : function(){\n\t return 'ListCollectionView(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';\n\t }\n\t});\n\t\n\t\n\t// =============================================================================\n\t/** @class non-editable, read-only View/Controller for a dataset collection. */\n\tvar PairCollectionView = ListCollectionView.extend(\n\t/** @lends PairCollectionView.prototype */{\n\t\n\t // ........................................................................ misc\n\t /** string rep */\n\t toString : function(){\n\t return 'PairCollectionView(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';\n\t }\n\t});\n\t\n\t\n\t// =============================================================================\n\t/** @class non-editable, read-only View/Controller for a dataset collection. */\n\tvar ListOfPairsCollectionView = CollectionView.extend(\n\t/** @lends ListOfPairsCollectionView.prototype */{\n\t\n\t //TODO: not strictly needed - due to switch in CollectionView._getContentClass\n\t /** sub view class used for nested collections */\n\t NestedDCDCEViewClass : DC_LI.NestedDCDCEListItemView.extend({\n\t foldoutPanelClass : PairCollectionView\n\t }),\n\t\n\t // ........................................................................ misc\n\t /** string rep */\n\t toString : function(){\n\t return 'ListOfPairsCollectionView(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';\n\t }\n\t});\n\t\n\t\n\t// =============================================================================\n\t/** @class non-editable, read-only View/Controller for a list of lists dataset collection. */\n\tvar ListOfListsCollectionView = CollectionView.extend({\n\t\n\t /** sub view class used for nested collections */\n\t NestedDCDCEViewClass : DC_LI.NestedDCDCEListItemView.extend({\n\t foldoutPanelClass : PairCollectionView\n\t }),\n\t\n\t /** string rep */\n\t toString : function(){\n\t return 'ListOfListsCollectionView(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';\n\t }\n\t});\n\t\n\t\n\t//==============================================================================\n\t return {\n\t CollectionView : CollectionView,\n\t ListCollectionView : ListCollectionView,\n\t PairCollectionView : PairCollectionView,\n\t ListOfPairsCollectionView : ListOfPairsCollectionView,\n\t ListOfListsCollectionView : ListOfListsCollectionView\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(2)))\n\n/***/ },\n/* 69 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(_, $) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(12),\n\t __webpack_require__(32),\n\t __webpack_require__(77),\n\t __webpack_require__(66),\n\t __webpack_require__(22),\n\t __webpack_require__(6),\n\t __webpack_require__(5)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( STATES, DATASET_LI, TAGS, ANNOTATIONS, faIconButton, BASE_MVC, _l ){\n\t\n\t'use strict';\n\t//==============================================================================\n\tvar _super = DATASET_LI.DatasetListItemView;\n\t/** @class Editing view for DatasetAssociation.\n\t */\n\tvar DatasetListItemEdit = _super.extend(\n\t/** @lends DatasetListItemEdit.prototype */{\n\t\n\t /** set up: options */\n\t initialize : function( attributes ){\n\t _super.prototype.initialize.call( this, attributes );\n\t this.hasUser = attributes.hasUser;\n\t\n\t /** allow user purge of dataset files? */\n\t this.purgeAllowed = attributes.purgeAllowed || false;\n\t\n\t //TODO: move to HiddenUntilActivatedViewMixin\n\t /** should the tags editor be shown or hidden initially? */\n\t this.tagsEditorShown = attributes.tagsEditorShown || false;\n\t /** should the tags editor be shown or hidden initially? */\n\t this.annotationEditorShown = attributes.annotationEditorShown || false;\n\t },\n\t\n\t // ......................................................................... titlebar actions\n\t /** In this override, add the other two primary actions: edit and delete */\n\t _renderPrimaryActions : function(){\n\t var actions = _super.prototype._renderPrimaryActions.call( this );\n\t if( this.model.get( 'state' ) === STATES.NOT_VIEWABLE ){\n\t return actions;\n\t }\n\t // render the display, edit attr and delete icon-buttons\n\t return _super.prototype._renderPrimaryActions.call( this ).concat([\n\t this._renderEditButton(),\n\t this._renderDeleteButton()\n\t ]);\n\t },\n\t\n\t //TODO: move titleButtons into state renderers, remove state checks in the buttons\n\t\n\t /** Render icon-button to edit the attributes (format, permissions, etc.) this dataset. */\n\t _renderEditButton : function(){\n\t // don't show edit while uploading, in-accessible\n\t // DO show if in error (ala previous history panel)\n\t if( ( this.model.get( 'state' ) === STATES.DISCARDED )\n\t || ( !this.model.get( 'accessible' ) ) ){\n\t return null;\n\t }\n\t\n\t var purged = this.model.get( 'purged' ),\n\t deleted = this.model.get( 'deleted' ),\n\t editBtnData = {\n\t title : _l( 'Edit attributes' ),\n\t href : this.model.urls.edit,\n\t target : this.linkTarget,\n\t faIcon : 'fa-pencil',\n\t classes : 'edit-btn'\n\t };\n\t\n\t // disable if purged or deleted and explain why in the tooltip\n\t if( deleted || purged ){\n\t editBtnData.disabled = true;\n\t if( purged ){\n\t editBtnData.title = _l( 'Cannot edit attributes of datasets removed from disk' );\n\t } else if( deleted ){\n\t editBtnData.title = _l( 'Undelete dataset to edit attributes' );\n\t }\n\t\n\t // disable if still uploading or new\n\t } else if( _.contains( [ STATES.UPLOAD, STATES.NEW ], this.model.get( 'state' ) ) ){\n\t editBtnData.disabled = true;\n\t editBtnData.title = _l( 'This dataset is not yet editable' );\n\t }\n\t return faIconButton( editBtnData );\n\t },\n\t\n\t /** Render icon-button to delete this hda. */\n\t _renderDeleteButton : function(){\n\t // don't show delete if...\n\t if( ( !this.model.get( 'accessible' ) ) ){\n\t return null;\n\t }\n\t\n\t var self = this,\n\t deletedAlready = this.model.isDeletedOrPurged();\n\t return faIconButton({\n\t title : !deletedAlready? _l( 'Delete' ) : _l( 'Dataset is already deleted' ),\n\t disabled : deletedAlready,\n\t faIcon : 'fa-times',\n\t classes : 'delete-btn',\n\t onclick : function() {\n\t // ...bler... tooltips being left behind in DOM (hover out never called on deletion)\n\t self.$el.find( '.icon-btn.delete-btn' ).trigger( 'mouseout' );\n\t self.model[ 'delete' ]();\n\t }\n\t });\n\t },\n\t\n\t // ......................................................................... details\n\t /** In this override, add tags and annotations controls, make the ? dbkey a link to editing page */\n\t _renderDetails : function(){\n\t //TODO: generalize to be allow different details for each state\n\t var $details = _super.prototype._renderDetails.call( this ),\n\t state = this.model.get( 'state' );\n\t\n\t if( !this.model.isDeletedOrPurged() && _.contains([ STATES.OK, STATES.FAILED_METADATA ], state ) ){\n\t this._renderTags( $details );\n\t this._renderAnnotation( $details );\n\t this._makeDbkeyEditLink( $details );\n\t }\n\t\n\t this._setUpBehaviors( $details );\n\t return $details;\n\t },\n\t\n\t /** Add less commonly used actions in the details section based on state */\n\t _renderSecondaryActions : function(){\n\t var actions = _super.prototype._renderSecondaryActions.call( this );\n\t switch( this.model.get( 'state' ) ){\n\t case STATES.UPLOAD:\n\t case STATES.NOT_VIEWABLE:\n\t return actions;\n\t case STATES.ERROR:\n\t // error button comes first\n\t actions.unshift( this._renderErrButton() );\n\t return actions.concat([ this._renderRerunButton() ]);\n\t case STATES.OK:\n\t case STATES.FAILED_METADATA:\n\t return actions.concat([ this._renderRerunButton(), this._renderVisualizationsButton() ]);\n\t }\n\t return actions.concat([ this._renderRerunButton() ]);\n\t },\n\t\n\t /** Render icon-button to report an error on this dataset to the galaxy admin. */\n\t _renderErrButton : function(){\n\t return faIconButton({\n\t title : _l( 'View or report this error' ),\n\t href : this.model.urls.report_error,\n\t classes : 'report-error-btn',\n\t target : this.linkTarget,\n\t faIcon : 'fa-bug'\n\t });\n\t },\n\t\n\t /** Render icon-button to re-run the job that created this dataset. */\n\t _renderRerunButton : function(){\n\t var creating_job = this.model.get( 'creating_job' );\n\t if( this.model.get( 'rerunnable' ) ){\n\t return faIconButton({\n\t title : _l( 'Run this job again' ),\n\t href : this.model.urls.rerun,\n\t classes : 'rerun-btn',\n\t target : this.linkTarget,\n\t faIcon : 'fa-refresh',\n\t onclick : function( ev ) {\n\t ev.preventDefault();\n\t // create webpack split point in order to load the tool form async\n\t // TODO: split not working (tool loads fine)\n\t !/* require */(/* empty */function() { var __WEBPACK_AMD_REQUIRE_ARRAY__ = [ __webpack_require__(18) ]; (function( ToolForm ){\n\t var form = new ToolForm.View({ 'job_id' : creating_job });\n\t form.deferred.execute( function(){\n\t Galaxy.app.display( form );\n\t });\n\t }.apply(null, __WEBPACK_AMD_REQUIRE_ARRAY__));}());\n\t }\n\t });\n\t }\n\t },\n\t\n\t /** Render an icon-button or popupmenu of links based on the applicable visualizations */\n\t _renderVisualizationsButton : function(){\n\t //TODO: someday - lazyload visualizations\n\t var visualizations = this.model.get( 'visualizations' );\n\t if( ( this.model.isDeletedOrPurged() )\n\t || ( !this.hasUser )\n\t || ( !this.model.hasData() )\n\t || ( _.isEmpty( visualizations ) ) ){\n\t return null;\n\t }\n\t if( !_.isObject( visualizations[0] ) ){\n\t this.warn( 'Visualizations have been switched off' );\n\t return null;\n\t }\n\t\n\t var $visualizations = $( this.templates.visualizations( visualizations, this ) );\n\t //HACK: need to re-write those directed at galaxy_main with linkTarget\n\t $visualizations.find( '[target=\"galaxy_main\"]').attr( 'target', this.linkTarget );\n\t // use addBack here to include the root $visualizations elem (for the case of 1 visualization)\n\t this._addScratchBookFn( $visualizations.find( '.visualization-link' ).addBack( '.visualization-link' ) );\n\t return $visualizations;\n\t },\n\t\n\t /** add scratchbook functionality to visualization links */\n\t _addScratchBookFn : function( $links ){\n\t var li = this;\n\t $links.click( function( ev ){\n\t if( Galaxy.frame && Galaxy.frame.active ){\n\t Galaxy.frame.add({\n\t title : 'Visualization',\n\t url : $( this ).attr( 'href' )\n\t });\n\t ev.preventDefault();\n\t ev.stopPropagation();\n\t }\n\t });\n\t },\n\t\n\t //TODO: if possible move these to readonly view - but display the owner's tags/annotation (no edit)\n\t /** Render the tags list/control */\n\t _renderTags : function( $where ){\n\t if( !this.hasUser ){ return; }\n\t var view = this;\n\t this.tagsEditor = new TAGS.TagsEditor({\n\t model : this.model,\n\t el : $where.find( '.tags-display' ),\n\t onshowFirstTime : function(){ this.render(); },\n\t // persist state on the hda view (and not the editor) since these are currently re-created each time\n\t onshow : function(){ view.tagsEditorShown = true; },\n\t onhide : function(){ view.tagsEditorShown = false; },\n\t $activator : faIconButton({\n\t title : _l( 'Edit dataset tags' ),\n\t classes : 'tag-btn',\n\t faIcon : 'fa-tags'\n\t }).appendTo( $where.find( '.actions .right' ) )\n\t });\n\t if( this.tagsEditorShown ){ this.tagsEditor.toggle( true ); }\n\t },\n\t\n\t /** Render the annotation display/control */\n\t _renderAnnotation : function( $where ){\n\t if( !this.hasUser ){ return; }\n\t var view = this;\n\t this.annotationEditor = new ANNOTATIONS.AnnotationEditor({\n\t model : this.model,\n\t el : $where.find( '.annotation-display' ),\n\t onshowFirstTime : function(){ this.render(); },\n\t // persist state on the hda view (and not the editor) since these are currently re-created each time\n\t onshow : function(){ view.annotationEditorShown = true; },\n\t onhide : function(){ view.annotationEditorShown = false; },\n\t $activator : faIconButton({\n\t title : _l( 'Edit dataset annotation' ),\n\t classes : 'annotate-btn',\n\t faIcon : 'fa-comment'\n\t }).appendTo( $where.find( '.actions .right' ) )\n\t });\n\t if( this.annotationEditorShown ){ this.annotationEditor.toggle( true ); }\n\t },\n\t\n\t /** If the format/dbkey/genome_build isn't set, make the display a link to the edit page */\n\t _makeDbkeyEditLink : function( $details ){\n\t // make the dbkey a link to editing\n\t if( this.model.get( 'metadata_dbkey' ) === '?'\n\t && !this.model.isDeletedOrPurged() ){\n\t var editableDbkey = $( '?' )\n\t .attr( 'href', this.model.urls.edit )\n\t .attr( 'target', this.linkTarget );\n\t $details.find( '.dbkey .value' ).replaceWith( editableDbkey );\n\t }\n\t },\n\t\n\t // ......................................................................... events\n\t /** event map */\n\t events : _.extend( _.clone( _super.prototype.events ), {\n\t 'click .undelete-link' : '_clickUndeleteLink',\n\t 'click .purge-link' : '_clickPurgeLink',\n\t\n\t 'click .edit-btn' : function( ev ){ this.trigger( 'edit', this, ev ); },\n\t 'click .delete-btn' : function( ev ){ this.trigger( 'delete', this, ev ); },\n\t 'click .rerun-btn' : function( ev ){ this.trigger( 'rerun', this, ev ); },\n\t 'click .report-err-btn' : function( ev ){ this.trigger( 'report-err', this, ev ); },\n\t 'click .visualization-btn' : function( ev ){ this.trigger( 'visualize', this, ev ); },\n\t 'click .dbkey a' : function( ev ){ this.trigger( 'edit', this, ev ); }\n\t }),\n\t\n\t /** listener for item undelete (in the messages section) */\n\t _clickUndeleteLink : function( ev ){\n\t this.model.undelete();\n\t return false;\n\t },\n\t\n\t /** listener for item purge (in the messages section) */\n\t _clickPurgeLink : function( ev ){\n\t if( confirm( _l( 'This will permanently remove the data in your dataset. Are you sure?' ) ) ){\n\t this.model.purge();\n\t }\n\t return false;\n\t },\n\t\n\t // ......................................................................... misc\n\t /** string rep */\n\t toString : function(){\n\t var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n\t return 'HDAEditView(' + modelString + ')';\n\t }\n\t});\n\t\n\t\n\t// ............................................................................ TEMPLATES\n\t/** underscore templates */\n\tDatasetListItemEdit.prototype.templates = (function(){\n\t\n\t var warnings = _.extend( {}, _super.prototype.templates.warnings, {\n\t failed_metadata : BASE_MVC.wrapTemplate([\n\t // in this override, provide a link to the edit page\n\t '<% if( dataset.state === \"failed_metadata\" ){ %>',\n\t '',\n\t '<% } %>'\n\t ], 'dataset' ),\n\t\n\t deleted : BASE_MVC.wrapTemplate([\n\t // in this override, provide links to undelete or purge the dataset\n\t '<% if( dataset.deleted && !dataset.purged ){ %>',\n\t // deleted not purged\n\t '
    ',\n\t _l( 'This dataset has been deleted' ),\n\t '
    ', _l( 'Undelete it' ), '',\n\t '<% if( view.purgeAllowed ){ %>',\n\t '
    ',\n\t _l( 'Permanently remove it from disk' ),\n\t '',\n\t '<% } %>',\n\t '
    ',\n\t '<% } %>'\n\t ], 'dataset' )\n\t });\n\t\n\t var visualizationsTemplate = BASE_MVC.wrapTemplate([\n\t '<% if( visualizations.length === 1 ){ %>',\n\t '\"',\n\t ' target=\"<%- visualizations[0].target %>\" title=\"', _l( 'Visualize in' ),\n\t ' <%- visualizations[0].html %>\">',\n\t '',\n\t '',\n\t\n\t '<% } else { %>',\n\t '
    ',\n\t '',\n\t '',\n\t '',\n\t '',\n\t '
    ',\n\t '<% } %>'\n\t ], 'visualizations' );\n\t\n\t return _.extend( {}, _super.prototype.templates, {\n\t warnings : warnings,\n\t visualizations : visualizationsTemplate\n\t });\n\t}());\n\t\n\t\n\t//==============================================================================\n\t return {\n\t DatasetListItemEdit : DatasetListItemEdit\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(2), __webpack_require__(1)))\n\n/***/ },\n/* 70 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, _, jQuery) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(12),\n\t __webpack_require__(6),\n\t __webpack_require__(5)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( STATES, BASE_MVC, _l ){\n\t'use strict';\n\t\n\tvar logNamespace = 'dataset';\n\t//==============================================================================\n\tvar searchableMixin = BASE_MVC.SearchableModelMixin;\n\t/** @class base model for any DatasetAssociation (HDAs, LDDAs, DatasetCollectionDAs).\n\t * No knowledge of what type (HDA/LDDA/DCDA) should be needed here.\n\t * The DA's are made searchable (by attribute) by mixing in SearchableModelMixin.\n\t */\n\tvar DatasetAssociation = Backbone.Model\n\t .extend( BASE_MVC.LoggableMixin )\n\t .extend( BASE_MVC.mixin( searchableMixin, /** @lends DatasetAssociation.prototype */{\n\t _logNamespace : logNamespace,\n\t\n\t /** default attributes for a model */\n\t defaults : {\n\t state : STATES.NEW,\n\t deleted : false,\n\t purged : false,\n\t name : '(unnamed dataset)',\n\t accessible : true,\n\t // sniffed datatype (sam, tabular, bed, etc.)\n\t data_type : '',\n\t file_ext : '',\n\t file_size : 0,\n\t\n\t // array of associated file types (eg. [ 'bam_index', ... ])\n\t meta_files : [],\n\t\n\t misc_blurb : '',\n\t misc_info : '',\n\t\n\t tags : []\n\t // do NOT default on annotation, as this default is valid and will be passed on 'save'\n\t // which is incorrect behavior when the model is only partially fetched (annos are not passed in summary data)\n\t //annotation : ''\n\t },\n\t\n\t /** instance vars and listeners */\n\t initialize : function( attributes, options ){\n\t this.debug( this + '(Dataset).initialize', attributes, options );\n\t\n\t //!! this state is not in trans.app.model.Dataset.states - set it here -\n\t if( !this.get( 'accessible' ) ){\n\t this.set( 'state', STATES.NOT_VIEWABLE );\n\t }\n\t\n\t /** Datasets rely/use some web controllers - have the model generate those URLs on startup */\n\t this.urls = this._generateUrls();\n\t\n\t this._setUpListeners();\n\t },\n\t\n\t /** returns misc. web urls for rendering things like re-run, display, etc. */\n\t _generateUrls : function(){\n\t var id = this.get( 'id' );\n\t if( !id ){ return {}; }\n\t var urls = {\n\t 'purge' : 'datasets/' + id + '/purge_async',\n\t 'display' : 'datasets/' + id + '/display/?preview=True',\n\t 'edit' : 'datasets/' + id + '/edit',\n\t 'download' : 'datasets/' + id + '/display?to_ext=' + this.get( 'file_ext' ),\n\t 'report_error' : 'dataset/errors?id=' + id,\n\t 'rerun' : 'tool_runner/rerun?id=' + id,\n\t 'show_params' : 'datasets/' + id + '/show_params',\n\t 'visualization' : 'visualization',\n\t 'meta_download' : 'dataset/get_metadata_file?hda_id=' + id + '&metadata_name='\n\t };\n\t _.each( urls, function( value, key ){\n\t urls[ key ] = Galaxy.root + value;\n\t });\n\t this.urls = urls;\n\t return urls;\n\t },\n\t\n\t /** set up any event listeners\n\t * event: state:ready fired when this DA moves into/is already in a ready state\n\t */\n\t _setUpListeners : function(){\n\t // if the state has changed and the new state is a ready state, fire an event\n\t this.on( 'change:state', function( currModel, newState ){\n\t this.log( this + ' has changed state:', currModel, newState );\n\t if( this.inReadyState() ){\n\t this.trigger( 'state:ready', currModel, newState, this.previous( 'state' ) );\n\t }\n\t });\n\t // the download url (currently) relies on having a correct file extension\n\t this.on( 'change:id change:file_ext', function( currModel ){\n\t this._generateUrls();\n\t });\n\t },\n\t\n\t // ........................................................................ common queries\n\t /** override to add urls */\n\t toJSON : function(){\n\t var json = Backbone.Model.prototype.toJSON.call( this );\n\t //console.warn( 'returning json?' );\n\t //return json;\n\t return _.extend( json, {\n\t urls : this.urls\n\t });\n\t },\n\t\n\t /** Is this dataset deleted or purged? */\n\t isDeletedOrPurged : function(){\n\t return ( this.get( 'deleted' ) || this.get( 'purged' ) );\n\t },\n\t\n\t /** Is this dataset in a 'ready' state; where 'Ready' states are states where no\n\t * processing (for the ds) is left to do on the server.\n\t */\n\t inReadyState : function(){\n\t var ready = _.contains( STATES.READY_STATES, this.get( 'state' ) );\n\t return ( this.isDeletedOrPurged() || ready );\n\t },\n\t\n\t /** Does this model already contain detailed data (as opposed to just summary level data)? */\n\t hasDetails : function(){\n\t // if it's inaccessible assume it has everything it needs\n\t if( !this.get( 'accessible' ) ){ return true; }\n\t return this.has( 'annotation' );\n\t },\n\t\n\t /** Convenience function to match dataset.has_data. */\n\t hasData : function(){\n\t return ( this.get( 'file_size' ) > 0 );\n\t },\n\t\n\t // ........................................................................ ajax\n\t fetch : function( options ){\n\t var dataset = this;\n\t return Backbone.Model.prototype.fetch.call( this, options )\n\t .always( function(){\n\t dataset._generateUrls();\n\t });\n\t },\n\t\n\t /** override to use actual Dates objects for create/update times */\n\t parse : function( response, options ){\n\t var parsed = Backbone.Model.prototype.parse.call( this, response, options );\n\t if( parsed.create_time ){\n\t parsed.create_time = new Date( parsed.create_time );\n\t }\n\t if( parsed.update_time ){\n\t parsed.update_time = new Date( parsed.update_time );\n\t }\n\t return parsed;\n\t },\n\t\n\t /** override to wait by default */\n\t save : function( attrs, options ){\n\t options = options || {};\n\t options.wait = _.isUndefined( options.wait ) ? true : options.wait;\n\t return Backbone.Model.prototype.save.call( this, attrs, options );\n\t },\n\t\n\t //NOTE: subclasses of DA's will need to implement url and urlRoot in order to have these work properly\n\t /** save this dataset, _Mark_ing it as deleted (just a flag) */\n\t 'delete' : function( options ){\n\t if( this.get( 'deleted' ) ){ return jQuery.when(); }\n\t return this.save( { deleted: true }, options );\n\t },\n\t /** save this dataset, _Mark_ing it as undeleted */\n\t undelete : function( options ){\n\t if( !this.get( 'deleted' ) || this.get( 'purged' ) ){ return jQuery.when(); }\n\t return this.save( { deleted: false }, options );\n\t },\n\t\n\t /** remove the file behind this dataset from the filesystem (if permitted) */\n\t purge : function _purge( options ){\n\t //TODO: use, override model.destroy, HDA.delete({ purge: true })\n\t if( this.get( 'purged' ) ){ return jQuery.when(); }\n\t options = options || {};\n\t options.url = this.urls.purge;\n\t\n\t //TODO: ideally this would be a DELETE call to the api\n\t // using purge async for now\n\t var hda = this,\n\t xhr = jQuery.ajax( options );\n\t xhr.done( function( message, status, responseObj ){\n\t hda.set({ deleted: true, purged: true });\n\t });\n\t xhr.fail( function( xhr, status, message ){\n\t // Exception messages are hidden within error page including: '...not allowed in this Galaxy instance.'\n\t // unbury and re-add to xhr\n\t var error = _l( \"Unable to purge dataset\" );\n\t var messageBuriedInUnfortunatelyFormattedError = ( 'Removal of datasets by users '\n\t + 'is not allowed in this Galaxy instance' );\n\t if( xhr.responseJSON && xhr.responseJSON.error ){\n\t error = xhr.responseJSON.error;\n\t } else if( xhr.responseText.indexOf( messageBuriedInUnfortunatelyFormattedError ) !== -1 ){\n\t error = messageBuriedInUnfortunatelyFormattedError;\n\t }\n\t xhr.responseText = error;\n\t hda.trigger( 'error', hda, xhr, options, _l( error ), { error: error } );\n\t });\n\t return xhr;\n\t },\n\t\n\t // ........................................................................ searching\n\t /** what attributes of an HDA will be used in a text search */\n\t searchAttributes : [\n\t 'name', 'file_ext', 'genome_build', 'misc_blurb', 'misc_info', 'annotation', 'tags'\n\t ],\n\t\n\t /** our attr keys don't often match the labels we display to the user - so, when using\n\t * attribute specifiers ('name=\"bler\"') in a term, allow passing in aliases for the\n\t * following attr keys.\n\t */\n\t searchAliases : {\n\t title : 'name',\n\t format : 'file_ext',\n\t database : 'genome_build',\n\t blurb : 'misc_blurb',\n\t description : 'misc_blurb',\n\t info : 'misc_info',\n\t tag : 'tags'\n\t },\n\t\n\t // ........................................................................ misc\n\t /** String representation */\n\t toString : function(){\n\t var nameAndId = this.get( 'id' ) || '';\n\t if( this.get( 'name' ) ){\n\t nameAndId = '\"' + this.get( 'name' ) + '\",' + nameAndId;\n\t }\n\t return 'Dataset(' + nameAndId + ')';\n\t }\n\t}));\n\t\n\t\n\t//==============================================================================\n\t/** @class Backbone collection for dataset associations.\n\t */\n\tvar DatasetAssociationCollection = Backbone.Collection.extend( BASE_MVC.LoggableMixin ).extend(\n\t/** @lends HistoryContents.prototype */{\n\t _logNamespace : logNamespace,\n\t\n\t model : DatasetAssociation,\n\t\n\t /** root api url */\n\t urlRoot : Galaxy.root + 'api/datasets',\n\t\n\t /** url fn */\n\t url : function(){\n\t return this.urlRoot;\n\t },\n\t\n\t // ........................................................................ common queries\n\t /** Get the ids of every item in this collection\n\t * @returns array of encoded ids\n\t */\n\t ids : function(){\n\t return this.map( function( item ){ return item.get('id'); });\n\t },\n\t\n\t /** Get contents that are not ready\n\t * @returns array of content models\n\t */\n\t notReady : function(){\n\t return this.filter( function( content ){\n\t return !content.inReadyState();\n\t });\n\t },\n\t\n\t /** return true if any datasets don't have details */\n\t haveDetails : function(){\n\t return this.all( function( dataset ){ return dataset.hasDetails(); });\n\t },\n\t\n\t // ........................................................................ ajax\n\t /** using a queue, perform ajaxFn on each of the models in this collection */\n\t ajaxQueue : function( ajaxFn, options ){\n\t var deferred = jQuery.Deferred(),\n\t startingLength = this.length,\n\t responses = [];\n\t\n\t if( !startingLength ){\n\t deferred.resolve([]);\n\t return deferred;\n\t }\n\t\n\t // use reverse order (stylistic choice)\n\t var ajaxFns = this.chain().reverse().map( function( dataset, i ){\n\t return function(){\n\t var xhr = ajaxFn.call( dataset, options );\n\t // if successful, notify using the deferred to allow tracking progress\n\t xhr.done( function( response ){\n\t deferred.notify({ curr: i, total: startingLength, response: response, model: dataset });\n\t });\n\t // (regardless of previous error or success) if not last ajax call, shift and call the next\n\t // if last fn, resolve deferred\n\t xhr.always( function( response ){\n\t responses.push( response );\n\t if( ajaxFns.length ){\n\t ajaxFns.shift()();\n\t } else {\n\t deferred.resolve( responses );\n\t }\n\t });\n\t };\n\t }).value();\n\t // start the queue\n\t ajaxFns.shift()();\n\t\n\t return deferred;\n\t },\n\t\n\t // ........................................................................ sorting/filtering\n\t /** return a new collection of datasets whose attributes contain the substring matchesWhat */\n\t matches : function( matchesWhat ){\n\t return this.filter( function( dataset ){\n\t return dataset.matches( matchesWhat );\n\t });\n\t },\n\t\n\t /** String representation. */\n\t toString : function(){\n\t return ([ 'DatasetAssociationCollection(', this.length, ')' ].join( '' ));\n\t }\n\t});\n\t\n\t\n\t//==============================================================================\n\t return {\n\t DatasetAssociation : DatasetAssociation,\n\t DatasetAssociationCollection : DatasetAssociationCollection\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(2), __webpack_require__(1)))\n\n/***/ },\n/* 71 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(_) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(32),\n\t __webpack_require__(6),\n\t __webpack_require__(5)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( DATASET_LI, BASE_MVC, _l ){\n\t\n\t'use strict';\n\t\n\t//==============================================================================\n\tvar _super = DATASET_LI.DatasetListItemView;\n\t/** @class Read only view for HistoryDatasetAssociation.\n\t * Since there are no controls on the HDAView to hide the dataset,\n\t * the primary thing this class does (currently) is override templates\n\t * to render the HID.\n\t */\n\tvar HDAListItemView = _super.extend(\n\t/** @lends HDAListItemView.prototype */{\n\t\n\t className : _super.prototype.className + \" history-content\",\n\t\n\t initialize : function( attributes, options ){\n\t _super.prototype.initialize.call( this, attributes, options );\n\t },\n\t\n\t // ......................................................................... misc\n\t /** String representation */\n\t toString : function(){\n\t var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n\t return 'HDAListItemView(' + modelString + ')';\n\t }\n\t});\n\t\n\t// ............................................................................ TEMPLATES\n\t/** underscore templates */\n\tHDAListItemView.prototype.templates = (function(){\n\t\n\t var titleBarTemplate = BASE_MVC.wrapTemplate([\n\t // adding the hid display to the title\n\t '
    ',\n\t '',\n\t '
    ',\n\t //TODO: remove whitespace and use margin-right\n\t '<%- dataset.hid %> ',\n\t '<%- dataset.name %>',\n\t '
    ',\n\t '
    '\n\t ], 'dataset' );\n\t\n\t var warnings = _.extend( {}, _super.prototype.templates.warnings, {\n\t hidden : BASE_MVC.wrapTemplate([\n\t // add a warning when hidden\n\t '<% if( !dataset.visible ){ %>',\n\t '
    ',\n\t _l( 'This dataset has been hidden' ),\n\t '
    ',\n\t '<% } %>'\n\t ], 'dataset' )\n\t });\n\t\n\t return _.extend( {}, _super.prototype.templates, {\n\t titleBar : titleBarTemplate,\n\t warnings : warnings\n\t });\n\t}());\n\t\n\t\n\t\n\t//==============================================================================\n\t return {\n\t HDAListItemView : HDAListItemView\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(2)))\n\n/***/ },\n/* 72 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(_) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(70),\n\t __webpack_require__(74),\n\t __webpack_require__(6),\n\t __webpack_require__(5)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( DATASET, HISTORY_CONTENT, BASE_MVC, _l ){\n\t'use strict';\n\t\n\t//==============================================================================\n\tvar _super = DATASET.DatasetAssociation,\n\t hcontentMixin = HISTORY_CONTENT.HistoryContentMixin;\n\t/** @class (HDA) model for a Galaxy dataset contained in and related to a history.\n\t */\n\tvar HistoryDatasetAssociation = _super.extend( BASE_MVC.mixin( hcontentMixin,\n\t/** @lends HistoryDatasetAssociation.prototype */{\n\t\n\t /** default attributes for a model */\n\t defaults : _.extend( {}, _super.prototype.defaults, hcontentMixin.defaults, {\n\t history_content_type: 'dataset',\n\t model_class : 'HistoryDatasetAssociation'\n\t }),\n\t}));\n\t\n\t//==============================================================================\n\t return {\n\t HistoryDatasetAssociation : HistoryDatasetAssociation\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(2)))\n\n/***/ },\n/* 73 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(_) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(12),\n\t __webpack_require__(29),\n\t __webpack_require__(68),\n\t __webpack_require__(6),\n\t __webpack_require__(5)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( STATES, DC_LI, DC_VIEW, BASE_MVC, _l ){\n\t\n\t'use strict';\n\t\n\t//==============================================================================\n\tvar _super = DC_LI.DCListItemView;\n\t/** @class Read only view for HistoryDatasetCollectionAssociation (a dataset collection inside a history).\n\t */\n\tvar HDCAListItemView = _super.extend(\n\t/** @lends HDCAListItemView.prototype */{\n\t\n\t className : _super.prototype.className + \" history-content\",\n\t\n\t /** event listeners */\n\t _setUpListeners : function(){\n\t _super.prototype._setUpListeners.call( this );\n\t\n\t this.listenTo( this.model, {\n\t 'change:populated change:visible' : function( model, options ){ this.render(); },\n\t });\n\t },\n\t\n\t /** Override to provide the proper collections panels as the foldout */\n\t _getFoldoutPanelClass : function(){\n\t switch( this.model.get( 'collection_type' ) ){\n\t case 'list':\n\t return DC_VIEW.ListCollectionView;\n\t case 'paired':\n\t return DC_VIEW.PairCollectionView;\n\t case 'list:paired':\n\t return DC_VIEW.ListOfPairsCollectionView;\n\t case 'list:list':\n\t return DC_VIEW.ListOfListsCollectionView;\n\t }\n\t throw new TypeError( 'Uknown collection_type: ' + this.model.get( 'collection_type' ) );\n\t },\n\t\n\t /** In this override, add the state as a class for use with state-based CSS */\n\t _swapNewRender : function( $newRender ){\n\t _super.prototype._swapNewRender.call( this, $newRender );\n\t //TODO: model currently has no state\n\t var state = !this.model.get( 'populated' ) ? STATES.RUNNING : STATES.OK;\n\t //if( this.model.has( 'state' ) ){\n\t this.$el.addClass( 'state-' + state );\n\t //}\n\t return this.$el;\n\t },\n\t\n\t // ......................................................................... misc\n\t /** String representation */\n\t toString : function(){\n\t var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n\t return 'HDCAListItemView(' + modelString + ')';\n\t }\n\t});\n\t\n\t/** underscore templates */\n\tHDCAListItemView.prototype.templates = (function(){\n\t\n\t var warnings = _.extend( {}, _super.prototype.templates.warnings, {\n\t hidden : BASE_MVC.wrapTemplate([\n\t // add a warning when hidden\n\t '<% if( !collection.visible ){ %>',\n\t '
    ',\n\t _l( 'This collection has been hidden' ),\n\t '
    ',\n\t '<% } %>'\n\t ], 'collection' )\n\t });\n\t\n\t// could steal this from hda-base (or use mixed content)\n\t var titleBarTemplate = BASE_MVC.wrapTemplate([\n\t // adding the hid display to the title\n\t '
    ',\n\t '',\n\t '
    ',\n\t //TODO: remove whitespace and use margin-right\n\t '<%- collection.hid %> ',\n\t '<%- collection.name %>',\n\t '
    ',\n\t '
    ',\n\t '
    '\n\t ], 'collection' );\n\t\n\t return _.extend( {}, _super.prototype.templates, {\n\t warnings : warnings,\n\t titleBar : titleBarTemplate\n\t });\n\t}());\n\t\n\t\n\t//==============================================================================\n\t return {\n\t HDCAListItemView : HDCAListItemView\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(2)))\n\n/***/ },\n/* 74 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(jQuery) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(12),\n\t __webpack_require__(6),\n\t __webpack_require__(5)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( STATES, BASE_MVC, _l ){\n\t'use strict';\n\t\n\t//==============================================================================\n\t/** @class Mixin for HistoryContents content (HDAs, HDCAs).\n\t */\n\tvar HistoryContentMixin = {\n\t\n\t /** default attributes for a model */\n\t defaults : {\n\t /** parent (containing) history */\n\t history_id : null,\n\t /** some content_type (HistoryContents can contain mixed model classes) */\n\t history_content_type: null,\n\t /** indicating when/what order the content was generated in the context of the history */\n\t hid : null,\n\t /** whether the user wants the content shown (visible) */\n\t visible : true\n\t },\n\t\n\t // ........................................................................ mixed content element\n\t // In order to be part of a MIXED bbone collection, we can't rely on the id\n\t // (which may collide btwn models of different classes)\n\t // Instead, use type_id which prefixes the history_content_type so the bbone collection can differentiate\n\t idAttribute : 'type_id',\n\t\n\t // ........................................................................ common queries\n\t /** the more common alias of visible */\n\t hidden : function(){\n\t return !this.get( 'visible' );\n\t },\n\t\n\t//TODO: remove\n\t /** based on includeDeleted, includeHidden (gen. from the container control),\n\t * would this ds show in the list of ds's?\n\t * @param {Boolean} includeDeleted are we showing deleted hdas?\n\t * @param {Boolean} includeHidden are we showing hidden hdas?\n\t */\n\t isVisible : function( includeDeleted, includeHidden ){\n\t var isVisible = true;\n\t if( ( !includeDeleted )\n\t && ( this.get( 'deleted' ) || this.get( 'purged' ) ) ){\n\t isVisible = false;\n\t }\n\t if( ( !includeHidden )\n\t && ( !this.get( 'visible' ) ) ){\n\t isVisible = false;\n\t }\n\t return isVisible;\n\t },\n\t\n\t // ........................................................................ ajax\n\t //TODO?: these are probably better done on the leaf classes\n\t /** history content goes through the 'api/histories' API */\n\t urlRoot: Galaxy.root + 'api/histories/',\n\t\n\t /** full url spec. for this content */\n\t url : function(){\n\t var url = this.urlRoot + this.get( 'history_id' ) + '/contents/'\n\t + this.get('history_content_type') + 's/' + this.get( 'id' );\n\t return url;\n\t },\n\t\n\t /** save this content as not visible */\n\t hide : function( options ){\n\t if( !this.get( 'visible' ) ){ return jQuery.when(); }\n\t return this.save( { visible: false }, options );\n\t },\n\t /** save this content as visible */\n\t unhide : function( options ){\n\t if( this.get( 'visible' ) ){ return jQuery.when(); }\n\t return this.save( { visible: true }, options );\n\t },\n\t\n\t // ........................................................................ misc\n\t toString : function(){\n\t return ([ this.get( 'type_id' ), this.get( 'hid' ), this.get( 'name' ) ].join(':'));\n\t }\n\t};\n\t\n\t\n\t//==============================================================================\n\t return {\n\t HistoryContentMixin : HistoryContentMixin\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(1)))\n\n/***/ },\n/* 75 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, jQuery, _, $) {\n\t!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(40),\n\t __webpack_require__(41),\n\t __webpack_require__(67),\n\t __webpack_require__(4),\n\t __webpack_require__(6),\n\t __webpack_require__(5)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( HISTORY_CONTENTS, HISTORY_PREFS, CONTROLLED_FETCH_COLLECTION, UTILS, BASE_MVC, _l ){\n\t'use strict';\n\t\n\t//==============================================================================\n\t/** @class Model for a Galaxy history resource - both a record of user\n\t * tool use and a collection of the datasets those tools produced.\n\t * @name History\n\t * @augments Backbone.Model\n\t */\n\tvar History = Backbone.Model\n\t .extend( BASE_MVC.LoggableMixin )\n\t .extend( BASE_MVC.mixin( BASE_MVC.SearchableModelMixin, /** @lends History.prototype */{\n\t _logNamespace : 'history',\n\t\n\t /** ms between fetches when checking running jobs/datasets for updates */\n\t UPDATE_DELAY : 4000,\n\t\n\t // values from api (may need more)\n\t defaults : {\n\t model_class : 'History',\n\t id : null,\n\t name : 'Unnamed History',\n\t state : 'new',\n\t\n\t deleted : false,\n\t contents_active : {},\n\t contents_states : {},\n\t },\n\t\n\t urlRoot: Galaxy.root + 'api/histories',\n\t\n\t contentsClass : HISTORY_CONTENTS.HistoryContents,\n\t\n\t /** What model fields to search with */\n\t searchAttributes : [\n\t 'name', 'annotation', 'tags'\n\t ],\n\t\n\t /** Adding title and singular tag */\n\t searchAliases : {\n\t title : 'name',\n\t tag : 'tags'\n\t },\n\t\n\t // ........................................................................ set up/tear down\n\t /** Set up the model\n\t * @param {Object} historyJSON model data for this History\n\t * @param {Object} options any extra settings including logger\n\t */\n\t initialize : function( historyJSON, options ){\n\t options = options || {};\n\t this.logger = options.logger || null;\n\t this.log( this + \".initialize:\", historyJSON, options );\n\t\n\t /** HistoryContents collection of the HDAs contained in this history. */\n\t this.contents = new this.contentsClass( [], {\n\t history : this,\n\t historyId : this.get( 'id' ),\n\t order : options.order,\n\t });\n\t\n\t this._setUpListeners();\n\t this._setUpCollectionListeners();\n\t\n\t /** cached timeout id for the dataset updater */\n\t this.updateTimeoutId = null;\n\t },\n\t\n\t /** set up any event listeners for this history including those to the contained HDAs\n\t * events: error:contents if an error occurred with the contents collection\n\t */\n\t _setUpListeners : function(){\n\t // if the model's id changes ('current' or null -> an actual id), update the contents history_id\n\t return this.on({\n\t 'error' : function( model, xhr, options, msg, details ){\n\t this.clearUpdateTimeout();\n\t },\n\t 'change:id' : function( model, newId ){\n\t if( this.contents ){\n\t this.contents.historyId = newId;\n\t }\n\t },\n\t });\n\t },\n\t\n\t /** event handlers for the contents submodels */\n\t _setUpCollectionListeners : function(){\n\t if( !this.contents ){ return this; }\n\t // bubble up errors\n\t return this.listenTo( this.contents, {\n\t 'error' : function(){\n\t this.trigger.apply( this, jQuery.makeArray( arguments ) );\n\t },\n\t });\n\t },\n\t\n\t // ........................................................................ derived attributes\n\t /** */\n\t contentsShown : function(){\n\t var contentsActive = this.get( 'contents_active' );\n\t var shown = contentsActive.active || 0;\n\t shown += this.contents.includeDeleted? contentsActive.deleted : 0;\n\t shown += this.contents.includeHidden? contentsActive.hidden : 0;\n\t return shown;\n\t },\n\t\n\t /** convert size in bytes to a more human readable version */\n\t nice_size : function(){\n\t var size = this.get( 'size' );\n\t return size? UTILS.bytesToString( size, true, 2 ) : _l( '(empty)' );\n\t },\n\t\n\t /** override to add nice_size */\n\t toJSON : function(){\n\t return _.extend( Backbone.Model.prototype.toJSON.call( this ), {\n\t nice_size : this.nice_size()\n\t });\n\t },\n\t\n\t /** override to allow getting nice_size */\n\t get : function( key ){\n\t if( key === 'nice_size' ){\n\t return this.nice_size();\n\t }\n\t return Backbone.Model.prototype.get.apply( this, arguments );\n\t },\n\t\n\t // ........................................................................ common queries\n\t /** T/F is this history owned by the current user (Galaxy.user)\n\t * Note: that this will return false for an anon user even if the history is theirs.\n\t */\n\t ownedByCurrUser : function(){\n\t // no currUser\n\t if( !Galaxy || !Galaxy.user ){\n\t return false;\n\t }\n\t // user is anon or history isn't owned\n\t if( Galaxy.user.isAnonymous() || Galaxy.user.id !== this.get( 'user_id' ) ){\n\t return false;\n\t }\n\t return true;\n\t },\n\t\n\t /** Return the number of running jobs assoc with this history (note: unknown === 0) */\n\t numOfUnfinishedJobs : function(){\n\t var unfinishedJobIds = this.get( 'non_ready_jobs' );\n\t return unfinishedJobIds? unfinishedJobIds.length : 0;\n\t },\n\t\n\t /** Return the number of running hda/hdcas in this history (note: unknown === 0) */\n\t numOfUnfinishedShownContents : function(){\n\t return this.contents.runningAndActive().length || 0;\n\t },\n\t\n\t // ........................................................................ updates\n\t _fetchContentRelatedAttributes : function(){\n\t var contentRelatedAttrs = [ 'size', 'non_ready_jobs', 'contents_active', 'hid_counter' ];\n\t return this.fetch({ data : $.param({ keys : contentRelatedAttrs.join( ',' ) }) });\n\t },\n\t\n\t /** check for any changes since the last time we updated (or fetch all if ) */\n\t refresh : function( options ){\n\t // console.log( this + '.refresh' );\n\t options = options || {};\n\t var self = this;\n\t\n\t // note if there was no previous update time, all summary contents will be fetched\n\t var lastUpdateTime = self.lastUpdateTime;\n\t // if we don't flip this, then a fully-fetched list will not be re-checked via fetch\n\t this.contents.allFetched = false;\n\t var fetchFn = self.contents.currentPage !== 0\n\t ? function(){ return self.contents.fetchPage( 0 ); }\n\t : function(){ return self.contents.fetchUpdated( lastUpdateTime ); };\n\t // note: if there was no previous update time, all summary contents will be fetched\n\t return fetchFn()\n\t .done( function( response, status, xhr ){\n\t var serverResponseDatetime;\n\t try {\n\t serverResponseDatetime = new Date( xhr.getResponseHeader( 'Date' ) );\n\t } catch( err ){}\n\t self.lastUpdateTime = serverResponseDatetime || new Date();\n\t self.checkForUpdates( options );\n\t });\n\t },\n\t\n\t /** continuously fetch updated contents every UPDATE_DELAY ms if this history's datasets or jobs are unfinished */\n\t checkForUpdates : function( options ){\n\t // console.log( this + '.checkForUpdates' );\n\t options = options || {};\n\t var delay = this.UPDATE_DELAY;\n\t var self = this;\n\t if( !self.id ){ return; }\n\t\n\t function _delayThenUpdate(){\n\t // prevent buildup of updater timeouts by clearing previous if any, then set new and cache id\n\t self.clearUpdateTimeout();\n\t self.updateTimeoutId = setTimeout( function(){\n\t self.refresh( options );\n\t }, delay );\n\t }\n\t\n\t // if there are still datasets in the non-ready state, recurse into this function with the new time\n\t var nonReadyContentCount = this.numOfUnfinishedShownContents();\n\t // console.log( 'nonReadyContentCount:', nonReadyContentCount );\n\t if( nonReadyContentCount > 0 ){\n\t _delayThenUpdate();\n\t\n\t } else {\n\t // no datasets are running, but currently runnning jobs may still produce new datasets\n\t // see if the history has any running jobs and continue to update if so\n\t // (also update the size for the user in either case)\n\t self._fetchContentRelatedAttributes()\n\t .done( function( historyData ){\n\t // console.log( 'non_ready_jobs:', historyData.non_ready_jobs );\n\t if( self.numOfUnfinishedJobs() > 0 ){\n\t _delayThenUpdate();\n\t\n\t } else {\n\t // otherwise, let listeners know that all updates have stopped\n\t self.trigger( 'ready' );\n\t }\n\t });\n\t }\n\t },\n\t\n\t /** clear the timeout and the cached timeout id */\n\t clearUpdateTimeout : function(){\n\t if( this.updateTimeoutId ){\n\t clearTimeout( this.updateTimeoutId );\n\t this.updateTimeoutId = null;\n\t }\n\t },\n\t\n\t // ........................................................................ ajax\n\t /** override to use actual Dates objects for create/update times */\n\t parse : function( response, options ){\n\t var parsed = Backbone.Model.prototype.parse.call( this, response, options );\n\t if( parsed.create_time ){\n\t parsed.create_time = new Date( parsed.create_time );\n\t }\n\t if( parsed.update_time ){\n\t parsed.update_time = new Date( parsed.update_time );\n\t }\n\t return parsed;\n\t },\n\t\n\t /** fetch this histories data (using options) then it's contents (using contentsOptions) */\n\t fetchWithContents : function( options, contentsOptions ){\n\t options = options || {};\n\t var self = this;\n\t\n\t // console.log( this + '.fetchWithContents' );\n\t // TODO: push down to a base class\n\t options.view = 'dev-detailed';\n\t\n\t // fetch history then use history data to fetch (paginated) contents\n\t return this.fetch( options ).then( function getContents( history ){\n\t self.contents.history = self;\n\t self.contents.setHistoryId( history.id );\n\t return self.fetchContents( contentsOptions );\n\t });\n\t },\n\t\n\t /** fetch this histories contents, adjusting options based on the stored history preferences */\n\t fetchContents : function( options ){\n\t options = options || {};\n\t var self = this;\n\t\n\t // we're updating, reset the update time\n\t self.lastUpdateTime = new Date();\n\t return self.contents.fetchCurrentPage( options );\n\t },\n\t\n\t /** save this history, _Mark_ing it as deleted (just a flag) */\n\t _delete : function( options ){\n\t if( this.get( 'deleted' ) ){ return jQuery.when(); }\n\t return this.save( { deleted: true }, options );\n\t },\n\t /** purge this history, _Mark_ing it as purged and removing all dataset data from the server */\n\t purge : function( options ){\n\t if( this.get( 'purged' ) ){ return jQuery.when(); }\n\t return this.save( { deleted: true, purged: true }, options );\n\t },\n\t /** save this history, _Mark_ing it as undeleted */\n\t undelete : function( options ){\n\t if( !this.get( 'deleted' ) ){ return jQuery.when(); }\n\t return this.save( { deleted: false }, options );\n\t },\n\t\n\t /** Make a copy of this history on the server\n\t * @param {Boolean} current if true, set the copy as the new current history (default: true)\n\t * @param {String} name name of new history (default: none - server sets to: Copy of )\n\t * @fires copied passed this history and the response JSON from the copy\n\t * @returns {xhr}\n\t */\n\t copy : function( current, name, allDatasets ){\n\t current = ( current !== undefined )?( current ):( true );\n\t if( !this.id ){\n\t throw new Error( 'You must set the history ID before copying it.' );\n\t }\n\t\n\t var postData = { history_id : this.id };\n\t if( current ){\n\t postData.current = true;\n\t }\n\t if( name ){\n\t postData.name = name;\n\t }\n\t if( !allDatasets ){\n\t postData.all_datasets = false;\n\t }\n\t postData.view = 'dev-detailed';\n\t\n\t var history = this;\n\t var copy = jQuery.post( this.urlRoot, postData );\n\t // if current - queue to setAsCurrent before firing 'copied'\n\t if( current ){\n\t return copy.then( function( response ){\n\t var newHistory = new History( response );\n\t return newHistory.setAsCurrent()\n\t .done( function(){\n\t history.trigger( 'copied', history, response );\n\t });\n\t });\n\t }\n\t return copy.done( function( response ){\n\t history.trigger( 'copied', history, response );\n\t });\n\t },\n\t\n\t setAsCurrent : function(){\n\t var history = this,\n\t xhr = jQuery.getJSON( Galaxy.root + 'history/set_as_current?id=' + this.id );\n\t\n\t xhr.done( function(){\n\t history.trigger( 'set-as-current', history );\n\t });\n\t return xhr;\n\t },\n\t\n\t // ........................................................................ misc\n\t toString : function(){\n\t return 'History(' + this.get( 'id' ) + ',' + this.get( 'name' ) + ')';\n\t }\n\t}));\n\t\n\t\n\t//==============================================================================\n\tvar _collectionSuper = CONTROLLED_FETCH_COLLECTION.InfinitelyScrollingCollection;\n\t/** @class A collection of histories (per user)\n\t * that maintains the current history as the first in the collection.\n\t * New or copied histories become the current history.\n\t */\n\tvar HistoryCollection = _collectionSuper.extend( BASE_MVC.LoggableMixin ).extend({\n\t _logNamespace : 'history',\n\t\n\t model : History,\n\t /** @type {String} initial order used by collection */\n\t order : 'update_time',\n\t /** @type {Number} limit used for the first fetch (or a reset) */\n\t limitOnFirstFetch : 10,\n\t /** @type {Number} limit used for each subsequent fetch */\n\t limitPerFetch : 10,\n\t\n\t initialize : function( models, options ){\n\t options = options || {};\n\t this.log( 'HistoryCollection.initialize', models, options );\n\t _collectionSuper.prototype.initialize.call( this, models, options );\n\t\n\t /** @type {boolean} should deleted histories be included */\n\t this.includeDeleted = options.includeDeleted || false;\n\t\n\t /** @type {String} encoded id of the history that's current */\n\t this.currentHistoryId = options.currentHistoryId;\n\t\n\t this.setUpListeners();\n\t // note: models are sent to reset *after* this fn ends; up to this point\n\t // the collection *is empty*\n\t },\n\t\n\t urlRoot : Galaxy.root + 'api/histories',\n\t url : function(){ return this.urlRoot; },\n\t\n\t /** set up reflexive event handlers */\n\t setUpListeners : function setUpListeners(){\n\t return this.on({\n\t // when a history is deleted, remove it from the collection (if optionally set to do so)\n\t 'change:deleted' : function( history ){\n\t // TODO: this becomes complicated when more filters are used\n\t this.debug( 'change:deleted', this.includeDeleted, history.get( 'deleted' ) );\n\t if( !this.includeDeleted && history.get( 'deleted' ) ){\n\t this.remove( history );\n\t }\n\t },\n\t // listen for a history copy, setting it to current\n\t 'copied' : function( original, newData ){\n\t this.setCurrent( new History( newData, [] ) );\n\t },\n\t // when a history is made current, track the id in the collection\n\t 'set-as-current' : function( history ){\n\t var oldCurrentId = this.currentHistoryId;\n\t this.trigger( 'no-longer-current', oldCurrentId );\n\t this.currentHistoryId = history.id;\n\t }\n\t });\n\t },\n\t\n\t /** override to change view */\n\t _buildFetchData : function( options ){\n\t return _.extend( _collectionSuper.prototype._buildFetchData.call( this, options ), {\n\t view : 'dev-detailed'\n\t });\n\t },\n\t\n\t /** override to filter out deleted and purged */\n\t _buildFetchFilters : function( options ){\n\t var superFilters = _collectionSuper.prototype._buildFetchFilters.call( this, options ) || {};\n\t var filters = {};\n\t if( !this.includeDeleted ){\n\t filters.deleted = false;\n\t filters.purged = false;\n\t } else {\n\t // force API to return both deleted and non\n\t //TODO: when the API is updated, remove this\n\t filters.deleted = null;\n\t }\n\t return _.defaults( superFilters, filters );\n\t },\n\t\n\t /** override to fetch current as well (as it may be outside the first 10, etc.) */\n\t fetchFirst : function( options ){\n\t var self = this;\n\t // TODO: batch?\n\t var xhr = $.when();\n\t if( this.currentHistoryId ){\n\t xhr = _collectionSuper.prototype.fetchFirst.call( self, {\n\t silent: true,\n\t limit : 1,\n\t filters: {\n\t // without these a deleted current history will return [] here and block the other xhr\n\t 'purged' : '',\n\t 'deleted' : '',\n\t 'encoded_id-in' : this.currentHistoryId,\n\t }\n\t });\n\t }\n\t return xhr.then( function(){\n\t options = options || {};\n\t options.offset = 0;\n\t return self.fetchMore( options );\n\t });\n\t },\n\t\n\t /** @type {Object} map of collection available sorting orders containing comparator fns */\n\t comparators : _.extend( _.clone( _collectionSuper.prototype.comparators ), {\n\t 'name' : BASE_MVC.buildComparator( 'name', { ascending: true }),\n\t 'name-dsc' : BASE_MVC.buildComparator( 'name', { ascending: false }),\n\t 'size' : BASE_MVC.buildComparator( 'size', { ascending: false }),\n\t 'size-asc' : BASE_MVC.buildComparator( 'size', { ascending: true }),\n\t }),\n\t\n\t /** override to always have the current history first */\n\t sort : function( options ){\n\t options = options || {};\n\t var silent = options.silent;\n\t var currentHistory = this.remove( this.get( this.currentHistoryId ) );\n\t _collectionSuper.prototype.sort.call( this, _.defaults({ silent: true }, options ) );\n\t this.unshift( currentHistory, { silent: true });\n\t if( !silent ){\n\t this.trigger( 'sort', this, options );\n\t }\n\t return this;\n\t },\n\t\n\t /** create a new history and by default set it to be the current history */\n\t create : function create( data, hdas, historyOptions, xhrOptions ){\n\t //TODO: .create is actually a collection function that's overridden here\n\t var collection = this,\n\t xhr = jQuery.getJSON( Galaxy.root + 'history/create_new_current' );\n\t return xhr.done( function( newData ){\n\t collection.setCurrent( new History( newData, [], historyOptions || {} ) );\n\t });\n\t },\n\t\n\t /** set the current history to the given history, placing it first in the collection.\n\t * Pass standard bbone options for use in unshift.\n\t * @triggers new-current passed history and this collection\n\t */\n\t setCurrent : function( history, options ){\n\t options = options || {};\n\t // new histories go in the front\n\t this.unshift( history, options );\n\t this.currentHistoryId = history.get( 'id' );\n\t if( !options.silent ){\n\t this.trigger( 'new-current', history, this );\n\t }\n\t return this;\n\t },\n\t\n\t toString: function toString(){\n\t return 'HistoryCollection(' + this.length + ',current:' + this.currentHistoryId + ')';\n\t }\n\t});\n\t\n\t\n\t//==============================================================================\n\treturn {\n\t History : History,\n\t HistoryCollection : HistoryCollection\n\t};}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(1), __webpack_require__(2), __webpack_require__(1)))\n\n/***/ },\n/* 76 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, _, $) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(42),\n\t __webpack_require__(127),\n\t __webpack_require__(6),\n\t __webpack_require__(5),\n\t __webpack_require__(85)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( LIST_ITEM, LoadingIndicator, BASE_MVC, _l ){\n\t\n\t'use strict';\n\t\n\tvar logNamespace = 'list';\n\t/* ============================================================================\n\tTODO:\n\t\n\t============================================================================ */\n\t/** @class View for a list/collection of models and the sub-views of those models.\n\t * Sub-views must (at least have the interface if not) inherit from ListItemView.\n\t * (For a list panel that also includes some 'container' model (History->HistoryContents)\n\t * use ModelWithListPanel)\n\t *\n\t * Allows for:\n\t * searching collection/sub-views\n\t * selecting/multi-selecting sub-views\n\t *\n\t * Currently used:\n\t * for dataset/dataset-choice\n\t * as superclass of ModelListPanel\n\t */\n\tvar ListPanel = Backbone.View.extend( BASE_MVC.LoggableMixin ).extend(/** @lends ListPanel.prototype */{\n\t _logNamespace : logNamespace,\n\t\n\t /** class to use for constructing the sub-views */\n\t viewClass : LIST_ITEM.ListItemView,\n\t /** class to used for constructing collection of sub-view models */\n\t collectionClass : Backbone.Collection,\n\t\n\t tagName : 'div',\n\t className : 'list-panel',\n\t\n\t /** (in ms) that jquery effects will use */\n\t fxSpeed : 'fast',\n\t\n\t /** string to display when the collection has no contents */\n\t emptyMsg : _l( 'This list is empty' ),\n\t /** displayed when no items match the search terms */\n\t noneFoundMsg : _l( 'No matching items found' ),\n\t /** string used for search placeholder */\n\t searchPlaceholder : _l( 'search' ),\n\t\n\t // ......................................................................... SET UP\n\t /** Set up the view, set up storage, bind listeners to HistoryContents events\n\t * @param {Object} attributes optional settings for the list\n\t */\n\t initialize : function( attributes, options ){\n\t attributes = attributes || {};\n\t // set the logger if requested\n\t if( attributes.logger ){\n\t this.logger = attributes.logger;\n\t }\n\t this.log( this + '.initialize:', attributes );\n\t\n\t // ---- instance vars\n\t /** how quickly should jquery fx run? */\n\t this.fxSpeed = _.has( attributes, 'fxSpeed' )?( attributes.fxSpeed ):( this.fxSpeed );\n\t\n\t /** filters for displaying subviews */\n\t this.filters = [];\n\t /** current search terms */\n\t this.searchFor = attributes.searchFor || '';\n\t\n\t /** loading indicator */\n\t // this.indicator = new LoadingIndicator( this.$el );\n\t\n\t /** currently showing selectors on items? */\n\t this.selecting = ( attributes.selecting !== undefined )? attributes.selecting : true;\n\t //this.selecting = false;\n\t\n\t /** cached selected item.model.ids to persist btwn renders */\n\t this.selected = attributes.selected || [];\n\t /** the last selected item.model.id */\n\t this.lastSelected = null;\n\t\n\t /** are sub-views draggable */\n\t this.dragItems = attributes.dragItems || false;\n\t\n\t /** list item view class (when passed models) */\n\t this.viewClass = attributes.viewClass || this.viewClass;\n\t\n\t /** list item views */\n\t this.views = [];\n\t /** list item models */\n\t this.collection = attributes.collection || this._createDefaultCollection();\n\t\n\t /** filter fns run over collection items to see if they should show in the list */\n\t this.filters = attributes.filters || [];\n\t\n\t /** override $scrollContainer fn via attributes - fn should return jq for elem to call scrollTo on */\n\t this.$scrollContainer = attributes.$scrollContainer || this.$scrollContainer;\n\t\n\t /** @type {String} generic title */\n\t this.title = attributes.title || '';\n\t /** @type {String} generic subtitle */\n\t this.subtitle = attributes.subtitle || '';\n\t\n\t this._setUpListeners();\n\t },\n\t\n\t // ------------------------------------------------------------------------ listeners\n\t /** create any event listeners for the list */\n\t _setUpListeners : function(){\n\t this.off();\n\t\n\t //TODO: move errorHandler down into list-view from history-view or\n\t // pass to global error handler (Galaxy)\n\t this.on({\n\t error: function( model, xhr, options, msg, details ){\n\t //this.errorHandler( model, xhr, options, msg, details );\n\t console.error( model, xhr, options, msg, details );\n\t },\n\t // show hide the loading indicator\n\t loading: function(){\n\t this._showLoadingIndicator( 'loading...', 40 );\n\t },\n\t 'loading-done': function(){\n\t this._hideLoadingIndicator( 40 );\n\t },\n\t });\n\t\n\t // throw the first render up as a diff namespace using once (for outside consumption)\n\t this.once( 'rendered', function(){\n\t this.trigger( 'rendered:initial', this );\n\t });\n\t\n\t this._setUpCollectionListeners();\n\t this._setUpViewListeners();\n\t return this;\n\t },\n\t\n\t /** create and return a collection for when none is initially passed */\n\t _createDefaultCollection : function(){\n\t // override\n\t return new this.collectionClass([]);\n\t },\n\t\n\t /** listening for collection events */\n\t _setUpCollectionListeners : function(){\n\t this.log( this + '._setUpCollectionListeners', this.collection );\n\t this.stopListening( this.collection );\n\t\n\t // bubble up error events\n\t this.listenTo( this.collection, {\n\t error : function( model, xhr, options, msg, details ){\n\t this.trigger( 'error', model, xhr, options, msg, details );\n\t },\n\t update : function( collection, options ){\n\t var changes = options.changes;\n\t // console.info( collection + ', update:', changes, '\\noptions:', options );\n\t // more than one: render everything\n\t if( options.renderAll || ( changes.added.length + changes.removed.length > 1 ) ){\n\t return this.renderItems();\n\t }\n\t // otherwise, let the single add/remove handlers do it\n\t if( changes.added.length === 1 ){\n\t return this.addItemView( _.first( changes.added ), collection, options );\n\t }\n\t if( changes.removed.length === 1 ){\n\t return this.removeItemView( _.first( changes.removed ), collection, options );\n\t }\n\t }\n\t });\n\t return this;\n\t },\n\t\n\t /** listening for sub-view events that bubble up with the 'view:' prefix */\n\t _setUpViewListeners : function(){\n\t this.log( this + '._setUpViewListeners' );\n\t\n\t // shift to select a range\n\t this.on({\n\t 'view:selected': function( view, ev ){\n\t if( ev && ev.shiftKey && this.lastSelected ){\n\t var lastSelectedView = this.viewFromModelId( this.lastSelected );\n\t if( lastSelectedView ){\n\t this.selectRange( view, lastSelectedView );\n\t }\n\t } else if( ev && ev.altKey && !this.selecting ){\n\t this.showSelectors();\n\t }\n\t this.selected.push( view.model.id );\n\t this.lastSelected = view.model.id;\n\t },\n\t\n\t 'view:de-selected': function( view, ev ){\n\t this.selected = _.without( this.selected, view.model.id );\n\t }\n\t });\n\t },\n\t\n\t // ------------------------------------------------------------------------ rendering\n\t /** Render this content, set up ui.\n\t * @param {Number or String} speed the speed of the render\n\t */\n\t render : function( speed ){\n\t this.log( this + '.render', speed );\n\t var $newRender = this._buildNewRender();\n\t this._setUpBehaviors( $newRender );\n\t this._queueNewRender( $newRender, speed );\n\t return this;\n\t },\n\t\n\t /** Build a temp div containing the new children for the view's $el. */\n\t _buildNewRender : function(){\n\t this.debug( this + '(ListPanel)._buildNewRender' );\n\t var $newRender = $( this.templates.el( {}, this ) );\n\t this._renderControls( $newRender );\n\t this._renderTitle( $newRender );\n\t this._renderSubtitle( $newRender );\n\t this._renderSearch( $newRender );\n\t this.renderItems( $newRender );\n\t return $newRender;\n\t },\n\t\n\t /** Build a temp div containing the new children for the view's $el. */\n\t _renderControls : function( $newRender ){\n\t this.debug( this + '(ListPanel)._renderControls' );\n\t var $controls = $( this.templates.controls( {}, this ) );\n\t $newRender.find( '.controls' ).replaceWith( $controls );\n\t return $controls;\n\t },\n\t\n\t /** return a jQuery object containing the title DOM */\n\t _renderTitle : function( $where ){\n\t //$where = $where || this.$el;\n\t //$where.find( '.title' ).replaceWith( ... )\n\t },\n\t\n\t /** return a jQuery object containing the subtitle DOM (if any) */\n\t _renderSubtitle : function( $where ){\n\t //$where = $where || this.$el;\n\t //$where.find( '.title' ).replaceWith( ... )\n\t },\n\t\n\t /** Fade out the old el, swap in the new contents, then fade in.\n\t * @param {Number or String} speed jq speed to use for rendering effects\n\t * @fires rendered when rendered\n\t */\n\t _queueNewRender : function( $newRender, speed ) {\n\t speed = ( speed === undefined )?( this.fxSpeed ):( speed );\n\t var panel = this;\n\t panel.log( '_queueNewRender:', $newRender, speed );\n\t\n\t $( panel ).queue( 'fx', [\n\t function( next ){\n\t panel.$el.fadeOut( speed, next );\n\t },\n\t function( next ){\n\t panel._swapNewRender( $newRender );\n\t next();\n\t },\n\t function( next ){\n\t panel.$el.fadeIn( speed, next );\n\t },\n\t function( next ){\n\t panel.trigger( 'rendered', panel );\n\t next();\n\t }\n\t ]);\n\t },\n\t\n\t /** empty out the current el, move the $newRender's children in */\n\t _swapNewRender : function( $newRender ){\n\t this.$el.empty().attr( 'class', this.className ).append( $newRender.children() );\n\t if( this.selecting ){ this.showSelectors( 0 ); }\n\t return this;\n\t },\n\t\n\t /** Set up any behaviors, handlers (ep. plugins) that need to be called when the entire view has been built but\n\t * not attached to the page yet.\n\t */\n\t _setUpBehaviors : function( $where ){\n\t $where = $where || this.$el;\n\t this.$controls( $where ).find('[title]').tooltip();\n\t // set up the pupup for actions available when multi selecting\n\t this._renderMultiselectActionMenu( $where );\n\t return this;\n\t },\n\t\n\t /** render a menu containing the actions available to sets of selected items */\n\t _renderMultiselectActionMenu : function( $where ){\n\t $where = $where || this.$el;\n\t var $menu = $where.find( '.list-action-menu' ),\n\t actions = this.multiselectActions();\n\t if( !actions.length ){\n\t return $menu.empty();\n\t }\n\t\n\t var $newMenu = $([\n\t '
    ',\n\t '',\n\t '
      ', '
    ',\n\t '
    '\n\t ].join(''));\n\t var $actions = actions.map( function( action ){\n\t var html = [ '
  • ', action.html, '
  • ' ].join( '' );\n\t return $( html ).click( function( ev ){\n\t ev.preventDefault();\n\t return action.func( ev );\n\t });\n\t });\n\t $newMenu.find( 'ul' ).append( $actions );\n\t $menu.replaceWith( $newMenu );\n\t return $newMenu;\n\t },\n\t\n\t /** return a list of plain objects used to render multiselect actions menu. Each object should have:\n\t * html: an html string used as the anchor contents\n\t * func: a function called when the anchor is clicked (passed the click event)\n\t */\n\t multiselectActions : function(){\n\t return [];\n\t },\n\t\n\t // ------------------------------------------------------------------------ sub-$element shortcuts\n\t /** the scroll container for this panel - can be $el, $el.parent(), or grandparent depending on context */\n\t $scrollContainer : function( $where ){\n\t // override or set via attributes.$scrollContainer\n\t return ( $where || this.$el ).parent().parent();\n\t },\n\t /** convenience selector for the section that displays the list controls */\n\t $controls : function( $where ){\n\t return ( $where || this.$el ).find( '> .controls' );\n\t },\n\t /** list-items: where the subviews are contained in the view's dom */\n\t $list : function( $where ){\n\t return ( $where || this.$el ).find( '> .list-items' );\n\t },\n\t /** container where list messages are attached */\n\t $messages : function( $where ){\n\t //TODO: controls isn't really correct here (only for ModelListPanel)\n\t return ( $where || this.$el ).find( '> .controls .messages' );\n\t },\n\t /** the message displayed when no views can be shown (no views, none matching search) */\n\t $emptyMessage : function( $where ){\n\t return ( $where || this.$el ).find( '> .empty-message' );\n\t },\n\t\n\t // ------------------------------------------------------------------------ hda sub-views\n\t /** render the subviews for the list's collection */\n\t renderItems : function( $whereTo ){\n\t $whereTo = $whereTo || this.$el;\n\t var panel = this;\n\t panel.log( this + '.renderItems', $whereTo );\n\t\n\t var $list = panel.$list( $whereTo );\n\t panel.freeViews();\n\t // console.log( 'views freed' );\n\t //TODO:? cache and re-use views?\n\t var shownModels = panel._filterCollection();\n\t // console.log( 'models filtered:', shownModels );\n\t\n\t panel.views = shownModels.map( function( itemModel ){\n\t var view = panel._createItemView( itemModel );\n\t return view;\n\t });\n\t\n\t $list.empty();\n\t // console.log( 'list emptied' );\n\t if( panel.views.length ){\n\t panel._attachItems( $whereTo );\n\t // console.log( 'items attached' );\n\t }\n\t panel._renderEmptyMessage( $whereTo ).toggle( !panel.views.length );\n\t panel.trigger( 'views:ready', panel.views );\n\t\n\t // console.log( '------------------------------------------- rendering items' );\n\t return panel.views;\n\t },\n\t\n\t /** Filter the collection to only those models that should be currently viewed */\n\t _filterCollection : function(){\n\t // override this\n\t var panel = this;\n\t return panel.collection.filter( _.bind( panel._filterItem, panel ) );\n\t },\n\t\n\t /** Should the model be viewable in the current state?\n\t * Checks against this.filters and this.searchFor\n\t */\n\t _filterItem : function( model ){\n\t // override this\n\t var panel = this;\n\t return ( _.every( panel.filters.map( function( fn ){ return fn.call( model ); }) ) )\n\t && ( !panel.searchFor || model.matchesAll( panel.searchFor ) );\n\t },\n\t\n\t /** Create a view for a model and set up it's listeners */\n\t _createItemView : function( model ){\n\t var ViewClass = this._getItemViewClass( model );\n\t var options = _.extend( this._getItemViewOptions( model ), {\n\t model : model\n\t });\n\t var view = new ViewClass( options );\n\t this._setUpItemViewListeners( view );\n\t return view;\n\t },\n\t\n\t /** Free a view for a model. Note: does not remove it from the DOM */\n\t _destroyItemView : function( view ){\n\t this.stopListening( view );\n\t this.views = _.without( this.views, view );\n\t },\n\t\n\t _destroyItemViews : function( view ){\n\t var self = this;\n\t self.views.forEach( function( v ){\n\t self.stopListening( v );\n\t });\n\t self.views = [];\n\t return self;\n\t },\n\t\n\t /** free any sub-views the list has */\n\t freeViews : function(){\n\t return this._destroyItemViews();\n\t },\n\t\n\t /** Get the bbone view class based on the model */\n\t _getItemViewClass : function( model ){\n\t // override this\n\t return this.viewClass;\n\t },\n\t\n\t /** Get the options passed to the new view based on the model */\n\t _getItemViewOptions : function( model ){\n\t // override this\n\t return {\n\t //logger : this.logger,\n\t fxSpeed : this.fxSpeed,\n\t expanded : false,\n\t selectable : this.selecting,\n\t selected : _.contains( this.selected, model.id ),\n\t draggable : this.dragItems\n\t };\n\t },\n\t\n\t /** Set up listeners for new models */\n\t _setUpItemViewListeners : function( view ){\n\t var panel = this;\n\t // send all events to the panel, re-namspaceing them with the view prefix\n\t this.listenTo( view, 'all', function(){\n\t var args = Array.prototype.slice.call( arguments, 0 );\n\t args[0] = 'view:' + args[0];\n\t panel.trigger.apply( panel, args );\n\t });\n\t\n\t // drag multiple - hijack ev.setData to add all selected items\n\t this.listenTo( view, 'draggable:dragstart', function( ev, v ){\n\t //TODO: set multiple drag data here\n\t var json = {},\n\t selected = this.getSelectedModels();\n\t if( selected.length ){\n\t json = selected.toJSON();\n\t } else {\n\t json = [ v.model.toJSON() ];\n\t }\n\t ev.dataTransfer.setData( 'text', JSON.stringify( json ) );\n\t //ev.dataTransfer.setDragImage( v.el, 60, 60 );\n\t }, this );\n\t\n\t return panel;\n\t },\n\t\n\t /** Attach views in this.views to the model based on $whereTo */\n\t _attachItems : function( $whereTo ){\n\t var self = this;\n\t // console.log( '_attachItems:', $whereTo, this.$list( $whereTo ) );\n\t //ASSUMES: $list has been emptied\n\t this.$list( $whereTo ).append( this.views.map( function( view ){\n\t return self._renderItemView$el( view );\n\t }));\n\t return this;\n\t },\n\t\n\t /** get a given subview's $el (or whatever may wrap it) and return it */\n\t _renderItemView$el : function( view ){\n\t // useful to wrap and override\n\t return view.render(0).$el;\n\t },\n\t\n\t /** render the empty/none-found message */\n\t _renderEmptyMessage : function( $whereTo ){\n\t this.debug( '_renderEmptyMessage', $whereTo, this.searchFor );\n\t var text = this.searchFor? this.noneFoundMsg : this.emptyMsg;\n\t return this.$emptyMessage( $whereTo ).text( text );\n\t },\n\t\n\t /** expand all item views */\n\t expandAll : function(){\n\t _.each( this.views, function( view ){\n\t view.expand();\n\t });\n\t },\n\t\n\t /** collapse all item views */\n\t collapseAll : function(){\n\t _.each( this.views, function( view ){\n\t view.collapse();\n\t });\n\t },\n\t\n\t // ------------------------------------------------------------------------ collection/views syncing\n\t /** Add a view (if the model should be viewable) to the panel */\n\t addItemView : function( model, collection, options ){\n\t // console.log( this + '.addItemView:', model );\n\t var panel = this;\n\t // get the index of the model in the list of filtered models shown by this list\n\t // in order to insert the view in the proper place\n\t //TODO:? potentially expensive\n\t var modelIndex = panel._filterCollection().indexOf( model );\n\t if( modelIndex === -1 ){ return undefined; }\n\t var view = panel._createItemView( model );\n\t // console.log( 'adding and rendering:', modelIndex, view.toString() );\n\t\n\t $( view ).queue( 'fx', [\n\t function( next ){\n\t // hide the empty message first if only view\n\t if( panel.$emptyMessage().is( ':visible' ) ){\n\t panel.$emptyMessage().fadeOut( panel.fxSpeed, next );\n\t } else {\n\t next();\n\t }\n\t },\n\t function( next ){\n\t panel._attachView( view, modelIndex );\n\t next();\n\t }\n\t ]);\n\t return view;\n\t },\n\t\n\t /** internal fn to add view (to both panel.views and panel.$list) */\n\t _attachView : function( view, modelIndex, useFx ){\n\t // console.log( this + '._attachView:', view, modelIndex, useFx );\n\t useFx = _.isUndefined( useFx )? true : useFx;\n\t modelIndex = modelIndex || 0;\n\t var panel = this;\n\t\n\t // use the modelIndex to splice into views and insert at the proper index in the DOM\n\t panel.views.splice( modelIndex, 0, view );\n\t panel._insertIntoListAt( modelIndex, panel._renderItemView$el( view ).hide() );\n\t\n\t panel.trigger( 'view:attached', view );\n\t if( useFx ){\n\t view.$el.slideDown( panel.fxSpeed, function(){\n\t panel.trigger( 'view:attached:rendered' );\n\t });\n\t } else {\n\t view.$el.show();\n\t panel.trigger( 'view:attached:rendered' );\n\t }\n\t return view;\n\t },\n\t\n\t /** insert a jq object as a child of list-items at the specified *DOM index* */\n\t _insertIntoListAt : function( index, $what ){\n\t // console.log( this + '._insertIntoListAt:', index, $what );\n\t var $list = this.$list();\n\t if( index === 0 ){\n\t $list.prepend( $what );\n\t } else {\n\t $list.children().eq( index - 1 ).after( $what );\n\t }\n\t return $what;\n\t },\n\t\n\t /** Remove a view from the panel (if found) */\n\t removeItemView : function( model, collection, options ){\n\t var panel = this;\n\t var view = _.find( panel.views, function( v ){ return v.model === model; });\n\t if( !view ){ return undefined; }\n\t panel.views = _.without( panel.views, view );\n\t panel.trigger( 'view:removed', view );\n\t\n\t // potentially show the empty message if no views left\n\t // use anonymous queue here - since remove can happen multiple times\n\t $({}).queue( 'fx', [\n\t function( next ){\n\t view.$el.fadeOut( panel.fxSpeed, next );\n\t },\n\t function( next ){\n\t view.remove();\n\t panel.trigger( 'view:removed:rendered' );\n\t if( !panel.views.length ){\n\t panel._renderEmptyMessage().fadeIn( panel.fxSpeed, next );\n\t } else {\n\t next();\n\t }\n\t }\n\t ]);\n\t return view;\n\t },\n\t\n\t /** get views based on model.id */\n\t viewFromModelId : function( id ){\n\t return _.find( this.views, function( v ){ return v.model.id === id; });\n\t },\n\t\n\t /** get views based on model */\n\t viewFromModel : function( model ){\n\t return model ? this.viewFromModelId( model.id ) : undefined;\n\t },\n\t\n\t /** get views based on model properties */\n\t viewsWhereModel : function( properties ){\n\t return this.views.filter( function( view ){\n\t return _.isMatch( view.model.attributes, properties );\n\t });\n\t },\n\t\n\t /** A range of views between (and including) viewA and viewB */\n\t viewRange : function( viewA, viewB ){\n\t if( viewA === viewB ){ return ( viewA )?( [ viewA ] ):( [] ); }\n\t\n\t var indexA = this.views.indexOf( viewA ),\n\t indexB = this.views.indexOf( viewB );\n\t\n\t // handle not found\n\t if( indexA === -1 || indexB === -1 ){\n\t if( indexA === indexB ){ return []; }\n\t return ( indexA === -1 )?( [ viewB ] ):( [ viewA ] );\n\t }\n\t // reverse if indeces are\n\t //note: end inclusive\n\t return ( indexA < indexB )?\n\t this.views.slice( indexA, indexB + 1 ) :\n\t this.views.slice( indexB, indexA + 1 );\n\t },\n\t\n\t // ------------------------------------------------------------------------ searching\n\t /** render a search input for filtering datasets shown\n\t * (see SearchableMixin in base-mvc for implementation of the actual searching)\n\t * return will start the search\n\t * esc will clear the search\n\t * clicking the clear button will clear the search\n\t * uses searchInput in ui.js\n\t */\n\t _renderSearch : function( $where ){\n\t $where.find( '.controls .search-input' ).searchInput({\n\t placeholder : this.searchPlaceholder,\n\t initialVal : this.searchFor,\n\t onfirstsearch : _.bind( this._firstSearch, this ),\n\t onsearch : _.bind( this.searchItems, this ),\n\t onclear : _.bind( this.clearSearch, this )\n\t });\n\t return $where;\n\t },\n\t\n\t /** What to do on the first search entered */\n\t _firstSearch : function( searchFor ){\n\t // override to load model details if necc.\n\t this.log( 'onFirstSearch', searchFor );\n\t return this.searchItems( searchFor );\n\t },\n\t\n\t /** filter view list to those that contain the searchFor terms */\n\t searchItems : function( searchFor, force ){\n\t this.log( 'searchItems', searchFor, this.searchFor, force );\n\t if( !force && this.searchFor === searchFor ){ return this; }\n\t this.searchFor = searchFor;\n\t this.renderItems();\n\t this.trigger( 'search:searching', searchFor, this );\n\t var $search = this.$( '> .controls .search-query' );\n\t if( $search.val() !== searchFor ){\n\t $search.val( searchFor );\n\t }\n\t return this;\n\t },\n\t\n\t /** clear the search filters and show all views that are normally shown */\n\t clearSearch : function( searchFor ){\n\t //this.log( 'onSearchClear', this );\n\t this.searchFor = '';\n\t this.trigger( 'search:clear', this );\n\t this.$( '> .controls .search-query' ).val( '' );\n\t this.renderItems();\n\t return this;\n\t },\n\t\n\t // ------------------------------------------------------------------------ selection\n\t /** @type Integer when the number of list item views is >= to this, don't animate selectors */\n\t THROTTLE_SELECTOR_FX_AT : 20,\n\t\n\t /** show selectors on all visible itemViews and associated controls */\n\t showSelectors : function( speed ){\n\t speed = ( speed !== undefined )?( speed ):( this.fxSpeed );\n\t this.selecting = true;\n\t this.$( '.list-actions' ).slideDown( speed );\n\t speed = this.views.length >= this.THROTTLE_SELECTOR_FX_AT? 0 : speed;\n\t _.each( this.views, function( view ){\n\t view.showSelector( speed );\n\t });\n\t //this.selected = [];\n\t //this.lastSelected = null;\n\t },\n\t\n\t /** hide selectors on all visible itemViews and associated controls */\n\t hideSelectors : function( speed ){\n\t speed = ( speed !== undefined )?( speed ):( this.fxSpeed );\n\t this.selecting = false;\n\t this.$( '.list-actions' ).slideUp( speed );\n\t speed = this.views.length >= this.THROTTLE_SELECTOR_FX_AT? 0 : speed;\n\t _.each( this.views, function( view ){\n\t view.hideSelector( speed );\n\t });\n\t this.selected = [];\n\t this.lastSelected = null;\n\t },\n\t\n\t /** show or hide selectors on all visible itemViews and associated controls */\n\t toggleSelectors : function(){\n\t if( !this.selecting ){\n\t this.showSelectors();\n\t } else {\n\t this.hideSelectors();\n\t }\n\t },\n\t\n\t /** select all visible items */\n\t selectAll : function( event ){\n\t _.each( this.views, function( view ){\n\t view.select( event );\n\t });\n\t },\n\t\n\t /** deselect all visible items */\n\t deselectAll : function( event ){\n\t this.lastSelected = null;\n\t _.each( this.views, function( view ){\n\t view.deselect( event );\n\t });\n\t },\n\t\n\t /** select a range of datasets between A and B */\n\t selectRange : function( viewA, viewB ){\n\t var range = this.viewRange( viewA, viewB );\n\t _.each( range, function( view ){\n\t view.select();\n\t });\n\t return range;\n\t },\n\t\n\t /** return an array of all currently selected itemViews */\n\t getSelectedViews : function(){\n\t return _.filter( this.views, function( v ){\n\t return v.selected;\n\t });\n\t },\n\t\n\t /** return a collection of the models of all currenly selected items */\n\t getSelectedModels : function(){\n\t // console.log( '(getSelectedModels)' );\n\t return new this.collection.constructor( _.map( this.getSelectedViews(), function( view ){\n\t return view.model;\n\t }));\n\t },\n\t\n\t // ------------------------------------------------------------------------ loading indicator\n\t /** hide the $el and display a loading indicator (in the $el's parent) when loading new data */\n\t _showLoadingIndicator : function( msg, speed, callback ){\n\t this.debug( '_showLoadingIndicator', this.indicator, msg, speed, callback );\n\t speed = ( speed !== undefined )?( speed ):( this.fxSpeed );\n\t if( !this.indicator ){\n\t this.indicator = new LoadingIndicator( this.$el );\n\t this.debug( '\\t created', this.indicator );\n\t }\n\t if( !this.$el.is( ':visible' ) ){\n\t this.indicator.show( 0, callback );\n\t } else {\n\t this.$el.fadeOut( speed );\n\t this.indicator.show( msg, speed, callback );\n\t }\n\t },\n\t\n\t /** hide the loading indicator */\n\t _hideLoadingIndicator : function( speed, callback ){\n\t this.debug( '_hideLoadingIndicator', this.indicator, speed, callback );\n\t speed = ( speed !== undefined )?( speed ):( this.fxSpeed );\n\t if( this.indicator ){\n\t this.indicator.hide( speed, callback );\n\t }\n\t },\n\t\n\t // ------------------------------------------------------------------------ scrolling\n\t /** get the current scroll position of the panel in its parent */\n\t scrollPosition : function(){\n\t return this.$scrollContainer().scrollTop();\n\t },\n\t\n\t /** set the current scroll position of the panel in its parent */\n\t scrollTo : function( pos, speed ){\n\t speed = speed || 0;\n\t this.$scrollContainer().animate({ scrollTop: pos }, speed );\n\t return this;\n\t },\n\t\n\t /** Scrolls the panel to the top. */\n\t scrollToTop : function( speed ){\n\t return this.scrollTo( 0, speed );\n\t },\n\t\n\t /** scroll to the given view in list-items */\n\t scrollToItem : function( view, speed ){\n\t if( !view ){ return this; }\n\t return this;\n\t },\n\t\n\t /** Scrolls the panel to show the content with the given id. */\n\t scrollToId : function( id, speed ){\n\t return this.scrollToItem( this.viewFromModelId( id ), speed );\n\t },\n\t\n\t // ------------------------------------------------------------------------ panel events\n\t /** event map */\n\t events : {\n\t 'click .select-all' : 'selectAll',\n\t 'click .deselect-all' : 'deselectAll'\n\t },\n\t\n\t // ------------------------------------------------------------------------ misc\n\t /** Return a string rep of the panel */\n\t toString : function(){\n\t return 'ListPanel(' + this.collection + ')';\n\t }\n\t});\n\t\n\t// ............................................................................ TEMPLATES\n\t/** underscore templates */\n\tListPanel.prototype.templates = (function(){\n\t\n\t var elTemplate = BASE_MVC.wrapTemplate([\n\t // temp container\n\t '
    ',\n\t '
    ',\n\t '
    ',\n\t '
    ',\n\t '
    '\n\t ]);\n\t\n\t var controlsTemplate = BASE_MVC.wrapTemplate([\n\t '
    ',\n\t '
    ',\n\t '
    <%- view.title %>
    ',\n\t '
    ',\n\t '
    <%- view.subtitle %>
    ',\n\t // buttons, controls go here\n\t '
    ',\n\t // deleted msg, etc.\n\t '
    ',\n\t\n\t '
    ',\n\t '
    ',\n\t '
    ',\n\t\n\t // show when selectors are shown\n\t '
    ',\n\t '
    ',\n\t '',\n\t '',\n\t '
    ',\n\t '
    ',\n\t '
    ',\n\t '
    ',\n\t '
    '\n\t ]);\n\t\n\t return {\n\t el : elTemplate,\n\t controls : controlsTemplate\n\t };\n\t}());\n\t\n\t\n\t//=============================================================================\n\t/** View for a model that has a sub-collection (e.g. History, DatasetCollection)\n\t * Allows:\n\t * the model to be reset\n\t * auto assign panel.collection to panel.model[ panel.modelCollectionKey ]\n\t *\n\t */\n\tvar ModelListPanel = ListPanel.extend({\n\t\n\t /** key of attribute in model to assign to this.collection */\n\t modelCollectionKey : 'contents',\n\t\n\t initialize : function( attributes ){\n\t ListPanel.prototype.initialize.call( this, attributes );\n\t this.selecting = ( attributes.selecting !== undefined )? attributes.selecting : false;\n\t\n\t this.setModel( this.model, attributes );\n\t },\n\t\n\t /** release/free/shutdown old models and set up panel for new models\n\t * @fires new-model with the panel as parameter\n\t */\n\t setModel : function( model, attributes ){\n\t attributes = attributes || {};\n\t this.debug( this + '.setModel:', model, attributes );\n\t\n\t this.freeModel();\n\t this.freeViews();\n\t\n\t if( model ){\n\t var oldModelId = this.model? this.model.get( 'id' ): null;\n\t\n\t // set up the new model with user, logger, storage, events\n\t this.model = model;\n\t if( this.logger ){\n\t this.model.logger = this.logger;\n\t }\n\t this._setUpModelListeners();\n\t\n\t //TODO: relation btwn model, collection becoming tangled here\n\t // free the collection, and assign the new collection to either\n\t // the model[ modelCollectionKey ], attributes.collection, or an empty vanilla collection\n\t this.stopListening( this.collection );\n\t this.collection = this.model[ this.modelCollectionKey ]\n\t || attributes.collection\n\t || this._createDefaultCollection();\n\t this._setUpCollectionListeners();\n\t\n\t if( oldModelId && model.get( 'id' ) !== oldModelId ){\n\t this.trigger( 'new-model', this );\n\t }\n\t }\n\t return this;\n\t },\n\t\n\t /** free the current model and all listeners for it, free any views for the model */\n\t freeModel : function(){\n\t // stop/release the previous model, and clear cache to sub-views\n\t if( this.model ){\n\t this.stopListening( this.model );\n\t //TODO: see base-mvc\n\t //this.model.free();\n\t //this.model = null;\n\t }\n\t return this;\n\t },\n\t\n\t // ------------------------------------------------------------------------ listening\n\t /** listening for model events */\n\t _setUpModelListeners : function(){\n\t // override\n\t this.log( this + '._setUpModelListeners', this.model );\n\t // bounce model errors up to the panel\n\t this.listenTo( this.model, 'error', function(){\n\t var args = Array.prototype.slice.call( arguments, 0 );\n\t //args.unshift( 'model:error' );\n\t args.unshift( 'error' );\n\t this.trigger.apply( this, args );\n\t }, this );\n\t\n\t // debugging\n\t if( this.logger ){\n\t this.listenTo( this.model, 'all', function( event ){\n\t this.info( this + '(model)', event, arguments );\n\t });\n\t }\n\t return this;\n\t },\n\t\n\t /** Build a temp div containing the new children for the view's $el.\n\t */\n\t _renderControls : function( $newRender ){\n\t this.debug( this + '(ModelListPanel)._renderControls' );\n\t var json = this.model? this.model.toJSON() : {},\n\t $controls = $( this.templates.controls( json, this ) );\n\t $newRender.find( '.controls' ).replaceWith( $controls );\n\t return $controls;\n\t },\n\t\n\t // ------------------------------------------------------------------------ misc\n\t /** Return a string rep of the panel */\n\t toString : function(){\n\t return 'ModelListPanel(' + this.model + ')';\n\t }\n\t});\n\t\n\t// ............................................................................ TEMPLATES\n\t/** underscore templates */\n\tModelListPanel.prototype.templates = (function(){\n\t\n\t var controlsTemplate = BASE_MVC.wrapTemplate([\n\t '
    ',\n\t '
    ',\n\t //TODO: this is really the only difference - consider factoring titlebar out\n\t '
    <%- model.name %>
    ',\n\t '
    ',\n\t '
    <%- view.subtitle %>
    ',\n\t '
    ',\n\t '
    ',\n\t\n\t '
    ',\n\t '
    ',\n\t '
    ',\n\t\n\t '
    ',\n\t '
    ',\n\t '',\n\t '',\n\t '
    ',\n\t '
    ',\n\t '
    ',\n\t '
    ',\n\t '
    '\n\t ]);\n\t\n\t return _.extend( _.clone( ListPanel.prototype.templates ), {\n\t controls : controlsTemplate\n\t });\n\t}());\n\t\n\t\n\t//=============================================================================\n\t return {\n\t ListPanel : ListPanel,\n\t ModelListPanel : ModelListPanel\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(2), __webpack_require__(1)))\n\n/***/ },\n/* 77 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, _) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(6),\n\t __webpack_require__(5)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( baseMVC, _l ){\n\t// =============================================================================\n\t/** A view on any model that has a 'tags' attribute (a list of tag strings)\n\t * Incorporates the select2 jQuery plugin for tags display/editing:\n\t * http://ivaynberg.github.io/select2/\n\t */\n\tvar TagsEditor = Backbone.View\n\t .extend( baseMVC.LoggableMixin )\n\t .extend( baseMVC.HiddenUntilActivatedViewMixin ).extend({\n\t\n\t tagName : 'div',\n\t className : 'tags-display',\n\t\n\t /** Set up listeners, parse options */\n\t initialize : function( options ){\n\t //console.debug( this, options );\n\t // only listen to the model only for changes to tags - re-render\n\t this.listenTo( this.model, 'change:tags', function(){\n\t this.render();\n\t });\n\t this.hiddenUntilActivated( options.$activator, options );\n\t },\n\t\n\t /** Build the DOM elements, call select to on the created input, and set up behaviors */\n\t render : function(){\n\t var view = this;\n\t this.$el.html( this._template() );\n\t\n\t this.$input().select2({\n\t placeholder : 'Add tags',\n\t width : '100%',\n\t tags : function(){\n\t // initialize possible tags in the dropdown based on all the tags the user has used so far\n\t return view._getTagsUsed();\n\t }\n\t });\n\t\n\t this._setUpBehaviors();\n\t return this;\n\t },\n\t\n\t /** @returns {String} the html text used to build the view's DOM */\n\t _template : function(){\n\t return [\n\t //TODO: make prompt optional\n\t '',\n\t // set up initial tags by adding as CSV to input vals (necc. to init select2)\n\t ''\n\t ].join( '' );\n\t },\n\t\n\t /** @returns {String} the sorted, comma-separated tags from the model */\n\t tagsToCSV : function(){\n\t var tagsArray = this.model.get( 'tags' );\n\t if( !_.isArray( tagsArray ) || _.isEmpty( tagsArray ) ){\n\t return '';\n\t }\n\t return tagsArray.map( function( tag ){\n\t return _.escape( tag );\n\t }).sort().join( ',' );\n\t },\n\t\n\t /** @returns {jQuery} the input for this view */\n\t $input : function(){\n\t return this.$el.find( 'input.tags-input' );\n\t },\n\t\n\t /** @returns {String[]} all tags used by the current user */\n\t _getTagsUsed : function(){\n\t//TODO: global\n\t return Galaxy.user.get( 'tags_used' );\n\t },\n\t\n\t /** set up any event listeners on the view's DOM (mostly handled by select2) */\n\t _setUpBehaviors : function(){\n\t var view = this;\n\t this.$input().on( 'change', function( event ){\n\t // save the model's tags in either remove or added event\n\t view.model.save({ tags: event.val }, { silent: true });\n\t // if it's new, add the tag to the users tags\n\t if( event.added ){\n\t //??: solve weird behavior in FF on test.galaxyproject.org where\n\t // event.added.text is string object: 'String{ 0=\"o\", 1=\"n\", 2=\"e\" }'\n\t view._addNewTagToTagsUsed( event.added.text + '' );\n\t }\n\t });\n\t },\n\t\n\t /** add a new tag (if not already there) to the list of all tags used by the user\n\t * @param {String} newTag the tag to add to the list of used\n\t */\n\t _addNewTagToTagsUsed : function( newTag ){\n\t//TODO: global\n\t var tagsUsed = Galaxy.user.get( 'tags_used' );\n\t if( !_.contains( tagsUsed, newTag ) ){\n\t tagsUsed.push( newTag );\n\t tagsUsed.sort();\n\t Galaxy.user.set( 'tags_used', tagsUsed );\n\t }\n\t },\n\t\n\t /** shut down event listeners and remove this view's DOM */\n\t remove : function(){\n\t this.$input.off();\n\t this.stopListening( this.model );\n\t Backbone.View.prototype.remove.call( this );\n\t },\n\t\n\t /** string rep */\n\t toString : function(){ return [ 'TagsEditor(', this.model + '', ')' ].join(''); }\n\t});\n\t\n\t// =============================================================================\n\treturn {\n\t TagsEditor : TagsEditor\n\t};\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(2)))\n\n/***/ },\n/* 78 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function($, _) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(5)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( _l ){\n\t'use strict';\n\t\n\t//TODO: toastr is another possibility - I didn't see where I might add details, tho\n\t\n\t/* ============================================================================\n\tError modals meant to replace the o-so-easy alerts.\n\t\n\tThese are currently styled as errormessages but use the Galaxy.modal\n\tinfrastructure to be shown/closed. They're capable of showing details in a\n\ttogglable dropdown and the details are formatted in a pre.\n\t\n\tExample:\n\t errorModal( 'Heres a message', 'A Title', { some_details: 'here' });\n\t errorModal( 'Heres a message' ); // no details, title is 'Error'\n\t\n\tThere are three specialized forms:\n\t offlineErrorModal a canned response for when there's no connection\n\t badGatewayErrorModal canned response for when Galaxy is restarting\n\t ajaxErrorModal plugable into any Backbone class as an\n\t error event handler by accepting the error args: model, xhr, options\n\t\n\tExamples:\n\t if( navigator.offLine ){ offlineErrorModal(); }\n\t if( xhr.status === 502 ){ badGatewayErrorModal(); }\n\t this.listenTo( this.model, 'error', ajaxErrorModal );\n\t\n\t============================================================================ */\n\t\n\tvar CONTACT_MSG = _l( 'Please contact a Galaxy administrator if the problem persists.' );\n\tvar DEFAULT_AJAX_ERR_MSG = _l( 'An error occurred while updating information with the server.' );\n\tvar DETAILS_MSG = _l( 'The following information can assist the developers in finding the source of the error:' );\n\t\n\t/** private helper that builds the modal and handles adding details */\n\tfunction _errorModal( message, title, details ){\n\t // create and return the modal, adding details button only if needed\n\t Galaxy.modal.show({\n\t title : title,\n\t body : message,\n\t closing_events : true,\n\t buttons : { Ok: function(){ Galaxy.modal.hide(); } },\n\t });\n\t Galaxy.modal.$el.addClass( 'error-modal' );\n\t\n\t if( details ){\n\t Galaxy.modal.$( '.error-details' ).add( Galaxy.modal.$( 'button:contains(\"Details\")' ) ).remove();\n\t $( '
    ' ).addClass( 'error-details' )\n\t .hide().appendTo( Galaxy.modal.$( '.modal-content' ) )\n\t .append([\n\t $( '

    ' ).text( DETAILS_MSG ),\n\t $( '

    ' ).text( JSON.stringify( details, null, '  ' ) )\n\t            ]);\n\t\n\t        $( '' )\n\t            .appendTo( Galaxy.modal.$( '.buttons' ) )\n\t            .click( function(){ Galaxy.modal.$( '.error-details' ).toggle(); });\n\t    }\n\t    return Galaxy.modal;\n\t}\n\t\n\t/** Display a modal showing an error message but fallback to alert if there's no modal */\n\tfunction errorModal( message, title, details ){\n\t    if( !message ){ return; }\n\t\n\t    message = _l( message );\n\t    title = _l( title ) || _l( 'Error:' );\n\t    if( window.Galaxy && Galaxy.modal ){\n\t        return _errorModal( message, title, details );\n\t    }\n\t\n\t    alert( title + '\\n\\n' + message );\n\t    console.log( 'error details:', JSON.stringify( details ) );\n\t}\n\t\n\t\n\t// ----------------------------------------------------------------------------\n\t/** display a modal when the user may be offline */\n\tfunction offlineErrorModal(){\n\t    return errorModal(\n\t        _l( 'You appear to be offline. Please check your connection and try again.' ),\n\t        _l( 'Offline?' )\n\t    );\n\t}\n\t\n\t\n\t// ----------------------------------------------------------------------------\n\t/** 502 messages that should be displayed when galaxy is restarting */\n\tfunction badGatewayErrorModal(){\n\t    return errorModal(\n\t        _l( 'Galaxy is currently unreachable. Please try again in a few minutes.' ) + ' ' + CONTACT_MSG,\n\t        _l( 'Cannot connect to Galaxy' )\n\t    );\n\t}\n\t\n\t\n\t// ----------------------------------------------------------------------------\n\t/** display a modal (with details) about a failed Backbone ajax operation */\n\tfunction ajaxErrorModal( model, xhr, options, message, title ){\n\t    message = message || DEFAULT_AJAX_ERR_MSG;\n\t    message += ' ' + CONTACT_MSG;\n\t    title = title || _l( 'An error occurred' );\n\t    var details = _ajaxDetails( model, xhr, options );\n\t    return errorModal( message, title, details );\n\t}\n\t\n\t/** build details which may help debugging the ajax call */\n\tfunction _ajaxDetails( model, xhr, options ){\n\t    return {\n\t//TODO: still can't manage Raven id\n\t        raven       : _.result( window.Raven, 'lastEventId' ),\n\t        userAgent   : navigator.userAgent,\n\t        onLine      : navigator.onLine,\n\t        version     : _.result( Galaxy.config, 'version_major' ),\n\t        xhr         : _.omit( xhr, _.functions( xhr ) ),\n\t        options     : _.omit( options, 'xhr' ),\n\t        // add ajax data from Galaxy object cache\n\t        url         : _.result( Galaxy.lastAjax, 'url' ),\n\t        data        : _.result( Galaxy.lastAjax, 'data' ),\n\t        // backbone stuff (auto-redacting email for user)\n\t        model       : _.result( model, 'toJSON' , model + '' ),\n\t        user        : _.omit( _.result( Galaxy.user, 'toJSON' ), 'email' ),\n\t    };\n\t}\n\t\n\t\n\t//=============================================================================\n\t    return {\n\t        errorModal          : errorModal,\n\t        offlineErrorModal   : offlineErrorModal,\n\t        badGatewayErrorModal: badGatewayErrorModal,\n\t        ajaxErrorModal      : ajaxErrorModal\n\t    };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(1), __webpack_require__(2)))\n\n/***/ },\n/* 79 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, $, _, jQuery) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t    //jquery\n\t    //backbone\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function(){\n\t// =============================================================================\n\t/**\n\t * view for a popup menu\n\t */\n\tvar PopupMenu = Backbone.View.extend({\n\t//TODO: maybe better as singleton off the Galaxy obj\n\t    /** Cache the desired button element and options, set up the button click handler\n\t     *  NOTE: attaches this view as HTML/jQ data on the button for later use.\n\t     */\n\t    initialize: function( $button, options ){\n\t        // default settings\n\t        this.$button = $button;\n\t        if( !this.$button.length ){\n\t            this.$button = $( '
    ' );\n\t }\n\t this.options = options || [];\n\t this.$button.data( 'popupmenu', this );\n\t\n\t // set up button click -> open menu behavior\n\t var menu = this;\n\t this.$button.click( function( event ){\n\t // if there's already a menu open, remove it\n\t $( '.popmenu-wrapper' ).remove();\n\t menu._renderAndShow( event );\n\t return false;\n\t });\n\t },\n\t\n\t // render the menu, append to the page body at the click position, and set up the 'click-away' handlers, show\n\t _renderAndShow: function( clickEvent ){\n\t this.render();\n\t this.$el.appendTo( 'body' ).css( this._getShownPosition( clickEvent )).show();\n\t this._setUpCloseBehavior();\n\t },\n\t\n\t // render the menu\n\t // this menu doesn't attach itself to the DOM ( see _renderAndShow )\n\t render: function(){\n\t // render the menu body absolute and hidden, fill with template\n\t this.$el.addClass( 'popmenu-wrapper' ).hide()\n\t .css({ position : 'absolute' })\n\t .html( this.template( this.$button.attr( 'id' ), this.options ));\n\t\n\t // set up behavior on each link/anchor elem\n\t if( this.options.length ){\n\t var menu = this;\n\t //precondition: there should be one option per li\n\t this.$el.find( 'li' ).each( function( i, li ){\n\t var option = menu.options[i];\n\t\n\t // if the option has 'func', call that function when the anchor is clicked\n\t if( option.func ){\n\t $( this ).children( 'a.popupmenu-option' ).click( function( event ){\n\t option.func.call( menu, event, option );\n\t // We must preventDefault otherwise clicking \"cancel\"\n\t // on a purge or something still navigates and causes\n\t // the action.\n\t event.preventDefault();\n\t // bubble up so that an option click will call the close behavior\n\t });\n\t }\n\t });\n\t }\n\t return this;\n\t },\n\t\n\t template : function( id, options ){\n\t return [\n\t '
      ', this._templateOptions( options ), '
    '\n\t ].join( '' );\n\t },\n\t\n\t _templateOptions : function( options ){\n\t if( !options.length ){\n\t return '
  • (no options)
  • ';\n\t }\n\t return _.map( options, function( option ){\n\t if( option.divider ){\n\t return '
  • ';\n\t } else if( option.header ){\n\t return [ '
  • ', option.html, '
  • ' ].join( '' );\n\t }\n\t var href = option.href || 'javascript:void(0);',\n\t target = ( option.target )?( ' target=\"' + option.target + '\"' ):( '' ),\n\t check = ( option.checked )?( '' ):( '' );\n\t return [\n\t '
  • ',\n\t check, option.html,\n\t '
  • '\n\t ].join( '' );\n\t }).join( '' );\n\t },\n\t\n\t // get the absolute position/offset for the menu\n\t _getShownPosition : function( clickEvent ){\n\t\n\t // display menu horiz. centered on click...\n\t var menuWidth = this.$el.width();\n\t var x = clickEvent.pageX - menuWidth / 2 ;\n\t\n\t // adjust to handle horiz. scroll and window dimensions ( draw entirely on visible screen area )\n\t x = Math.min( x, $( document ).scrollLeft() + $( window ).width() - menuWidth - 5 );\n\t x = Math.max( x, $( document ).scrollLeft() + 5 );\n\t return {\n\t top: clickEvent.pageY,\n\t left: x\n\t };\n\t },\n\t\n\t // bind an event handler to all available frames so that when anything is clicked\n\t // the menu is removed from the DOM and the event handler unbinds itself\n\t _setUpCloseBehavior: function(){\n\t var menu = this;\n\t//TODO: alternately: focus hack, blocking overlay, jquery.blockui\n\t\n\t // function to close popup and unbind itself\n\t function closePopup( event ){\n\t $( document ).off( 'click.close_popup' );\n\t if( window && window.parent !== window ){\n\t try {\n\t $( window.parent.document ).off( \"click.close_popup\" );\n\t } catch( err ){}\n\t } else {\n\t try {\n\t $( 'iframe#galaxy_main' ).contents().off( \"click.close_popup\" );\n\t } catch( err ){}\n\t }\n\t menu.remove();\n\t }\n\t\n\t $( 'html' ).one( \"click.close_popup\", closePopup );\n\t if( window && window.parent !== window ){\n\t try {\n\t $( window.parent.document ).find( 'html' ).one( \"click.close_popup\", closePopup );\n\t } catch( err ){}\n\t } else {\n\t try {\n\t $( 'iframe#galaxy_main' ).contents().one( \"click.close_popup\", closePopup );\n\t } catch( err ){}\n\t }\n\t },\n\t\n\t // add a menu option/item at the given index\n\t addItem: function( item, index ){\n\t // append to end if no index\n\t index = ( index >= 0 ) ? index : this.options.length;\n\t this.options.splice( index, 0, item );\n\t return this;\n\t },\n\t\n\t // remove a menu option/item at the given index\n\t removeItem: function( index ){\n\t if( index >=0 ){\n\t this.options.splice( index, 1 );\n\t }\n\t return this;\n\t },\n\t\n\t // search for a menu option by its html\n\t findIndexByHtml: function( html ){\n\t for( var i = 0; i < this.options.length; i++ ){\n\t if( _.has( this.options[i], 'html' ) && ( this.options[i].html === html )){\n\t return i;\n\t }\n\t }\n\t return null;\n\t },\n\t\n\t // search for a menu option by its html\n\t findItemByHtml: function( html ){\n\t return this.options[( this.findIndexByHtml( html ))];\n\t },\n\t\n\t // string representation\n\t toString: function(){\n\t return 'PopupMenu';\n\t }\n\t});\n\t/** shortcut to new for when you don't need to preserve the ref */\n\tPopupMenu.create = function _create( $button, options ){\n\t return new PopupMenu( $button, options );\n\t};\n\t\n\t// -----------------------------------------------------------------------------\n\t// the following class functions are bridges from the original make_popupmenu and make_popup_menus\n\t// to the newer backbone.js PopupMenu\n\t\n\t/** Create a PopupMenu from simple map initial_options activated by clicking button_element.\n\t * Converts initial_options to object array used by PopupMenu.\n\t * @param {jQuery|DOMElement} button_element element which, when clicked, activates menu\n\t * @param {Object} initial_options map of key -> values, where\n\t * key is option text, value is fn to call when option is clicked\n\t * @returns {PopupMenu} the PopupMenu created\n\t */\n\tPopupMenu.make_popupmenu = function( button_element, initial_options ){\n\t var convertedOptions = [];\n\t _.each( initial_options, function( optionVal, optionKey ){\n\t var newOption = { html: optionKey };\n\t\n\t // keys with null values indicate: header\n\t if( optionVal === null ){ // !optionVal? (null only?)\n\t newOption.header = true;\n\t\n\t // keys with function values indicate: a menu option\n\t } else if( jQuery.type( optionVal ) === 'function' ){\n\t newOption.func = optionVal;\n\t }\n\t //TODO:?? any other special optionVals?\n\t // there was no divider option originally\n\t convertedOptions.push( newOption );\n\t });\n\t return new PopupMenu( $( button_element ), convertedOptions );\n\t};\n\t\n\t/** Find all anchors in $parent (using selector) and covert anchors into a PopupMenu options map.\n\t * @param {jQuery} $parent the element that contains the links to convert to options\n\t * @param {String} selector jq selector string to find links\n\t * @returns {Object[]} the options array to initialize a PopupMenu\n\t */\n\t//TODO: lose parent and selector, pass in array of links, use map to return options\n\tPopupMenu.convertLinksToOptions = function( $parent, selector ){\n\t $parent = $( $parent );\n\t selector = selector || 'a';\n\t var options = [];\n\t $parent.find( selector ).each( function( elem, i ){\n\t var option = {}, $link = $( elem );\n\t\n\t // convert link text to the option text (html) and the href into the option func\n\t option.html = $link.text();\n\t if( $link.attr( 'href' ) ){\n\t var linkHref = $link.attr( 'href' ),\n\t linkTarget = $link.attr( 'target' ),\n\t confirmText = $link.attr( 'confirm' );\n\t\n\t option.func = function(){\n\t // if there's a \"confirm\" attribute, throw up a confirmation dialog, and\n\t // if the user cancels - do nothing\n\t if( ( confirmText ) && ( !confirm( confirmText ) ) ){ return; }\n\t\n\t // if there's no confirm attribute, or the user accepted the confirm dialog:\n\t switch( linkTarget ){\n\t // relocate the center panel\n\t case '_parent':\n\t window.parent.location = linkHref;\n\t break;\n\t\n\t // relocate the entire window\n\t case '_top':\n\t window.top.location = linkHref;\n\t break;\n\t\n\t // relocate this panel\n\t default:\n\t window.location = linkHref;\n\t }\n\t };\n\t }\n\t options.push( option );\n\t });\n\t return options;\n\t};\n\t\n\t/** Create a single popupmenu from existing DOM button and anchor elements\n\t * @param {jQuery} $buttonElement the element that when clicked will open the menu\n\t * @param {jQuery} $menuElement the element that contains the anchors to convert into a menu\n\t * @param {String} menuElementLinkSelector jq selector string used to find anchors to be made into menu options\n\t * @returns {PopupMenu} the PopupMenu (Backbone View) that can render, control the menu\n\t */\n\tPopupMenu.fromExistingDom = function( $buttonElement, $menuElement, menuElementLinkSelector ){\n\t $buttonElement = $( $buttonElement );\n\t $menuElement = $( $menuElement );\n\t var options = PopupMenu.convertLinksToOptions( $menuElement, menuElementLinkSelector );\n\t // we're done with the menu (having converted it to an options map)\n\t $menuElement.remove();\n\t return new PopupMenu( $buttonElement, options );\n\t};\n\t\n\t/** Create all popupmenus within a document or a more specific element\n\t * @param {DOMElement} parent the DOM element in which to search for popupmenus to build (defaults to document)\n\t * @param {String} menuSelector jq selector string to find popupmenu menu elements (defaults to \"div[popupmenu]\")\n\t * @param {Function} buttonSelectorBuildFn the function to build the jq button selector.\n\t * Will be passed $menuElement, parent.\n\t * (Defaults to return '#' + $menuElement.attr( 'popupmenu' ); )\n\t * @returns {PopupMenu[]} array of popupmenus created\n\t */\n\tPopupMenu.make_popup_menus = function( parent, menuSelector, buttonSelectorBuildFn ){\n\t parent = parent || document;\n\t // orig. Glx popupmenu menus have a (non-std) attribute 'popupmenu'\n\t // which contains the id of the button that activates the menu\n\t menuSelector = menuSelector || 'div[popupmenu]';\n\t // default to (orig. Glx) matching button to menu by using the popupmenu attr of the menu as the id of the button\n\t buttonSelectorBuildFn = buttonSelectorBuildFn || function( $menuElement, parent ){\n\t return '#' + $menuElement.attr( 'popupmenu' );\n\t };\n\t\n\t // aggregate and return all PopupMenus\n\t var popupMenusCreated = [];\n\t $( parent ).find( menuSelector ).each( function(){\n\t var $menuElement = $( this ),\n\t $buttonElement = $( parent ).find( buttonSelectorBuildFn( $menuElement, parent ) );\n\t popupMenusCreated.push( PopupMenu.fromDom( $buttonElement, $menuElement ) );\n\t $buttonElement.addClass( 'popup' );\n\t });\n\t return popupMenusCreated;\n\t};\n\t\n\t\n\t// =============================================================================\n\t return PopupMenu;\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(1), __webpack_require__(2), __webpack_require__(1)))\n\n/***/ },\n/* 80 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, $) {/** This renders the content of the ftp popup **/\n\t!(__WEBPACK_AMD_DEFINE_ARRAY__ = [ __webpack_require__(4) ], __WEBPACK_AMD_DEFINE_RESULT__ = function( Utils ) {\n\t return Backbone.View.extend({\n\t initialize: function( options ) {\n\t var self = this;\n\t this.options = Utils.merge( options, {\n\t class_add : 'upload-icon-button fa fa-square-o',\n\t class_remove : 'upload-icon-button fa fa-check-square-o',\n\t class_partial : 'upload-icon-button fa fa-minus-square-o',\n\t collection : null,\n\t onchange : function() {},\n\t onadd : function() {},\n\t onremove : function() {}\n\t } );\n\t this.collection = this.options.collection;\n\t this.setElement( this._template() );\n\t this.rows = [];\n\t Utils.get({\n\t url : Galaxy.root + 'api/remote_files',\n\t success : function( ftp_files ) { self._fill( ftp_files ) },\n\t error : function() { self._fill(); }\n\t });\n\t },\n\t\n\t /** Fill table with ftp entries */\n\t _fill: function( ftp_files ) {\n\t if ( ftp_files && ftp_files.length > 0 ) {\n\t this.$( '.upload-ftp-content' ).html( $( this._templateTable() ) );\n\t var size = 0;\n\t for ( index in ftp_files ) {\n\t this.rows.push( this._add( ftp_files[ index ] ) );\n\t size += ftp_files[ index ].size;\n\t }\n\t this.$( '.upload-ftp-number' ).html( ftp_files.length + ' files' );\n\t this.$( '.upload-ftp-disk' ).html( Utils.bytesToString ( size, true ) );\n\t if ( this.collection ) {\n\t var self = this;\n\t this.$( '._has_collection' ).show();\n\t this.$select_all = this.$( '.upload-selectall' ).addClass( this.options.class_add );\n\t this.$select_all.on( 'click', function() {\n\t var add = self.$select_all.hasClass( self.options.class_add );\n\t for ( index in ftp_files ) {\n\t var ftp_file = ftp_files[ index ];\n\t var model_index = self._find( ftp_file );\n\t if( !model_index && add || model_index && !add ) {\n\t self.rows[ index ].trigger( 'click' );\n\t }\n\t }\n\t });\n\t this._refresh();\n\t }\n\t } else {\n\t this.$( '.upload-ftp-content' ).html( $( this._templateInfo() ) );\n\t }\n\t this.$( '.upload-ftp-wait' ).hide();\n\t },\n\t\n\t /** Add file to table */\n\t _add: function( ftp_file ) {\n\t var self = this;\n\t var $it = $( this._templateRow( ftp_file ) );\n\t var $icon = $it.find( '.icon' );\n\t this.$( 'tbody' ).append( $it );\n\t if ( this.collection ) {\n\t $icon.addClass( this._find( ftp_file ) ? this.options.class_remove : this.options.class_add );\n\t $it.on('click', function() {\n\t var model_index = self._find( ftp_file );\n\t $icon.removeClass();\n\t if ( !model_index ) {\n\t self.options.onadd( ftp_file );\n\t $icon.addClass( self.options.class_remove );\n\t } else {\n\t self.options.onremove( model_index );\n\t $icon.addClass( self.options.class_add );\n\t }\n\t self._refresh();\n\t });\n\t } else {\n\t $it.on('click', function() { self.options.onchange( ftp_file ) } );\n\t }\n\t return $it;\n\t },\n\t\n\t /** Refresh select all button state */\n\t _refresh: function() {\n\t var filtered = this.collection.where( { file_mode: 'ftp', enabled: true } );\n\t this.$select_all.removeClass();\n\t if ( filtered.length == 0 ) {\n\t this.$select_all.addClass( this.options.class_add );\n\t } else {\n\t this.$select_all.addClass( filtered.length == this.rows.length ? this.options.class_remove : this.options.class_partial );\n\t }\n\t },\n\t\n\t /** Get model index */\n\t _find: function( ftp_file ) {\n\t var item = this.collection.findWhere({\n\t file_path : ftp_file.path,\n\t file_mode : 'ftp',\n\t enabled : true\n\t });\n\t return item && item.get('id');\n\t },\n\t\n\t /** Template of row */\n\t _templateRow: function( options ) {\n\t return '' +\n\t '
    ' +\n\t '' + options.path + '' +\n\t '' + Utils.bytesToString( options.size ) + '' +\n\t '' + options.ctime + '' +\n\t '';\n\t },\n\t\n\t /** Template of table */\n\t _templateTable: function() {\n\t return 'Available files: ' +\n\t '' +\n\t '' +\n\t '  ' +\n\t '' +\n\t '' +\n\t '' +\n\t '' +\n\t '' +\n\t '' +\n\t '' +\n\t '' +\n\t '' +\n\t '' +\n\t '' +\n\t '' +\n\t '' +\n\t '
    NameSizeCreated
    ';\n\t },\n\t\n\t /** Template of info message */\n\t _templateInfo: function() {\n\t return '
    ' +\n\t 'Your FTP directory does not contain any files.' +\n\t '
    ';\n\t },\n\t\n\t /** Template of main view */\n\t _template: function() {\n\t return '
    ' +\n\t '
    ' +\n\t '
    This Galaxy server allows you to upload files via FTP. To upload some files, log in to the FTP server at ' + this.options.ftp_upload_site + ' using your Galaxy credentials (email address and password).
    ' +\n\t '
    ' +\n\t '
    ';\n\t }\n\t });\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(1)))\n\n/***/ },\n/* 81 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, $, _) {/** This renders the content of the settings popup, allowing users to specify flags i.e. for space-to-tab conversion **/\n\t!(__WEBPACK_AMD_DEFINE_ARRAY__ = [ __webpack_require__(4) ], __WEBPACK_AMD_DEFINE_RESULT__ = function( Utils ) {\n\t return Backbone.View.extend({\n\t options: {\n\t class_check : 'fa-check-square-o',\n\t class_uncheck : 'fa-square-o',\n\t parameters : [{\n\t id : 'space_to_tab',\n\t title : 'Convert spaces to tabs',\n\t },{\n\t id : 'to_posix_lines',\n\t title : 'Use POSIX standard'\n\t }]\n\t },\n\t\n\t initialize: function( options ) {\n\t var self = this;\n\t this.model = options.model;\n\t this.setElement( $( '
    ' ).addClass( 'upload-settings' ) );\n\t this.$el.append( $( '
    ' ).addClass( 'upload-settings-cover' ) );\n\t this.$el.append( $( '' ).addClass( 'upload-settings-table ui-table-striped' ).append( '' ) );\n\t this.$cover = this.$( '.upload-settings-cover' );\n\t this.$table = this.$( '.upload-settings-table > tbody' );\n\t this.listenTo ( this.model, 'change', this.render, this );\n\t this.model.trigger( 'change' );\n\t },\n\t\n\t render: function() {\n\t var self = this;\n\t this.$table.empty();\n\t _.each( this.options.parameters, function( parameter ) {\n\t var $checkbox = $( '
    ' ).addClass( 'upload-' + parameter.id + ' upload-icon-button fa' )\n\t .addClass( self.model.get( parameter.id ) && self.options.class_check || self.options.class_uncheck )\n\t .on( 'click', function() {\n\t self.model.get( 'enabled' ) && self.model.set( parameter.id, !self.model.get( parameter.id ) )\n\t });\n\t self.$table.append( $( '
    ' ).append( $( '' +\n\t '');\n wrapper.append($el);\n this.row.append(wrapper);\n },\n \n // header\n appendHeader: function() {\n // append header row\n this.$thead.append(this.row);\n\n // row\n this.row = $('');\n },\n \n // add row cell\n add: function($el, width, align) {\n var wrapper = $('');\n if (width) {\n wrapper.css('width', width);\n }\n if (align) {\n wrapper.css('text-align', align);\n }\n wrapper.append($el);\n this.row.append(wrapper);\n },\n \n // append\n append: function(id, fade) {\n this._commit(id, fade, false);\n },\n \n // prepend\n prepend: function(id, fade) {\n this._commit(id, fade, true);\n },\n \n // get element\n get: function(id) {\n return this.$el.find('#' + id);\n },\n \n // delete\n del: function(id) {\n var item = this.$tbody.find('#' + id);\n if (item.length > 0) {\n item.remove();\n this.row_count--;\n this._refresh();\n }\n },\n\n // delete all\n delAll: function() {\n this.$tbody.empty();\n this.row_count = 0;\n this._refresh();\n },\n \n // value\n value: function(new_value) {\n // get current id/value\n this.before = this.$tbody.find('.current').attr('id');\n \n // check if new_value is defined\n if (new_value !== undefined) {\n this.$tbody.find('tr').removeClass('current');\n if (new_value) {\n this.$tbody.find('#' + new_value).addClass('current');\n }\n }\n \n // get current id/value\n var after = this.$tbody.find('.current').attr('id');\n if(after === undefined) {\n return null;\n } else {\n // fire onchange\n if (after != this.before && this.options.onchange) {\n this.options.onchange(new_value);\n }\n \n // return current value\n return after;\n }\n },\n \n // size\n size: function() {\n return this.$tbody.find('tr').length;\n },\n \n // commit\n _commit: function(id, fade, prepend) {\n // remove previous item with same id\n this.del(id);\n \n // add\n this.row.attr('id', id);\n \n // add row\n if (prepend) {\n this.$tbody.prepend(this.row);\n } else {\n this.$tbody.append(this.row);\n }\n \n // fade mode\n if (fade) {\n this.row.hide();\n this.row.fadeIn();\n }\n \n // row\n this.row = this._row();\n \n // row count\n this.row_count++;\n this._refresh();\n },\n \n // create new row\n _row: function() {\n return $('');\n },\n \n // onclick\n _onclick: function(e) {\n // get values\n var old_value = this.value();\n var new_value = $(e.target).closest('tr').attr('id');\n if (new_value != ''){\n // check equality\n if (new_value && old_value != new_value) {\n if (this.options.onconfirm) {\n this.options.onconfirm(new_value);\n } else {\n this.value(new_value);\n }\n }\n }\n },\n\n // ondblclick\n _ondblclick: function(e) {\n var value = this.value();\n if (value && this.options.ondblclick) {\n this.options.ondblclick(value);\n }\n },\n \n // refresh\n _refresh: function() {\n if (this.row_count == 0) {\n this.$tmessage.show();\n } else {\n this.$tmessage.hide();\n }\n },\n \n // load html template\n _template: function(options) {\n return '
    ' +\n '
    ' ).append( $checkbox ) )\n\t .append( $( '' ).append( parameter.title ) ) )\n\t });\n\t this.$cover[ this.model.get( 'enabled' ) && 'hide' || 'show' ]();\n\t }\n\t });\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(1), __webpack_require__(2)))\n\n/***/ },\n/* 82 */,\n/* 83 */,\n/* 84 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(jQuery, $) {(function (factory) {\n\t if (true) {\n\t !(__WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t } else {\n\t // Browser globals\n\t factory(jQuery);\n\t }\n\t\n\t}(function () {\n\t//=============================================================================\n\t\n\t jQuery.fn.extend({\n\t hoverhighlight : function $hoverhighlight( scope, color ){\n\t scope = scope || 'body';\n\t if( !this.length ){ return this; }\n\t\n\t $( this ).each( function(){\n\t var $this = $( this ),\n\t targetSelector = $this.data( 'target' );\n\t\n\t if( targetSelector ){\n\t $this.mouseover( function( ev ){\n\t $( targetSelector, scope ).css({\n\t background: color\n\t });\n\t })\n\t .mouseout( function( ev ){\n\t $( targetSelector ).css({\n\t background: ''\n\t });\n\t });\n\t }\n\t });\n\t return this;\n\t }\n\t });\n\t}));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(1), __webpack_require__(1)))\n\n/***/ },\n/* 85 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function($, jQuery) {// from: https://raw.githubusercontent.com/umdjs/umd/master/jqueryPlugin.js\n\t// Uses AMD or browser globals to create a jQuery plugin.\n\t(function (factory) {\n\t if (true) {\n\t //TODO: So...this turns out to be an all or nothing thing. If I load jQuery in the define below, it will\n\t // (of course) wipe the old jquery *and all the plugins loaded into it*. So the define below *is still\n\t // relying on jquery being loaded globally* in order to preserve plugins.\n\t !(__WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t } else {\n\t // Browser globals\n\t factory(jQuery);\n\t }\n\t\n\t}(function () {\n\t var _l = window._l || function( s ){ return s; };\n\t\n\t //TODO: consolidate with tool menu functionality, use there\n\t\n\t /** searchInput: (jQuery plugin)\n\t * Creates a search input, a clear button, and loading indicator\n\t * within the selected node.\n\t *\n\t * When the user either presses return or enters some minimal number\n\t * of characters, a callback is called. Pressing ESC when the input\n\t * is focused will clear the input and call a separate callback.\n\t */\n\t function searchInput( parentNode, options ){\n\t var KEYCODE_ESC = 27,\n\t KEYCODE_RETURN = 13,\n\t $parentNode = $( parentNode ),\n\t firstSearch = true,\n\t defaults = {\n\t initialVal : '',\n\t name : 'search',\n\t placeholder : 'search',\n\t classes : '',\n\t onclear : function(){},\n\t onfirstsearch : null,\n\t onsearch : function( inputVal ){},\n\t minSearchLen : 0,\n\t escWillClear : true,\n\t oninit : function(){}\n\t };\n\t\n\t // .................................................................... input rendering and events\n\t // visually clear the search, trigger an event, and call the callback\n\t function clearSearchInput( event ){\n\t var $input = $( this ).parent().children( 'input' );\n\t $input.val( '' ).trigger( 'searchInput.clear' ).blur();\n\t options.onclear();\n\t }\n\t\n\t // search for searchTerms, trigger an event, call the appropo callback (based on whether this is the first)\n\t function search( event, searchTerms ){\n\t if( !searchTerms ){\n\t return clearSearchInput();\n\t }\n\t $( this ).trigger( 'search.search', searchTerms );\n\t if( typeof options.onfirstsearch === 'function' && firstSearch ){\n\t firstSearch = false;\n\t options.onfirstsearch( searchTerms );\n\t } else {\n\t options.onsearch( searchTerms );\n\t }\n\t }\n\t\n\t // .................................................................... input rendering and events\n\t function inputTemplate(){\n\t // class search-query is bootstrap 2.3 style that now lives in base.less\n\t return [ '' ].join( '' );\n\t }\n\t\n\t // the search input that responds to keyboard events and displays the search value\n\t function $input(){\n\t return $( inputTemplate() )\n\t // select all text on a focus\n\t .focus( function( event ){\n\t $( this ).select();\n\t })\n\t // attach behaviors to esc, return if desired, search on some min len string\n\t .keyup( function( event ){\n\t event.preventDefault();\n\t event.stopPropagation();\n\t\n\t // esc key will clear if desired\n\t if( event.which === KEYCODE_ESC && options.escWillClear ){\n\t clearSearchInput.call( this, event );\n\t\n\t } else {\n\t var searchTerms = $( this ).val();\n\t // return key or the search string len > minSearchLen (if not 0) triggers search\n\t if( ( event.which === KEYCODE_RETURN )\n\t || ( options.minSearchLen && searchTerms.length >= options.minSearchLen ) ){\n\t search.call( this, event, searchTerms );\n\t }\n\t }\n\t })\n\t .val( options.initialVal );\n\t }\n\t\n\t // .................................................................... clear button rendering and events\n\t // a button for clearing the search bar, placed on the right hand side\n\t function $clearBtn(){\n\t return $([ '' ].join('') )\n\t .tooltip({ placement: 'bottom' })\n\t .click( function( event ){\n\t clearSearchInput.call( this, event );\n\t });\n\t }\n\t\n\t // .................................................................... loadingIndicator rendering\n\t // a button for clearing the search bar, placed on the right hand side\n\t function $loadingIndicator(){\n\t return $([ '' ].join('') )\n\t .hide().tooltip({ placement: 'bottom' });\n\t }\n\t\n\t // .................................................................... commands\n\t // visually swap the load, clear buttons\n\t function toggleLoadingIndicator(){\n\t $parentNode.find( '.search-loading' ).toggle();\n\t $parentNode.find( '.search-clear' ).toggle();\n\t }\n\t\n\t // .................................................................... init\n\t // string command (not constructor)\n\t if( jQuery.type( options ) === 'string' ){\n\t if( options === 'toggle-loading' ){\n\t toggleLoadingIndicator();\n\t }\n\t return $parentNode;\n\t }\n\t\n\t // initial render\n\t if( jQuery.type( options ) === 'object' ){\n\t options = jQuery.extend( true, {}, defaults, options );\n\t }\n\t //NOTE: prepended\n\t return $parentNode.addClass( 'search-input' ).prepend([ $input(), $clearBtn(), $loadingIndicator() ]);\n\t }\n\t\n\t // as jq plugin\n\t jQuery.fn.extend({\n\t searchInput : function $searchInput( options ){\n\t return this.each( function(){\n\t return searchInput( this, options );\n\t });\n\t }\n\t });\n\t}));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(1), __webpack_require__(1)))\n\n/***/ },\n/* 86 */,\n/* 87 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;!(__WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_RESULT__ = function(){\n\t// Alphanumeric/natural sort fn\n\tfunction naturalSort(a, b) {\n\t // setup temp-scope variables for comparison evauluation\n\t var re = /(-?[0-9\\.]+)/g,\n\t x = a.toString().toLowerCase() || '',\n\t y = b.toString().toLowerCase() || '',\n\t nC = String.fromCharCode(0),\n\t xN = x.replace( re, nC + '$1' + nC ).split(nC),\n\t yN = y.replace( re, nC + '$1' + nC ).split(nC),\n\t xD = (new Date(x)).getTime(),\n\t yD = xD ? (new Date(y)).getTime() : null;\n\t // natural sorting of dates\n\t if ( yD ) {\n\t if ( xD < yD ) { return -1; }\n\t else if ( xD > yD ) { return 1; }\n\t }\n\t // natural sorting through split numeric strings and default strings\n\t var oFxNcL, oFyNcL;\n\t for ( var cLoc = 0, numS = Math.max(xN.length, yN.length); cLoc < numS; cLoc++ ) {\n\t oFxNcL = parseFloat(xN[cLoc]) || xN[cLoc];\n\t oFyNcL = parseFloat(yN[cLoc]) || yN[cLoc];\n\t if (oFxNcL < oFyNcL) { return -1; }\n\t else if (oFxNcL > oFyNcL) { return 1; }\n\t }\n\t return 0;\n\t}\n\t\n\treturn naturalSort;\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__))\n\n\n/***/ },\n/* 88 */\n/***/ function(module, exports, __webpack_require__) {\n\n\t/* WEBPACK VAR INJECTION */(function(jQuery, _) {/*\n\t galaxy upload plugins - requires FormData and XMLHttpRequest\n\t*/\n\t;(function($){\n\t // add event properties\n\t jQuery.event.props.push(\"dataTransfer\");\n\t\n\t /**\n\t Posts file data to the API\n\t */\n\t $.uploadpost = function (config) {\n\t // parse options\n\t var cnf = $.extend({}, {\n\t data : {},\n\t success : function() {},\n\t error : function() {},\n\t progress : function() {},\n\t url : null,\n\t maxfilesize : 2048,\n\t error_filesize : 'File exceeds 2GB. Please use a FTP client.',\n\t error_default : 'Please make sure the file is available.',\n\t error_server : 'Upload request failed.',\n\t error_login : 'Uploads require you to log in.'\n\t }, config);\n\t\n\t // link data\n\t var data = cnf.data;\n\t\n\t // check errors\n\t if (data.error_message) {\n\t cnf.error(data.error_message);\n\t return;\n\t }\n\t\n\t // construct form data\n\t var form = new FormData();\n\t for (var key in data.payload) {\n\t form.append(key, data.payload[key]);\n\t }\n\t\n\t // add files to submission\n\t var sizes = 0;\n\t for (var key in data.files) {\n\t var d = data.files[key];\n\t form.append(d.name, d.file, d.file.name);\n\t sizes += d.file.size;\n\t }\n\t\n\t // check file size, unless it's an ftp file\n\t if (sizes > 1048576 * cnf.maxfilesize) {\n\t cnf.error(cnf.error_filesize);\n\t return;\n\t }\n\t\n\t // prepare request\n\t xhr = new XMLHttpRequest();\n\t xhr.open('POST', cnf.url, true);\n\t xhr.setRequestHeader('Accept', 'application/json');\n\t xhr.setRequestHeader('Cache-Control', 'no-cache');\n\t xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');\n\t\n\t // captures state changes\n\t xhr.onreadystatechange = function() {\n\t // check for request completed, server connection closed\n\t if (xhr.readyState == xhr.DONE) {\n\t // parse response\n\t var response = null;\n\t if (xhr.responseText) {\n\t try {\n\t response = jQuery.parseJSON(xhr.responseText);\n\t } catch (e) {\n\t response = xhr.responseText;\n\t }\n\t }\n\t // pass any error to the error option\n\t if (xhr.status < 200 || xhr.status > 299) {\n\t var text = xhr.statusText;\n\t if (xhr.status == 403) {\n\t text = cnf.error_login;\n\t } else if (xhr.status == 0) {\n\t text = cnf.error_server;\n\t } else if (!text) {\n\t text = cnf.error_default;\n\t }\n\t cnf.error(text + ' (' + xhr.status + ')');\n\t } else {\n\t cnf.success(response);\n\t }\n\t }\n\t }\n\t\n\t // prepare upload progress\n\t xhr.upload.addEventListener('progress', function(e) {\n\t if (e.lengthComputable) {\n\t cnf.progress(Math.round((e.loaded * 100) / e.total));\n\t }\n\t }, false);\n\t\n\t // send request\n\t Galaxy.emit.debug('uploadbox::uploadpost()', 'Posting following data.', cnf);\n\t xhr.send(form);\n\t }\n\t\n\t /**\n\t Handles the upload events drag/drop etc.\n\t */\n\t $.fn.uploadinput = function(options) {\n\t // initialize\n\t var el = this;\n\t var opts = $.extend({}, {\n\t ondragover : function() {},\n\t ondragleave : function() {},\n\t onchange : function() {},\n\t multiple : false\n\t }, options);\n\t\n\t // append hidden upload field\n\t var $input = $('');\n\t el.append($input.change(function (e) {\n\t opts.onchange(e.target.files);\n\t $(this).val('');\n\t }));\n\t\n\t // drag/drop events\n\t el.on('drop', function (e) {\n\t opts.ondragleave(e);\n\t if(e.dataTransfer) {\n\t opts.onchange(e.dataTransfer.files);\n\t e.preventDefault();\n\t }\n\t });\n\t el.on('dragover', function (e) {\n\t e.preventDefault();\n\t opts.ondragover(e);\n\t });\n\t el.on('dragleave', function (e) {\n\t e.stopPropagation();\n\t opts.ondragleave(e);\n\t });\n\t\n\t // exports\n\t return {\n\t dialog: function () {\n\t $input.trigger('click');\n\t }\n\t }\n\t }\n\t\n\t /**\n\t Handles the upload queue and events such as drag/drop etc.\n\t */\n\t $.fn.uploadbox = function(options) {\n\t // parse options\n\t var opts = $.extend({}, {\n\t dragover : function() {},\n\t dragleave : function() {},\n\t announce : function(d) {},\n\t initialize : function(d) {},\n\t progress : function(d, m) {},\n\t success : function(d, m) {},\n\t error : function(d, m) { alert(m); },\n\t complete : function() {}\n\t }, options);\n\t\n\t // file queue\n\t var queue = {};\n\t\n\t // queue index/length counter\n\t var queue_index = 0;\n\t var queue_length = 0;\n\t\n\t // indicates if queue is currently running\n\t var queue_running = false;\n\t var queue_stop = false;\n\t\n\t // element\n\t var uploadinput = $(this).uploadinput({\n\t multiple : true,\n\t onchange : function(files) { add(files); },\n\t ondragover : options.ondragover,\n\t ondragleave : options.ondragleave\n\t });\n\t\n\t // add new files to upload queue\n\t function add(files) {\n\t if (files && files.length && !queue_running) {\n\t var current_index = queue_index;\n\t _.each(files, function(file, key) {\n\t if (file.mode !== 'new' && _.filter(queue, function(f) {\n\t return f.name === file.name && f.size === file.size;\n\t }).length) {\n\t file.duplicate = true;\n\t }\n\t });\n\t _.each(files, function(file) {\n\t if (!file.duplicate) {\n\t var index = String(queue_index++);\n\t queue[index] = file;\n\t opts.announce(index, queue[index]);\n\t queue_length++;\n\t }\n\t });\n\t return current_index;\n\t }\n\t }\n\t\n\t // remove file from queue\n\t function remove(index) {\n\t if (queue[index]) {\n\t delete queue[index];\n\t queue_length--;\n\t }\n\t }\n\t\n\t // process an upload, recursive\n\t function process() {\n\t // validate\n\t if (queue_length == 0 || queue_stop) {\n\t queue_stop = false;\n\t queue_running = false;\n\t opts.complete();\n\t return;\n\t } else {\n\t queue_running = true;\n\t }\n\t\n\t // get an identifier from the queue\n\t var index = -1;\n\t for (var key in queue) {\n\t index = key;\n\t break;\n\t }\n\t\n\t // get current file from queue\n\t var file = queue[index];\n\t\n\t // remove from queue\n\t remove(index)\n\t\n\t // create and submit data\n\t $.uploadpost({\n\t url : opts.url,\n\t data : opts.initialize(index),\n\t success : function(message) { opts.success(index, message); process();},\n\t error : function(message) { opts.error(index, message); process();},\n\t progress : function(percentage) { opts.progress(index, percentage); }\n\t });\n\t }\n\t\n\t /*\n\t public interface\n\t */\n\t\n\t // open file browser for selection\n\t function select() {\n\t uploadinput.dialog();\n\t }\n\t\n\t // remove all entries from queue\n\t function reset(index) {\n\t for (index in queue) {\n\t remove(index);\n\t }\n\t }\n\t\n\t // initiate upload process\n\t function start() {\n\t if (!queue_running) {\n\t queue_running = true;\n\t process();\n\t }\n\t }\n\t\n\t // stop upload process\n\t function stop() {\n\t queue_stop = true;\n\t }\n\t\n\t // set options\n\t function configure(options) {\n\t opts = $.extend({}, opts, options);\n\t return opts;\n\t }\n\t\n\t // verify browser compatibility\n\t function compatible() {\n\t return window.File && window.FormData && window.XMLHttpRequest && window.FileList;\n\t }\n\t\n\t // export functions\n\t return {\n\t 'select' : select,\n\t 'add' : add,\n\t 'remove' : remove,\n\t 'start' : start,\n\t 'stop' : stop,\n\t 'reset' : reset,\n\t 'configure' : configure,\n\t 'compatible' : compatible\n\t };\n\t }\n\t})(jQuery);\n\t\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(1), __webpack_require__(2)))\n\n/***/ },\n/* 89 */\n/***/ function(module, exports, __webpack_require__) {\n\n\t/* WEBPACK VAR INJECTION */(function(_) {var RightPanel = __webpack_require__( 10 ).RightPanel,\n\t Ui = __webpack_require__( 7 ),\n\t historyOptionsMenu = __webpack_require__( 116 );\n\t CurrentHistoryView = __webpack_require__( 113 ).CurrentHistoryView,\n\t _l = __webpack_require__( 5 );\n\t\n\t/** the right hand panel in the analysis page that shows the current history */\n\tvar HistoryPanel = RightPanel.extend({\n\t\n\t title : _l( 'History' ),\n\t\n\t initialize : function( options ){\n\t RightPanel.prototype.initialize.call( this, options );\n\t this.options = _.pick( options, 'userIsAnonymous', 'allow_user_dataset_purge', 'galaxyRoot' );\n\t\n\t // view of the current history\n\t this.historyView = new CurrentHistoryView({\n\t className : CurrentHistoryView.prototype.className + ' middle',\n\t purgeAllowed : options.allow_user_dataset_purge,\n\t linkTarget : 'galaxy_main'\n\t });\n\t },\n\t\n\t /** override to change footer selector */\n\t $toggleButton : function(){\n\t return this.$( '.footer > .panel-collapse' );\n\t },\n\t\n\t render : function(){\n\t RightPanel.prototype.render.call( this );\n\t this.optionsMenu = historyOptionsMenu( this.$( '#history-options-button' ), {\n\t anonymous : this.options.userIsAnonymous,\n\t purgeAllowed : this.options.allow_user_dataset_purge,\n\t root : this.options.galaxyRoot\n\t });\n\t this.$( '> .header .buttons [title]' ).tooltip({ placement: 'bottom' });\n\t this.historyView.setElement( this.$( '.history-panel' ) );\n\t this.$el.attr( 'class', 'history-right-panel' );\n\t },\n\t\n\t /** override to add buttons */\n\t _templateHeader: function( data ){\n\t var historyUrl = this.options.galaxyRoot + 'history';\n\t var multiUrl = this.options.galaxyRoot + 'history/view_multiple';\n\t return [\n\t '
    ',\n\t '
    ',\n\t // this button re-fetches the history and contents and re-renders the history panel\n\t '',\n\t // opens a drop down menu with history related functions (like view all, delete, share, etc.)\n\t '',\n\t !this.options.userIsAnonymous?\n\t [ '' ].join('') : '',\n\t '
    ',\n\t '
    ', _.escape( this.title ), '
    ',\n\t '
    ',\n\t ].join('');\n\t },\n\t\n\t /** add history view div */\n\t _templateBody : function( data ){\n\t return [\n\t '
    ',\n\t ].join('');\n\t },\n\t\n\t /** override to use simplified selector */\n\t _templateFooter: function( data ){\n\t return [\n\t '
    ',\n\t '
    ',\n\t '
    ',\n\t '
    ',\n\t ].join('');\n\t },\n\t\n\t events : {\n\t 'click #history-refresh-button' : '_clickRefresh',\n\t // override to change footer selector\n\t 'mousedown .footer > .drag' : '_mousedownDragHandler',\n\t 'click .footer > .panel-collapse' : 'toggle'\n\t },\n\t\n\t _clickRefresh : function( ev ){\n\t ev.preventDefault();\n\t this.historyView.loadCurrentHistory();\n\t },\n\t\n\t toString : function(){ return 'HistoryPanel'; }\n\t});\n\t\n\tmodule.exports = HistoryPanel;\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(2)))\n\n/***/ },\n/* 90 */\n/***/ function(module, exports, __webpack_require__) {\n\n\t/* WEBPACK VAR INJECTION */(function($, _) {var LeftPanel = __webpack_require__( 10 ).LeftPanel,\n\t Tools = __webpack_require__( 44 ),\n\t Upload = __webpack_require__( 123 ),\n\t _l = __webpack_require__( 5 );\n\t\n\t/* Builds the tool menu panel on the left of the analysis page */\n\tvar ToolPanel = LeftPanel.extend({\n\t\n\t title : _l( 'Tools' ),\n\t\n\t initialize: function( options ){\n\t LeftPanel.prototype.initialize.call( this, options );\n\t this.log( this + '.initialize:', options );\n\t\n\t /** @type {Object[]} descriptions of user's workflows to be shown in the tool menu */\n\t this.stored_workflow_menu_entries = options.stored_workflow_menu_entries || [];\n\t\n\t // create tool search, tool panel, and tool panel view.\n\t var tool_search = new Tools.ToolSearch({\n\t search_url : options.search_url,\n\t hidden : false\n\t });\n\t var tools = new Tools.ToolCollection( options.toolbox );\n\t this.tool_panel = new Tools.ToolPanel({\n\t tool_search : tool_search,\n\t tools : tools,\n\t layout : options.toolbox_in_panel\n\t });\n\t this.tool_panel_view = new Tools.ToolPanelView({ model: this.tool_panel });\n\t\n\t // add upload modal\n\t this.uploadButton = new Upload({\n\t nginx_upload_path : options.nginx_upload_path,\n\t ftp_upload_site : options.ftp_upload_site,\n\t default_genome : options.default_genome,\n\t default_extension : options.default_extension,\n\t });\n\t },\n\t\n\t render : function(){\n\t var self = this;\n\t LeftPanel.prototype.render.call( self );\n\t self.$( '.panel-header-buttons' ).append( self.uploadButton.$el );\n\t\n\t // if there are tools, render panel and display everything\n\t if (self.tool_panel.get( 'layout' ).size() > 0) {\n\t self.tool_panel_view.render();\n\t //TODO: why the hide/show?\n\t self.$( '.toolMenu' ).show();\n\t }\n\t self.$( '.toolMenuContainer' ).prepend( self.tool_panel_view.$el );\n\t\n\t self._renderWorkflowMenu();\n\t\n\t // if a tool link has the minsizehint attribute, handle it here (gen. by hiding the tool panel)\n\t self.$( 'a[minsizehint]' ).click( function() {\n\t if ( parent.handle_minwidth_hint ) {\n\t parent.handle_minwidth_hint( $( self ).attr( 'minsizehint' ) );\n\t }\n\t });\n\t },\n\t\n\t /** build the dom for the workflow portion of the tool menu */\n\t _renderWorkflowMenu : function(){\n\t var self = this;\n\t // add internal workflow list\n\t self.$( '#internal-workflows' ).append( self._templateTool({\n\t title : _l( 'All workflows' ),\n\t href : 'workflow/list_for_run'\n\t }));\n\t _.each( self.stored_workflow_menu_entries, function( menu_entry ){\n\t self.$( '#internal-workflows' ).append( self._templateTool({\n\t title : menu_entry.stored_workflow.name,\n\t href : 'workflow/run?id=' + menu_entry.encoded_stored_workflow_id\n\t }));\n\t });\n\t },\n\t\n\t /** build a link to one tool */\n\t _templateTool: function( tool ) {\n\t return [\n\t '
    ',\n\t // global\n\t '', tool.title, '',\n\t '
    '\n\t ].join('');\n\t },\n\t\n\t /** override to include inital menu dom and workflow section */\n\t _templateBody : function(){\n\t return [\n\t '
    ',\n\t '
    ',\n\t '
    ',\n\t '
    ',\n\t '', _l( 'Search did not match any tools.' ), '',\n\t '
    ',\n\t '
    ',\n\t '
    ',\n\t '
    ',\n\t '
    ',\n\t '', _l( 'Workflows' ), '',\n\t '
    ',\n\t '
    ',\n\t '
    ',\n\t '
    ',\n\t '
    ',\n\t '
    '\n\t ].join('');\n\t },\n\t\n\t toString : function(){ return 'ToolPanel'; }\n\t});\n\t\n\tmodule.exports = ToolPanel;\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(1), __webpack_require__(2)))\n\n/***/ },\n/* 91 */,\n/* 92 */,\n/* 93 */,\n/* 94 */,\n/* 95 */,\n/* 96 */,\n/* 97 */,\n/* 98 */,\n/* 99 */,\n/* 100 */,\n/* 101 */,\n/* 102 */,\n/* 103 */,\n/* 104 */,\n/* 105 */,\n/* 106 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(jQuery, _) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(29),\n\t __webpack_require__(69),\n\t __webpack_require__(6),\n\t __webpack_require__(5)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( DC_LI, DATASET_LI_EDIT, BASE_MVC, _l ){\n\t\n\t'use strict';\n\t//==============================================================================\n\tvar DCListItemView = DC_LI.DCListItemView;\n\t/** @class Edit view for DatasetCollection.\n\t */\n\tvar DCListItemEdit = DCListItemView.extend(\n\t/** @lends DCListItemEdit.prototype */{\n\t\n\t /** override to add linkTarget */\n\t initialize : function( attributes ){\n\t DCListItemView.prototype.initialize.call( this, attributes );\n\t },\n\t\n\t // ......................................................................... misc\n\t /** String representation */\n\t toString : function(){\n\t var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n\t return 'DCListItemEdit(' + modelString + ')';\n\t }\n\t});\n\t\n\t\n\t//==============================================================================\n\tvar DCEListItemView = DC_LI.DCEListItemView;\n\t/** @class Read only view for DatasetCollectionElement.\n\t */\n\tvar DCEListItemEdit = DCEListItemView.extend(\n\t/** @lends DCEListItemEdit.prototype */{\n\t//TODO: this might be expendable - compacted with HDAListItemView\n\t\n\t /** set up */\n\t initialize : function( attributes ){\n\t DCEListItemView.prototype.initialize.call( this, attributes );\n\t },\n\t\n\t // ......................................................................... misc\n\t /** String representation */\n\t toString : function(){\n\t var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n\t return 'DCEListItemEdit(' + modelString + ')';\n\t }\n\t});\n\t\n\t\n\t//==============================================================================\n\t// NOTE: this does not inherit from DatasetDCEListItemView as you would expect\n\t//TODO: but should - if we can find something simpler than using diamond\n\t/** @class Editable view for a DatasetCollectionElement that is also an DatasetAssociation\n\t * (a dataset contained in a dataset collection).\n\t */\n\tvar DatasetDCEListItemEdit = DATASET_LI_EDIT.DatasetListItemEdit.extend(\n\t/** @lends DatasetDCEListItemEdit.prototype */{\n\t\n\t /** set up */\n\t initialize : function( attributes ){\n\t DATASET_LI_EDIT.DatasetListItemEdit.prototype.initialize.call( this, attributes );\n\t },\n\t\n\t // NOTE: this does not inherit from DatasetDCEListItemView - so we duplicate this here\n\t //TODO: fix\n\t /** In this override, only get details if in the ready state.\n\t * Note: fetch with no 'change' event triggering to prevent automatic rendering.\n\t */\n\t _fetchModelDetails : function(){\n\t var view = this;\n\t if( view.model.inReadyState() && !view.model.hasDetails() ){\n\t return view.model.fetch({ silent: true });\n\t }\n\t return jQuery.when();\n\t },\n\t\n\t /** Override to remove delete button */\n\t _renderDeleteButton : function(){\n\t return null;\n\t },\n\t\n\t // ......................................................................... misc\n\t /** String representation */\n\t toString : function(){\n\t var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n\t return 'DatasetDCEListItemEdit(' + modelString + ')';\n\t }\n\t});\n\t\n\t// ............................................................................ TEMPLATES\n\t/** underscore templates */\n\tDatasetDCEListItemEdit.prototype.templates = (function(){\n\t\n\t return _.extend( {}, DATASET_LI_EDIT.DatasetListItemEdit.prototype.templates, {\n\t titleBar : DC_LI.DatasetDCEListItemView.prototype.templates.titleBar\n\t });\n\t}());\n\t\n\t\n\t//==============================================================================\n\t/** @class Read only view for a DatasetCollectionElement that is also a DatasetCollection\n\t * (a nested DC).\n\t */\n\tvar NestedDCDCEListItemEdit = DC_LI.NestedDCDCEListItemView.extend(\n\t/** @lends NestedDCDCEListItemEdit.prototype */{\n\t\n\t /** String representation */\n\t toString : function(){\n\t var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n\t return 'NestedDCDCEListItemEdit(' + modelString + ')';\n\t }\n\t});\n\t\n\t\n\t//==============================================================================\n\t return {\n\t DCListItemEdit : DCListItemEdit,\n\t DCEListItemEdit : DCEListItemEdit,\n\t DatasetDCEListItemEdit : DatasetDCEListItemEdit,\n\t NestedDCDCEListItemEdit : NestedDCDCEListItemEdit\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(1), __webpack_require__(2)))\n\n/***/ },\n/* 107 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(68),\n\t __webpack_require__(30),\n\t __webpack_require__(106),\n\t __webpack_require__(6),\n\t __webpack_require__(5),\n\t __webpack_require__(15),\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( DC_VIEW, DC_MODEL, DC_EDIT, BASE_MVC, _l ){\n\t\n\t'use strict';\n\t/* =============================================================================\n\tTODO:\n\t\n\t============================================================================= */\n\t/** @class editable View/Controller for a dataset collection.\n\t */\n\tvar _super = DC_VIEW.CollectionView;\n\tvar CollectionViewEdit = _super.extend(\n\t/** @lends CollectionView.prototype */{\n\t //MODEL is either a DatasetCollection (or subclass) or a DatasetCollectionElement (list of pairs)\n\t\n\t /** logger used to record this.log messages, commonly set to console */\n\t //logger : console,\n\t\n\t /** sub view class used for datasets */\n\t DatasetDCEViewClass : DC_EDIT.DatasetDCEListItemEdit,\n\t /** sub view class used for nested collections */\n\t NestedDCDCEViewClass: DC_EDIT.NestedDCDCEListItemEdit,\n\t\n\t // ......................................................................... SET UP\n\t /** Set up the view, set up storage, bind listeners to HistoryContents events\n\t * @param {Object} attributes optional settings for the panel\n\t */\n\t initialize : function( attributes ){\n\t _super.prototype.initialize.call( this, attributes );\n\t },\n\t\n\t /** In this override, make the collection name editable\n\t */\n\t _setUpBehaviors : function( $where ){\n\t $where = $where || this.$el;\n\t _super.prototype._setUpBehaviors.call( this, $where );\n\t if( !this.model ){ return; }\n\t\n\t // anon users shouldn't have access to any of the following\n\t if( !Galaxy.user || Galaxy.user.isAnonymous() ){\n\t return;\n\t }\n\t\n\t //TODO: extract\n\t var panel = this,\n\t nameSelector = '> .controls .name';\n\t $where.find( nameSelector )\n\t .attr( 'title', _l( 'Click to rename collection' ) )\n\t .tooltip({ placement: 'bottom' })\n\t .make_text_editable({\n\t on_finish: function( newName ){\n\t var previousName = panel.model.get( 'name' );\n\t if( newName && newName !== previousName ){\n\t panel.$el.find( nameSelector ).text( newName );\n\t panel.model.save({ name: newName })\n\t .fail( function(){\n\t panel.$el.find( nameSelector ).text( panel.model.previous( 'name' ) );\n\t });\n\t } else {\n\t panel.$el.find( nameSelector ).text( previousName );\n\t }\n\t }\n\t });\n\t },\n\t\n\t // ........................................................................ misc\n\t /** string rep */\n\t toString : function(){\n\t return 'CollectionViewEdit(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';\n\t }\n\t});\n\t\n\t\n\t// =============================================================================\n\t/** @class non-editable, read-only View/Controller for a dataset collection. */\n\tvar ListCollectionViewEdit = CollectionViewEdit.extend(\n\t/** @lends ListCollectionView.prototype */{\n\t\n\t //TODO: not strictly needed - due to switch in CollectionView._getContentClass\n\t /** sub view class used for datasets */\n\t DatasetDCEViewClass : DC_EDIT.DatasetDCEListItemEdit,\n\t\n\t // ........................................................................ misc\n\t /** string rep */\n\t toString : function(){\n\t return 'ListCollectionViewEdit(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';\n\t }\n\t});\n\t\n\t\n\t// =============================================================================\n\t/** @class Editable, read-only View/Controller for a dataset collection. */\n\tvar PairCollectionViewEdit = ListCollectionViewEdit.extend(\n\t/** @lends PairCollectionViewEdit.prototype */{\n\t\n\t // ........................................................................ misc\n\t /** string rep */\n\t toString : function(){\n\t return 'PairCollectionViewEdit(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';\n\t }\n\t});\n\t\n\t\n\t// =============================================================================\n\t/** @class Editable (roughly since these collections are immutable),\n\t * View/Controller for a dataset collection.\n\t */\n\tvar NestedPairCollectionViewEdit = PairCollectionViewEdit.extend(\n\t/** @lends NestedPairCollectionViewEdit.prototype */{\n\t\n\t /** Override to remove the editable text from the name/identifier - these collections are considered immutable */\n\t _setUpBehaviors : function( $where ){\n\t _super.prototype._setUpBehaviors.call( this, $where );\n\t },\n\t\n\t // ........................................................................ misc\n\t /** string rep */\n\t toString : function(){\n\t return 'NestedPairCollectionViewEdit(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';\n\t }\n\t});\n\t\n\t\n\t// =============================================================================\n\t/** @class editable, View/Controller for a list of pairs dataset collection. */\n\tvar ListOfPairsCollectionViewEdit = CollectionViewEdit.extend(\n\t/** @lends ListOfPairsCollectionView.prototype */{\n\t\n\t //TODO: not strictly needed - due to switch in CollectionView._getContentClass\n\t /** sub view class used for nested collections */\n\t NestedDCDCEViewClass : DC_EDIT.NestedDCDCEListItemEdit.extend({\n\t foldoutPanelClass : NestedPairCollectionViewEdit\n\t }),\n\t\n\t // ........................................................................ misc\n\t /** string rep */\n\t toString : function(){\n\t return 'ListOfPairsCollectionViewEdit(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';\n\t }\n\t});\n\t\n\t\n\t// =============================================================================\n\t/** @class View/Controller for a list of lists dataset collection. */\n\tvar ListOfListsCollectionViewEdit = CollectionViewEdit.extend(\n\t/** @lends ListOfListsCollectionView.prototype */{\n\t\n\t //TODO: not strictly needed - due to switch in CollectionView._getContentClass\n\t /** sub view class used for nested collections */\n\t NestedDCDCEViewClass : DC_EDIT.NestedDCDCEListItemEdit.extend({\n\t foldoutPanelClass : NestedPairCollectionViewEdit\n\t }),\n\t\n\t // ........................................................................ misc\n\t /** string rep */\n\t toString : function(){\n\t return 'ListOfListsCollectionViewEdit(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';\n\t }\n\t});\n\t\n\t\n\t//==============================================================================\n\t return {\n\t CollectionViewEdit : CollectionViewEdit,\n\t ListCollectionViewEdit : ListCollectionViewEdit,\n\t PairCollectionViewEdit : PairCollectionViewEdit,\n\t ListOfPairsCollectionViewEdit : ListOfPairsCollectionViewEdit,\n\t ListOfListsCollectionViewEdit : ListOfListsCollectionViewEdit\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\n\n/***/ },\n/* 108 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, _, jQuery, $) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(131),\n\t __webpack_require__(87),\n\t __webpack_require__(31),\n\t __webpack_require__(6),\n\t __webpack_require__(5),\n\t __webpack_require__(84)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( levenshteinDistance, naturalSort, LIST_COLLECTION_CREATOR, baseMVC, _l ){\n\t\n\t'use strict';\n\t\n\tvar logNamespace = 'collections';\n\t/* ============================================================================\n\tTODO:\n\t\n\t\n\tPROGRAMMATICALLY:\n\tcurrPanel.once( 'rendered', function(){\n\t currPanel.showSelectors();\n\t currPanel.selectAll();\n\t _.last( currPanel.actionsPopup.options ).func();\n\t});\n\t\n\t============================================================================ */\n\t/** A view for paired datasets in the collections creator.\n\t */\n\tvar PairView = Backbone.View.extend( baseMVC.LoggableMixin ).extend({\n\t _logNamespace : logNamespace,\n\t\n\t tagName : 'li',\n\t className : 'dataset paired',\n\t\n\t initialize : function( attributes ){\n\t this.pair = attributes.pair || {};\n\t },\n\t\n\t template : _.template([\n\t '<%- pair.forward.name %>',\n\t '',\n\t '<%- pair.name %>',\n\t '',\n\t '<%- pair.reverse.name %>'\n\t ].join('')),\n\t\n\t render : function(){\n\t this.$el\n\t .attr( 'draggable', true )\n\t .data( 'pair', this.pair )\n\t .html( this.template({ pair: this.pair }) )\n\t .addClass( 'flex-column-container' );\n\t return this;\n\t },\n\t\n\t events : {\n\t 'dragstart' : '_dragstart',\n\t 'dragend' : '_dragend',\n\t 'dragover' : '_sendToParent',\n\t 'drop' : '_sendToParent'\n\t },\n\t\n\t /** dragging pairs for re-ordering */\n\t _dragstart : function( ev ){\n\t ev.currentTarget.style.opacity = '0.4';\n\t if( ev.originalEvent ){ ev = ev.originalEvent; }\n\t\n\t ev.dataTransfer.effectAllowed = 'move';\n\t ev.dataTransfer.setData( 'text/plain', JSON.stringify( this.pair ) );\n\t\n\t this.$el.parent().trigger( 'pair.dragstart', [ this ] );\n\t },\n\t\n\t /** dragging pairs for re-ordering */\n\t _dragend : function( ev ){\n\t ev.currentTarget.style.opacity = '1.0';\n\t this.$el.parent().trigger( 'pair.dragend', [ this ] );\n\t },\n\t\n\t /** manually bubble up an event to the parent/container */\n\t _sendToParent : function( ev ){\n\t this.$el.parent().trigger( ev );\n\t },\n\t\n\t /** string rep */\n\t toString : function(){\n\t return 'PairView(' + this.pair.name + ')';\n\t }\n\t});\n\t\n\t\n\t// ============================================================================\n\t/** returns an autopair function that uses the provided options.match function */\n\tfunction autoPairFnBuilder( options ){\n\t options = options || {};\n\t options.createPair = options.createPair || function _defaultCreatePair( params ){\n\t params = params || {};\n\t var a = params.listA.splice( params.indexA, 1 )[0],\n\t b = params.listB.splice( params.indexB, 1 )[0],\n\t aInBIndex = params.listB.indexOf( a ),\n\t bInAIndex = params.listA.indexOf( b );\n\t if( aInBIndex !== -1 ){ params.listB.splice( aInBIndex, 1 ); }\n\t if( bInAIndex !== -1 ){ params.listA.splice( bInAIndex, 1 ); }\n\t return this._pair( a, b, { silent: true });\n\t };\n\t // compile these here outside of the loop\n\t var _regexps = [];\n\t function getRegExps(){\n\t if( !_regexps.length ){\n\t _regexps = [\n\t new RegExp( this.filters[0] ),\n\t new RegExp( this.filters[1] )\n\t ];\n\t }\n\t return _regexps;\n\t }\n\t // mangle params as needed\n\t options.preprocessMatch = options.preprocessMatch || function _defaultPreprocessMatch( params ){\n\t var regexps = getRegExps.call( this );\n\t return _.extend( params, {\n\t matchTo : params.matchTo.name.replace( regexps[0], '' ),\n\t possible : params.possible.name.replace( regexps[1], '' )\n\t });\n\t };\n\t\n\t return function _strategy( params ){\n\t this.debug( 'autopair _strategy ---------------------------' );\n\t params = params || {};\n\t var listA = params.listA,\n\t listB = params.listB,\n\t indexA = 0, indexB,\n\t bestMatch = {\n\t score : 0.0,\n\t index : null\n\t },\n\t paired = [];\n\t //console.debug( 'params:', JSON.stringify( params, null, ' ' ) );\n\t this.debug( 'starting list lens:', listA.length, listB.length );\n\t this.debug( 'bestMatch (starting):', JSON.stringify( bestMatch, null, ' ' ) );\n\t\n\t while( indexA < listA.length ){\n\t var matchTo = listA[ indexA ];\n\t bestMatch.score = 0.0;\n\t\n\t for( indexB=0; indexB= scoreThreshold ){\n\t //console.debug( 'autoPairFnBuilder.strategy', listA[ indexA ].name, listB[ bestMatch.index ].name );\n\t paired.push( options.createPair.call( this, {\n\t listA : listA,\n\t indexA : indexA,\n\t listB : listB,\n\t indexB : bestMatch.index\n\t }));\n\t //console.debug( 'list lens now:', listA.length, listB.length );\n\t } else {\n\t indexA += 1;\n\t }\n\t if( !listA.length || !listB.length ){\n\t return paired;\n\t }\n\t }\n\t this.debug( 'paired:', JSON.stringify( paired, null, ' ' ) );\n\t this.debug( 'autopair _strategy ---------------------------' );\n\t return paired;\n\t };\n\t}\n\t\n\t\n\t// ============================================================================\n\t/** An interface for building collections of paired datasets.\n\t */\n\tvar PairedCollectionCreator = Backbone.View.extend( baseMVC.LoggableMixin ).extend({\n\t _logNamespace : logNamespace,\n\t\n\t className: 'list-of-pairs-collection-creator collection-creator flex-row-container',\n\t\n\t /** set up initial options, instance vars, behaviors, and autopair (if set to do so) */\n\t initialize : function( attributes ){\n\t this.metric( 'PairedCollectionCreator.initialize', attributes );\n\t //this.debug( '-- PairedCollectionCreator:', attributes );\n\t\n\t attributes = _.defaults( attributes, {\n\t datasets : [],\n\t filters : this.DEFAULT_FILTERS,\n\t automaticallyPair : true,\n\t strategy : 'lcs',\n\t matchPercentage : 0.9,\n\t twoPassAutopairing : true\n\t });\n\t\n\t /** unordered, original list */\n\t this.initialList = attributes.datasets;\n\t\n\t /** is this from a history? if so, what's its id? */\n\t this.historyId = attributes.historyId;\n\t\n\t /** which filters should be used initially? (String[2] or name in commonFilters) */\n\t this.filters = this.commonFilters[ attributes.filters ] || this.commonFilters[ this.DEFAULT_FILTERS ];\n\t if( _.isArray( attributes.filters ) ){\n\t this.filters = attributes.filters;\n\t }\n\t\n\t /** try to auto pair the unpaired datasets on load? */\n\t this.automaticallyPair = attributes.automaticallyPair;\n\t\n\t /** what method to use for auto pairing (will be passed aggression level) */\n\t this.strategy = this.strategies[ attributes.strategy ] || this.strategies[ this.DEFAULT_STRATEGY ];\n\t if( _.isFunction( attributes.strategy ) ){\n\t this.strategy = attributes.strategy;\n\t }\n\t\n\t /** distance/mismatch level allowed for autopairing */\n\t this.matchPercentage = attributes.matchPercentage;\n\t\n\t /** try to autopair using simple first, then this.strategy on the remainder */\n\t this.twoPassAutopairing = attributes.twoPassAutopairing;\n\t\n\t /** remove file extensions (\\.*) from created pair names? */\n\t this.removeExtensions = true;\n\t //this.removeExtensions = false;\n\t\n\t /** fn to call when the cancel button is clicked (scoped to this) - if falsy, no btn is displayed */\n\t this.oncancel = attributes.oncancel;\n\t /** fn to call when the collection is created (scoped to this) */\n\t this.oncreate = attributes.oncreate;\n\t\n\t /** fn to call when the cancel button is clicked (scoped to this) - if falsy, no btn is displayed */\n\t this.autoscrollDist = attributes.autoscrollDist || 24;\n\t\n\t /** is the unpaired panel shown? */\n\t this.unpairedPanelHidden = false;\n\t /** is the paired panel shown? */\n\t this.pairedPanelHidden = false;\n\t\n\t /** DOM elements currently being dragged */\n\t this.$dragging = null;\n\t\n\t /** Used for blocking UI events during ajax/operations (don't post twice) */\n\t this.blocking = false;\n\t\n\t this._setUpBehaviors();\n\t this._dataSetUp();\n\t },\n\t\n\t /** map of common filter pairs by name */\n\t commonFilters : {\n\t illumina : [ '_1', '_2' ],\n\t Rs : [ '_R1', '_R2' ]\n\t },\n\t /** which commonFilter to use by default */\n\t DEFAULT_FILTERS : 'illumina',\n\t\n\t /** map of name->fn for autopairing */\n\t strategies : {\n\t 'simple' : 'autopairSimple',\n\t 'lcs' : 'autopairLCS',\n\t 'levenshtein' : 'autopairLevenshtein'\n\t },\n\t /** default autopair strategy name */\n\t DEFAULT_STRATEGY : 'lcs',\n\t\n\t // ------------------------------------------------------------------------ process raw list\n\t /** set up main data: cache initialList, sort, and autopair */\n\t _dataSetUp : function(){\n\t //this.debug( '-- _dataSetUp' );\n\t\n\t this.paired = [];\n\t this.unpaired = [];\n\t\n\t this.selectedIds = [];\n\t\n\t // sort initial list, add ids if needed, and save new working copy to unpaired\n\t this._sortInitialList();\n\t this._ensureIds();\n\t this.unpaired = this.initialList.slice( 0 );\n\t\n\t if( this.automaticallyPair ){\n\t this.autoPair();\n\t this.once( 'rendered:initial', function(){\n\t this.trigger( 'autopair' );\n\t });\n\t }\n\t },\n\t\n\t /** sort initial list */\n\t _sortInitialList : function(){\n\t //this.debug( '-- _sortInitialList' );\n\t this._sortDatasetList( this.initialList );\n\t },\n\t\n\t /** sort a list of datasets */\n\t _sortDatasetList : function( list ){\n\t // currently only natural sort by name\n\t list.sort( function( a, b ){ return naturalSort( a.name, b.name ); });\n\t return list;\n\t },\n\t\n\t /** add ids to dataset objs in initial list if none */\n\t _ensureIds : function(){\n\t this.initialList.forEach( function( dataset ){\n\t if( !dataset.hasOwnProperty( 'id' ) ){\n\t dataset.id = _.uniqueId();\n\t }\n\t });\n\t return this.initialList;\n\t },\n\t\n\t /** split initial list into two lists, those that pass forward filters & those passing reverse */\n\t _splitByFilters : function(){\n\t var regexFilters = this.filters.map( function( stringFilter ){\n\t return new RegExp( stringFilter );\n\t }),\n\t split = [ [], [] ];\n\t\n\t function _filter( unpaired, filter ){\n\t return filter.test( unpaired.name );\n\t //return dataset.name.indexOf( filter ) >= 0;\n\t }\n\t this.unpaired.forEach( function _filterEach( unpaired ){\n\t // 90% of the time this seems to work, but:\n\t //TODO: this treats *all* strings as regex which may confuse people - possibly check for // surrounding?\n\t // would need explanation in help as well\n\t regexFilters.forEach( function( filter, i ){\n\t if( _filter( unpaired, filter ) ){\n\t split[i].push( unpaired );\n\t }\n\t });\n\t });\n\t return split;\n\t },\n\t\n\t /** add a dataset to the unpaired list in it's proper order */\n\t _addToUnpaired : function( dataset ){\n\t // currently, unpaired is natural sorted by name, use binary search to find insertion point\n\t var binSearchSortedIndex = function( low, hi ){\n\t if( low === hi ){ return low; }\n\t\n\t var mid = Math.floor( ( hi - low ) / 2 ) + low,\n\t compared = naturalSort( dataset.name, this.unpaired[ mid ].name );\n\t\n\t if( compared < 0 ){\n\t return binSearchSortedIndex( low, mid );\n\t } else if( compared > 0 ){\n\t return binSearchSortedIndex( mid + 1, hi );\n\t }\n\t // walk the equal to find the last\n\t while( this.unpaired[ mid ] && this.unpaired[ mid ].name === dataset.name ){ mid++; }\n\t return mid;\n\t\n\t }.bind( this );\n\t\n\t this.unpaired.splice( binSearchSortedIndex( 0, this.unpaired.length ), 0, dataset );\n\t },\n\t\n\t // ------------------------------------------------------------------------ auto pairing\n\t /** two passes to automatically create pairs:\n\t * use both simpleAutoPair, then the fn mentioned in strategy\n\t */\n\t autoPair : function( strategy ){\n\t // split first using exact matching\n\t var split = this._splitByFilters(),\n\t paired = [];\n\t if( this.twoPassAutopairing ){\n\t paired = this.autopairSimple({\n\t listA : split[0],\n\t listB : split[1]\n\t });\n\t split = this._splitByFilters();\n\t }\n\t\n\t // uncomment to see printlns while running tests\n\t //this.debug = function(){ console.log.apply( console, arguments ); };\n\t\n\t // then try the remainder with something less strict\n\t strategy = strategy || this.strategy;\n\t split = this._splitByFilters();\n\t paired = paired.concat( this[ strategy ].call( this, {\n\t listA : split[0],\n\t listB : split[1]\n\t }));\n\t return paired;\n\t },\n\t\n\t /** autopair by exact match */\n\t autopairSimple : autoPairFnBuilder({\n\t scoreThreshold: function(){ return 1.0; },\n\t match : function _match( params ){\n\t params = params || {};\n\t if( params.matchTo === params.possible ){\n\t return {\n\t index: params.index,\n\t score: 1.0\n\t };\n\t }\n\t return params.bestMatch;\n\t }\n\t }),\n\t\n\t /** autopair by levenshtein edit distance scoring */\n\t autopairLevenshtein : autoPairFnBuilder({\n\t scoreThreshold: function(){ return this.matchPercentage; },\n\t match : function _matches( params ){\n\t params = params || {};\n\t var distance = levenshteinDistance( params.matchTo, params.possible ),\n\t score = 1.0 - ( distance / ( Math.max( params.matchTo.length, params.possible.length ) ) );\n\t if( score > params.bestMatch.score ){\n\t return {\n\t index: params.index,\n\t score: score\n\t };\n\t }\n\t return params.bestMatch;\n\t }\n\t }),\n\t\n\t /** autopair by longest common substrings scoring */\n\t autopairLCS : autoPairFnBuilder({\n\t scoreThreshold: function(){ return this.matchPercentage; },\n\t match : function _matches( params ){\n\t params = params || {};\n\t var match = this._naiveStartingAndEndingLCS( params.matchTo, params.possible ).length,\n\t score = match / ( Math.max( params.matchTo.length, params.possible.length ) );\n\t if( score > params.bestMatch.score ){\n\t return {\n\t index: params.index,\n\t score: score\n\t };\n\t }\n\t return params.bestMatch;\n\t }\n\t }),\n\t\n\t /** return the concat'd longest common prefix and suffix from two strings */\n\t _naiveStartingAndEndingLCS : function( s1, s2 ){\n\t var fwdLCS = '',\n\t revLCS = '',\n\t i = 0, j = 0;\n\t while( i < s1.length && i < s2.length ){\n\t if( s1[ i ] !== s2[ i ] ){\n\t break;\n\t }\n\t fwdLCS += s1[ i ];\n\t i += 1;\n\t }\n\t if( i === s1.length ){ return s1; }\n\t if( i === s2.length ){ return s2; }\n\t\n\t i = ( s1.length - 1 );\n\t j = ( s2.length - 1 );\n\t while( i >= 0 && j >= 0 ){\n\t if( s1[ i ] !== s2[ j ] ){\n\t break;\n\t }\n\t revLCS = [ s1[ i ], revLCS ].join( '' );\n\t i -= 1;\n\t j -= 1;\n\t }\n\t return fwdLCS + revLCS;\n\t },\n\t\n\t // ------------------------------------------------------------------------ pairing / unpairing\n\t /** create a pair from fwd and rev, removing them from unpaired, and placing the new pair in paired */\n\t _pair : function( fwd, rev, options ){\n\t options = options || {};\n\t this.debug( '_pair:', fwd, rev );\n\t var pair = this._createPair( fwd, rev, options.name );\n\t this.paired.push( pair );\n\t this.unpaired = _.without( this.unpaired, fwd, rev );\n\t if( !options.silent ){\n\t this.trigger( 'pair:new', pair );\n\t }\n\t return pair;\n\t },\n\t\n\t /** create a pair Object from fwd and rev, adding the name attribute (will guess if not given) */\n\t _createPair : function( fwd, rev, name ){\n\t // ensure existance and don't pair something with itself\n\t if( !( fwd && rev ) || ( fwd === rev ) ){\n\t throw new Error( 'Bad pairing: ' + [ JSON.stringify( fwd ), JSON.stringify( rev ) ] );\n\t }\n\t name = name || this._guessNameForPair( fwd, rev );\n\t return { forward : fwd, name : name, reverse : rev };\n\t },\n\t\n\t /** try to find a good pair name for the given fwd and rev datasets */\n\t _guessNameForPair : function( fwd, rev, removeExtensions ){\n\t removeExtensions = ( removeExtensions !== undefined )?( removeExtensions ):( this.removeExtensions );\n\t var fwdName = fwd.name,\n\t revName = rev.name,\n\t lcs = this._naiveStartingAndEndingLCS(\n\t fwdName.replace( new RegExp( this.filters[0] ), '' ),\n\t revName.replace( new RegExp( this.filters[1] ), '' )\n\t );\n\t if( removeExtensions ){\n\t var lastDotIndex = lcs.lastIndexOf( '.' );\n\t if( lastDotIndex > 0 ){\n\t var extension = lcs.slice( lastDotIndex, lcs.length );\n\t lcs = lcs.replace( extension, '' );\n\t fwdName = fwdName.replace( extension, '' );\n\t revName = revName.replace( extension, '' );\n\t }\n\t }\n\t return lcs || ( fwdName + ' & ' + revName );\n\t },\n\t\n\t /** unpair a pair, removing it from paired, and adding the fwd,rev datasets back into unpaired */\n\t _unpair : function( pair, options ){\n\t options = options || {};\n\t if( !pair ){\n\t throw new Error( 'Bad pair: ' + JSON.stringify( pair ) );\n\t }\n\t this.paired = _.without( this.paired, pair );\n\t this._addToUnpaired( pair.forward );\n\t this._addToUnpaired( pair.reverse );\n\t\n\t if( !options.silent ){\n\t this.trigger( 'pair:unpair', [ pair ] );\n\t }\n\t return pair;\n\t },\n\t\n\t /** unpair all paired datasets */\n\t unpairAll : function(){\n\t var pairs = [];\n\t while( this.paired.length ){\n\t pairs.push( this._unpair( this.paired[ 0 ], { silent: true }) );\n\t }\n\t this.trigger( 'pair:unpair', pairs );\n\t },\n\t\n\t // ------------------------------------------------------------------------ API\n\t /** convert a pair into JSON compatible with the collections API */\n\t _pairToJSON : function( pair, src ){\n\t src = src || 'hda';\n\t //TODO: consider making this the pair structure when created instead\n\t return {\n\t collection_type : 'paired',\n\t src : 'new_collection',\n\t name : pair.name,\n\t element_identifiers : [{\n\t name : 'forward',\n\t id : pair.forward.id,\n\t src : src\n\t }, {\n\t name : 'reverse',\n\t id : pair.reverse.id,\n\t src : src\n\t }]\n\t };\n\t },\n\t\n\t /** create the collection via the API\n\t * @returns {jQuery.xhr Object} the jquery ajax request\n\t */\n\t createList : function( name ){\n\t var creator = this,\n\t url = Galaxy.root + 'api/histories/' + this.historyId + '/contents/dataset_collections';\n\t\n\t //TODO: use ListPairedCollection.create()\n\t var ajaxData = {\n\t type : 'dataset_collection',\n\t collection_type : 'list:paired',\n\t name : _.escape( name || creator.$( '.collection-name' ).val() ),\n\t element_identifiers : creator.paired.map( function( pair ){\n\t return creator._pairToJSON( pair );\n\t })\n\t\n\t };\n\t //this.debug( JSON.stringify( ajaxData ) );\n\t creator.blocking = true;\n\t return jQuery.ajax( url, {\n\t type : 'POST',\n\t contentType : 'application/json',\n\t dataType : 'json',\n\t data : JSON.stringify( ajaxData )\n\t })\n\t .always( function(){\n\t creator.blocking = false;\n\t })\n\t .fail( function( xhr, status, message ){\n\t creator._ajaxErrHandler( xhr, status, message );\n\t })\n\t .done( function( response, message, xhr ){\n\t //this.info( 'ok', response, message, xhr );\n\t creator.trigger( 'collection:created', response, message, xhr );\n\t creator.metric( 'collection:created', response );\n\t if( typeof creator.oncreate === 'function' ){\n\t creator.oncreate.call( this, response, message, xhr );\n\t }\n\t });\n\t },\n\t\n\t /** handle ajax errors with feedback and details to the user (if available) */\n\t _ajaxErrHandler : function( xhr, status, message ){\n\t this.error( xhr, status, message );\n\t var content = _l( 'An error occurred while creating this collection' );\n\t if( xhr ){\n\t if( xhr.readyState === 0 && xhr.status === 0 ){\n\t content += ': ' + _l( 'Galaxy could not be reached and may be updating.' )\n\t + _l( ' Try again in a few minutes.' );\n\t } else if( xhr.responseJSON ){\n\t content += '
    ' + JSON.stringify( xhr.responseJSON ) + '
    ';\n\t } else {\n\t content += ': ' + message;\n\t }\n\t }\n\t creator._showAlert( content, 'alert-danger' );\n\t },\n\t\n\t // ------------------------------------------------------------------------ rendering\n\t /** render the entire interface */\n\t render : function( speed, callback ){\n\t //this.debug( '-- _render' );\n\t //this.$el.empty().html( PairedCollectionCreator.templates.main() );\n\t this.$el.empty().html( PairedCollectionCreator.templates.main() );\n\t this._renderHeader( speed );\n\t this._renderMiddle( speed );\n\t this._renderFooter( speed );\n\t this._addPluginComponents();\n\t this.trigger( 'rendered', this );\n\t return this;\n\t },\n\t\n\t /** render the header section */\n\t _renderHeader : function( speed, callback ){\n\t //this.debug( '-- _renderHeader' );\n\t var $header = this.$( '.header' ).empty().html( PairedCollectionCreator.templates.header() )\n\t .find( '.help-content' ).prepend( $( PairedCollectionCreator.templates.helpContent() ) );\n\t\n\t this._renderFilters();\n\t return $header;\n\t },\n\t /** fill the filter inputs with the filter values */\n\t _renderFilters : function(){\n\t return this.$( '.forward-column .column-header input' ).val( this.filters[0] )\n\t .add( this.$( '.reverse-column .column-header input' ).val( this.filters[1] ) );\n\t },\n\t\n\t /** render the middle including unpaired and paired sections (which may be hidden) */\n\t _renderMiddle : function( speed, callback ){\n\t var $middle = this.$( '.middle' ).empty().html( PairedCollectionCreator.templates.middle() );\n\t\n\t // (re-) hide the un/paired panels based on instance vars\n\t if( this.unpairedPanelHidden ){\n\t this.$( '.unpaired-columns' ).hide();\n\t } else if( this.pairedPanelHidden ){\n\t this.$( '.paired-columns' ).hide();\n\t }\n\t\n\t this._renderUnpaired();\n\t this._renderPaired();\n\t return $middle;\n\t },\n\t /** render the unpaired section, showing datasets accrd. to filters, update the unpaired counts */\n\t _renderUnpaired : function( speed, callback ){\n\t //this.debug( '-- _renderUnpaired' );\n\t var creator = this,\n\t $fwd, $rev, $prd = [],\n\t split = this._splitByFilters();\n\t // update unpaired counts\n\t this.$( '.forward-column .title' )\n\t .text([ split[0].length, _l( 'unpaired forward' ) ].join( ' ' ));\n\t this.$( '.forward-column .unpaired-info' )\n\t .text( this._renderUnpairedDisplayStr( this.unpaired.length - split[0].length ) );\n\t this.$( '.reverse-column .title' )\n\t .text([ split[1].length, _l( 'unpaired reverse' ) ].join( ' ' ));\n\t this.$( '.reverse-column .unpaired-info' )\n\t .text( this._renderUnpairedDisplayStr( this.unpaired.length - split[1].length ) );\n\t\n\t this.$( '.unpaired-columns .column-datasets' ).empty();\n\t\n\t // show/hide the auto pair button if any unpaired are left\n\t this.$( '.autopair-link' ).toggle( this.unpaired.length !== 0 );\n\t if( this.unpaired.length === 0 ){\n\t this._renderUnpairedEmpty();\n\t return;\n\t }\n\t\n\t // create the dataset dom arrays\n\t $rev = split[1].map( function( dataset, i ){\n\t // if there'll be a fwd dataset across the way, add a button to pair the row\n\t if( ( split[0][ i ] !== undefined )\n\t && ( split[0][ i ] !== dataset ) ){\n\t $prd.push( creator._renderPairButton() );\n\t }\n\t return creator._renderUnpairedDataset( dataset );\n\t });\n\t $fwd = split[0].map( function( dataset ){\n\t return creator._renderUnpairedDataset( dataset );\n\t });\n\t\n\t if( !$fwd.length && !$rev.length ){\n\t this._renderUnpairedNotShown();\n\t return;\n\t }\n\t // add to appropo cols\n\t //TODO: not the best way to render - consider rendering the entire unpaired-columns section in a fragment\n\t // and swapping out that\n\t this.$( '.unpaired-columns .forward-column .column-datasets' ).append( $fwd )\n\t .add( this.$( '.unpaired-columns .paired-column .column-datasets' ).append( $prd ) )\n\t .add( this.$( '.unpaired-columns .reverse-column .column-datasets' ).append( $rev ) );\n\t this._adjUnpairedOnScrollbar();\n\t },\n\t /** return a string to display the count of filtered out datasets */\n\t _renderUnpairedDisplayStr : function( numFiltered ){\n\t return [ '(', numFiltered, ' ', _l( 'filtered out' ), ')' ].join('');\n\t },\n\t /** return an unattached jQuery DOM element to represent an unpaired dataset */\n\t _renderUnpairedDataset : function( dataset ){\n\t //TODO: to underscore template\n\t return $( '
  • ')\n\t .attr( 'id', 'dataset-' + dataset.id )\n\t .addClass( 'dataset unpaired' )\n\t .attr( 'draggable', true )\n\t .addClass( dataset.selected? 'selected': '' )\n\t .append( $( '' ).addClass( 'dataset-name' ).text( dataset.name ) )\n\t //??\n\t .data( 'dataset', dataset );\n\t },\n\t /** render the button that may go between unpaired datasets, allowing the user to pair a row */\n\t _renderPairButton : function(){\n\t //TODO: *not* a dataset - don't pretend like it is\n\t return $( '
  • ').addClass( 'dataset unpaired' )\n\t .append( $( '' ).addClass( 'dataset-name' ).text( _l( 'Pair these datasets' ) ) );\n\t },\n\t /** a message to display when no unpaired left */\n\t _renderUnpairedEmpty : function(){\n\t //this.debug( '-- renderUnpairedEmpty' );\n\t var $msg = $( '
    ' )\n\t .text( '(' + _l( 'no remaining unpaired datasets' ) + ')' );\n\t this.$( '.unpaired-columns .paired-column .column-datasets' ).empty().prepend( $msg );\n\t return $msg;\n\t },\n\t /** a message to display when no unpaired can be shown with the current filters */\n\t _renderUnpairedNotShown : function(){\n\t //this.debug( '-- renderUnpairedEmpty' );\n\t var $msg = $( '
    ' )\n\t .text( '(' + _l( 'no datasets were found matching the current filters' ) + ')' );\n\t this.$( '.unpaired-columns .paired-column .column-datasets' ).empty().prepend( $msg );\n\t return $msg;\n\t },\n\t /** try to detect if the unpaired section has a scrollbar and adjust left column for better centering of all */\n\t _adjUnpairedOnScrollbar : function(){\n\t var $unpairedColumns = this.$( '.unpaired-columns' ).last(),\n\t $firstDataset = this.$( '.unpaired-columns .reverse-column .dataset' ).first();\n\t if( !$firstDataset.length ){ return; }\n\t var ucRight = $unpairedColumns.offset().left + $unpairedColumns.outerWidth(),\n\t dsRight = $firstDataset.offset().left + $firstDataset.outerWidth(),\n\t rightDiff = Math.floor( ucRight ) - Math.floor( dsRight );\n\t //this.debug( 'rightDiff:', ucRight, '-', dsRight, '=', rightDiff );\n\t this.$( '.unpaired-columns .forward-column' )\n\t .css( 'margin-left', ( rightDiff > 0 )? rightDiff: 0 );\n\t },\n\t\n\t /** render the paired section and update counts of paired datasets */\n\t _renderPaired : function( speed, callback ){\n\t //this.debug( '-- _renderPaired' );\n\t this.$( '.paired-column-title .title' ).text([ this.paired.length, _l( 'paired' ) ].join( ' ' ) );\n\t // show/hide the unpair all link\n\t this.$( '.unpair-all-link' ).toggle( this.paired.length !== 0 );\n\t if( this.paired.length === 0 ){\n\t this._renderPairedEmpty();\n\t return;\n\t //TODO: would be best to return here (the $columns)\n\t } else {\n\t // show/hide 'remove extensions link' when any paired and they seem to have extensions\n\t this.$( '.remove-extensions-link' ).show();\n\t }\n\t\n\t this.$( '.paired-columns .column-datasets' ).empty();\n\t var creator = this;\n\t this.paired.forEach( function( pair, i ){\n\t //TODO: cache these?\n\t var pairView = new PairView({ pair: pair });\n\t creator.$( '.paired-columns .column-datasets' )\n\t .append( pairView.render().$el )\n\t .append([\n\t ''\n\t ].join( '' ));\n\t });\n\t },\n\t /** a message to display when none paired */\n\t _renderPairedEmpty : function(){\n\t var $msg = $( '
    ' )\n\t .text( '(' + _l( 'no paired datasets yet' ) + ')' );\n\t this.$( '.paired-columns .column-datasets' ).empty().prepend( $msg );\n\t return $msg;\n\t },\n\t\n\t /** render the footer, completion controls, and cancel controls */\n\t _renderFooter : function( speed, callback ){\n\t var $footer = this.$( '.footer' ).empty().html( PairedCollectionCreator.templates.footer() );\n\t this.$( '.remove-extensions' ).prop( 'checked', this.removeExtensions );\n\t if( typeof this.oncancel === 'function' ){\n\t this.$( '.cancel-create.btn' ).show();\n\t }\n\t return $footer;\n\t },\n\t\n\t /** add any jQuery/bootstrap/custom plugins to elements rendered */\n\t _addPluginComponents : function(){\n\t this._chooseFiltersPopover( '.choose-filters-link' );\n\t this.$( '.help-content i' ).hoverhighlight( '.collection-creator', 'rgba( 64, 255, 255, 1.0 )' );\n\t },\n\t\n\t /** build a filter selection popover allowing selection of common filter pairs */\n\t _chooseFiltersPopover : function( selector ){\n\t function filterChoice( val1, val2 ){\n\t return [\n\t ''\n\t ].join('');\n\t }\n\t var $popoverContent = $( _.template([\n\t '
    ',\n\t '
    ',\n\t _l( 'Choose from the following filters to change which unpaired reads are shown in the display' ),\n\t ':
    ',\n\t _.values( this.commonFilters ).map( function( filterSet ){\n\t return filterChoice( filterSet[0], filterSet[1] );\n\t }).join( '' ),\n\t '
    '\n\t ].join(''))({}));\n\t\n\t return this.$( selector ).popover({\n\t container : '.collection-creator',\n\t placement : 'bottom',\n\t html : true,\n\t //animation : false,\n\t content : $popoverContent\n\t });\n\t },\n\t\n\t /** add (or clear if clear is truthy) a validation warning to what */\n\t _validationWarning : function( what, clear ){\n\t var VALIDATION_CLASS = 'validation-warning';\n\t if( what === 'name' ){\n\t what = this.$( '.collection-name' ).add( this.$( '.collection-name-prompt' ) );\n\t this.$( '.collection-name' ).focus().select();\n\t }\n\t if( clear ){\n\t what = what || this.$( '.' + VALIDATION_CLASS );\n\t what.removeClass( VALIDATION_CLASS );\n\t } else {\n\t what.addClass( VALIDATION_CLASS );\n\t }\n\t },\n\t\n\t // ------------------------------------------------------------------------ events\n\t /** set up event handlers on self */\n\t _setUpBehaviors : function(){\n\t this.once( 'rendered', function(){\n\t this.trigger( 'rendered:initial', this );\n\t });\n\t\n\t this.on( 'pair:new', function(){\n\t //TODO: ideally only re-render the columns (or even elements) involved\n\t this._renderUnpaired();\n\t this._renderPaired();\n\t\n\t // scroll to bottom where new pairs are added\n\t //TODO: this doesn't seem to work - innerHeight sticks at 133...\n\t // may have to do with improper flex columns\n\t //var $pairedView = this.$( '.paired-columns' );\n\t //$pairedView.scrollTop( $pairedView.innerHeight() );\n\t //this.debug( $pairedView.height() )\n\t this.$( '.paired-columns' ).scrollTop( 8000000 );\n\t });\n\t this.on( 'pair:unpair', function( pairs ){\n\t //TODO: ideally only re-render the columns (or even elements) involved\n\t this._renderUnpaired();\n\t this._renderPaired();\n\t this.splitView();\n\t });\n\t\n\t this.on( 'filter-change', function(){\n\t this.filters = [\n\t this.$( '.forward-unpaired-filter input' ).val(),\n\t this.$( '.reverse-unpaired-filter input' ).val()\n\t ];\n\t this.metric( 'filter-change', this.filters );\n\t this._renderFilters();\n\t this._renderUnpaired();\n\t });\n\t\n\t this.on( 'autopair', function(){\n\t this._renderUnpaired();\n\t this._renderPaired();\n\t\n\t var message, msgClass = null;\n\t if( this.paired.length ){\n\t msgClass = 'alert-success';\n\t message = this.paired.length + ' ' + _l( 'pairs created' );\n\t if( !this.unpaired.length ){\n\t message += ': ' + _l( 'all datasets have been successfully paired' );\n\t this.hideUnpaired();\n\t this.$( '.collection-name' ).focus();\n\t }\n\t } else {\n\t message = _l([\n\t 'Could not automatically create any pairs from the given dataset names.',\n\t 'You may want to choose or enter different filters and try auto-pairing again.',\n\t 'Close this message using the X on the right to view more help.'\n\t ].join( ' ' ));\n\t }\n\t this._showAlert( message, msgClass );\n\t });\n\t\n\t //this.on( 'all', function(){\n\t // this.info( arguments );\n\t //});\n\t return this;\n\t },\n\t\n\t events : {\n\t // header\n\t 'click .more-help' : '_clickMoreHelp',\n\t 'click .less-help' : '_clickLessHelp',\n\t 'click .header .alert button' : '_hideAlert',\n\t 'click .forward-column .column-title' : '_clickShowOnlyUnpaired',\n\t 'click .reverse-column .column-title' : '_clickShowOnlyUnpaired',\n\t 'click .unpair-all-link' : '_clickUnpairAll',\n\t //TODO: this seems kinda backasswards - re-sending jq event as a backbone event, can we listen directly?\n\t 'change .forward-unpaired-filter input' : function( ev ){ this.trigger( 'filter-change' ); },\n\t 'focus .forward-unpaired-filter input' : function( ev ){ $( ev.currentTarget ).select(); },\n\t 'click .autopair-link' : '_clickAutopair',\n\t 'click .choose-filters .filter-choice' : '_clickFilterChoice',\n\t 'click .clear-filters-link' : '_clearFilters',\n\t 'change .reverse-unpaired-filter input' : function( ev ){ this.trigger( 'filter-change' ); },\n\t 'focus .reverse-unpaired-filter input' : function( ev ){ $( ev.currentTarget ).select(); },\n\t // unpaired\n\t 'click .forward-column .dataset.unpaired' : '_clickUnpairedDataset',\n\t 'click .reverse-column .dataset.unpaired' : '_clickUnpairedDataset',\n\t 'click .paired-column .dataset.unpaired' : '_clickPairRow',\n\t 'click .unpaired-columns' : 'clearSelectedUnpaired',\n\t 'mousedown .unpaired-columns .dataset' : '_mousedownUnpaired',\n\t // divider\n\t 'click .paired-column-title' : '_clickShowOnlyPaired',\n\t 'mousedown .flexible-partition-drag' : '_startPartitionDrag',\n\t // paired\n\t 'click .paired-columns .dataset.paired' : 'selectPair',\n\t 'click .paired-columns' : 'clearSelectedPaired',\n\t 'click .paired-columns .pair-name' : '_clickPairName',\n\t 'click .unpair-btn' : '_clickUnpair',\n\t // paired - drop target\n\t //'dragenter .paired-columns' : '_dragenterPairedColumns',\n\t //'dragleave .paired-columns .column-datasets': '_dragleavePairedColumns',\n\t 'dragover .paired-columns .column-datasets' : '_dragoverPairedColumns',\n\t 'drop .paired-columns .column-datasets' : '_dropPairedColumns',\n\t\n\t 'pair.dragstart .paired-columns .column-datasets' : '_pairDragstart',\n\t 'pair.dragend .paired-columns .column-datasets' : '_pairDragend',\n\t\n\t // footer\n\t 'change .remove-extensions' : function( ev ){ this.toggleExtensions(); },\n\t 'change .collection-name' : '_changeName',\n\t 'keydown .collection-name' : '_nameCheckForEnter',\n\t 'click .cancel-create' : function( ev ){\n\t if( typeof this.oncancel === 'function' ){\n\t this.oncancel.call( this );\n\t }\n\t },\n\t 'click .create-collection' : '_clickCreate'//,\n\t },\n\t\n\t // ........................................................................ header\n\t /** expand help */\n\t _clickMoreHelp : function( ev ){\n\t this.$( '.main-help' ).addClass( 'expanded' );\n\t this.$( '.more-help' ).hide();\n\t },\n\t /** collapse help */\n\t _clickLessHelp : function( ev ){\n\t this.$( '.main-help' ).removeClass( 'expanded' );\n\t this.$( '.more-help' ).show();\n\t },\n\t\n\t /** show an alert on the top of the interface containing message (alertClass is bootstrap's alert-*)*/\n\t _showAlert : function( message, alertClass ){\n\t alertClass = alertClass || 'alert-danger';\n\t this.$( '.main-help' ).hide();\n\t this.$( '.header .alert' ).attr( 'class', 'alert alert-dismissable' ).addClass( alertClass ).show()\n\t .find( '.alert-message' ).html( message );\n\t },\n\t /** hide the alerts at the top */\n\t _hideAlert : function( message ){\n\t this.$( '.main-help' ).show();\n\t this.$( '.header .alert' ).hide();\n\t },\n\t\n\t /** toggle between showing only unpaired and split view */\n\t _clickShowOnlyUnpaired : function( ev ){\n\t //this.debug( 'click unpaired', ev.currentTarget );\n\t if( this.$( '.paired-columns' ).is( ':visible' ) ){\n\t this.hidePaired();\n\t } else {\n\t this.splitView();\n\t }\n\t },\n\t /** toggle between showing only paired and split view */\n\t _clickShowOnlyPaired : function( ev ){\n\t //this.debug( 'click paired' );\n\t if( this.$( '.unpaired-columns' ).is( ':visible' ) ){\n\t this.hideUnpaired();\n\t } else {\n\t this.splitView();\n\t }\n\t },\n\t\n\t /** hide unpaired, show paired */\n\t hideUnpaired : function( speed, callback ){\n\t this.unpairedPanelHidden = true;\n\t this.pairedPanelHidden = false;\n\t this._renderMiddle( speed, callback );\n\t },\n\t /** hide paired, show unpaired */\n\t hidePaired : function( speed, callback ){\n\t this.unpairedPanelHidden = false;\n\t this.pairedPanelHidden = true;\n\t this._renderMiddle( speed, callback );\n\t },\n\t /** show both paired and unpaired (splitting evenly) */\n\t splitView : function( speed, callback ){\n\t this.unpairedPanelHidden = this.pairedPanelHidden = false;\n\t this._renderMiddle( speed, callback );\n\t return this;\n\t },\n\t\n\t /** unpair all paired and do other super neat stuff which I'm not really sure about yet... */\n\t _clickUnpairAll : function( ev ){\n\t this.metric( 'unpairAll' );\n\t this.unpairAll();\n\t },\n\t\n\t /** attempt to autopair */\n\t _clickAutopair : function( ev ){\n\t var paired = this.autoPair();\n\t this.metric( 'autopair', paired.length, this.unpaired.length );\n\t this.trigger( 'autopair' );\n\t },\n\t\n\t /** set the filters based on the data attributes of the button click target */\n\t _clickFilterChoice : function( ev ){\n\t var $selected = $( ev.currentTarget );\n\t this.$( '.forward-unpaired-filter input' ).val( $selected.data( 'forward' ) );\n\t this.$( '.reverse-unpaired-filter input' ).val( $selected.data( 'reverse' ) );\n\t this._hideChooseFilters();\n\t this.trigger( 'filter-change' );\n\t },\n\t\n\t /** hide the choose filters popover */\n\t _hideChooseFilters : function(){\n\t //TODO: update bootstrap and remove the following hack\n\t // see also: https://github.com/twbs/bootstrap/issues/10260\n\t this.$( '.choose-filters-link' ).popover( 'hide' );\n\t this.$( '.popover' ).css( 'display', 'none' );\n\t },\n\t\n\t /** clear both filters */\n\t _clearFilters : function( ev ){\n\t this.$( '.forward-unpaired-filter input' ).val( '' );\n\t this.$( '.reverse-unpaired-filter input' ).val( '' );\n\t this.trigger( 'filter-change' );\n\t },\n\t\n\t // ........................................................................ unpaired\n\t /** select an unpaired dataset */\n\t _clickUnpairedDataset : function( ev ){\n\t ev.stopPropagation();\n\t return this.toggleSelectUnpaired( $( ev.currentTarget ) );\n\t },\n\t\n\t /** Toggle the selection of an unpaired dataset representation.\n\t * @param [jQuery] $dataset the unpaired dataset dom rep to select\n\t * @param [Boolean] options.force if defined, force selection based on T/F; otherwise, toggle\n\t */\n\t toggleSelectUnpaired : function( $dataset, options ){\n\t options = options || {};\n\t var dataset = $dataset.data( 'dataset' ),\n\t select = options.force !== undefined? options.force: !$dataset.hasClass( 'selected' );\n\t //this.debug( id, options.force, $dataset, dataset );\n\t if( !$dataset.length || dataset === undefined ){ return $dataset; }\n\t\n\t if( select ){\n\t $dataset.addClass( 'selected' );\n\t if( !options.waitToPair ){\n\t this.pairAllSelected();\n\t }\n\t\n\t } else {\n\t $dataset.removeClass( 'selected' );\n\t //delete dataset.selected;\n\t }\n\t return $dataset;\n\t },\n\t\n\t /** pair all the currently selected unpaired datasets */\n\t pairAllSelected : function( options ){\n\t options = options || {};\n\t var creator = this,\n\t fwds = [],\n\t revs = [],\n\t pairs = [];\n\t creator.$( '.unpaired-columns .forward-column .dataset.selected' ).each( function(){\n\t fwds.push( $( this ).data( 'dataset' ) );\n\t });\n\t creator.$( '.unpaired-columns .reverse-column .dataset.selected' ).each( function(){\n\t revs.push( $( this ).data( 'dataset' ) );\n\t });\n\t fwds.length = revs.length = Math.min( fwds.length, revs.length );\n\t //this.debug( fwds );\n\t //this.debug( revs );\n\t fwds.forEach( function( fwd, i ){\n\t try {\n\t pairs.push( creator._pair( fwd, revs[i], { silent: true }) );\n\t\n\t } catch( err ){\n\t //TODO: preserve selected state of those that couldn't be paired\n\t //TODO: warn that some could not be paired\n\t creator.error( err );\n\t }\n\t });\n\t if( pairs.length && !options.silent ){\n\t this.trigger( 'pair:new', pairs );\n\t }\n\t return pairs;\n\t },\n\t\n\t /** clear the selection on all unpaired datasets */\n\t clearSelectedUnpaired : function(){\n\t this.$( '.unpaired-columns .dataset.selected' ).removeClass( 'selected' );\n\t },\n\t\n\t /** when holding down the shift key on a click, 'paint' the moused over datasets as selected */\n\t _mousedownUnpaired : function( ev ){\n\t if( ev.shiftKey ){\n\t var creator = this,\n\t $startTarget = $( ev.target ).addClass( 'selected' ),\n\t moveListener = function( ev ){\n\t creator.$( ev.target ).filter( '.dataset' ).addClass( 'selected' );\n\t };\n\t $startTarget.parent().on( 'mousemove', moveListener );\n\t\n\t // on any mouseup, stop listening to the move and try to pair any selected\n\t $( document ).one( 'mouseup', function( ev ){\n\t $startTarget.parent().off( 'mousemove', moveListener );\n\t creator.pairAllSelected();\n\t });\n\t }\n\t },\n\t\n\t /** attempt to pair two datasets directly across from one another */\n\t _clickPairRow : function( ev ){\n\t //if( !ev.currentTarget ){ return true; }\n\t var rowIndex = $( ev.currentTarget ).index(),\n\t fwd = $( '.unpaired-columns .forward-column .dataset' ).eq( rowIndex ).data( 'dataset' ),\n\t rev = $( '.unpaired-columns .reverse-column .dataset' ).eq( rowIndex ).data( 'dataset' );\n\t //this.debug( 'row:', rowIndex, fwd, rev );\n\t this._pair( fwd, rev );\n\t },\n\t\n\t // ........................................................................ divider/partition\n\t /** start dragging the visible divider/partition between unpaired and paired panes */\n\t _startPartitionDrag : function( ev ){\n\t var creator = this,\n\t startingY = ev.pageY;\n\t //this.debug( 'partition drag START:', ev );\n\t $( 'body' ).css( 'cursor', 'ns-resize' );\n\t creator.$( '.flexible-partition-drag' ).css( 'color', 'black' );\n\t\n\t function endDrag( ev ){\n\t //creator.debug( 'partition drag STOP:', ev );\n\t // doing this by an added class didn't really work well - kept flashing still\n\t creator.$( '.flexible-partition-drag' ).css( 'color', '' );\n\t $( 'body' ).css( 'cursor', '' ).unbind( 'mousemove', trackMouse );\n\t }\n\t function trackMouse( ev ){\n\t var offset = ev.pageY - startingY;\n\t //creator.debug( 'partition:', startingY, offset );\n\t if( !creator.adjPartition( offset ) ){\n\t //creator.debug( 'mouseup triggered' );\n\t $( 'body' ).trigger( 'mouseup' );\n\t }\n\t creator._adjUnpairedOnScrollbar();\n\t startingY += offset;\n\t }\n\t $( 'body' ).mousemove( trackMouse );\n\t $( 'body' ).one( 'mouseup', endDrag );\n\t },\n\t\n\t /** adjust the parition up/down +/-adj pixels */\n\t adjPartition : function( adj ){\n\t var $unpaired = this.$( '.unpaired-columns' ),\n\t $paired = this.$( '.paired-columns' ),\n\t unpairedHi = parseInt( $unpaired.css( 'height' ), 10 ),\n\t pairedHi = parseInt( $paired.css( 'height' ), 10 );\n\t //this.debug( adj, 'hi\\'s:', unpairedHi, pairedHi, unpairedHi + adj, pairedHi - adj );\n\t\n\t unpairedHi = Math.max( 10, unpairedHi + adj );\n\t pairedHi = pairedHi - adj;\n\t\n\t var movingUpwards = adj < 0;\n\t // when the divider gets close to the top - lock into hiding the unpaired section\n\t if( movingUpwards ){\n\t if( this.unpairedPanelHidden ){\n\t return false;\n\t } else if( unpairedHi <= 10 ){\n\t this.hideUnpaired();\n\t return false;\n\t }\n\t } else {\n\t if( this.unpairedPanelHidden ){\n\t $unpaired.show();\n\t this.unpairedPanelHidden = false;\n\t }\n\t }\n\t\n\t // when the divider gets close to the bottom - lock into hiding the paired section\n\t if( !movingUpwards ){\n\t if( this.pairedPanelHidden ){\n\t return false;\n\t } else if( pairedHi <= 15 ){\n\t this.hidePaired();\n\t return false;\n\t }\n\t\n\t } else {\n\t if( this.pairedPanelHidden ){\n\t $paired.show();\n\t this.pairedPanelHidden = false;\n\t }\n\t }\n\t\n\t $unpaired.css({\n\t height : unpairedHi + 'px',\n\t flex : '0 0 auto'\n\t });\n\t return true;\n\t },\n\t\n\t // ........................................................................ paired\n\t /** select a pair when clicked */\n\t selectPair : function( ev ){\n\t ev.stopPropagation();\n\t $( ev.currentTarget ).toggleClass( 'selected' );\n\t },\n\t\n\t /** deselect all pairs */\n\t clearSelectedPaired : function( ev ){\n\t this.$( '.paired-columns .dataset.selected' ).removeClass( 'selected' );\n\t },\n\t\n\t /** rename a pair when the pair name is clicked */\n\t _clickPairName : function( ev ){\n\t ev.stopPropagation();\n\t var $name = $( ev.currentTarget ),\n\t $pair = $name.parent().parent(),\n\t index = $pair.index( '.dataset.paired' ),\n\t pair = this.paired[ index ],\n\t response = prompt( 'Enter a new name for the pair:', pair.name );\n\t if( response ){\n\t pair.name = response;\n\t // set a flag (which won't be passed in json creation) for manual naming so we don't overwrite these\n\t // when adding/removing extensions\n\t //hackish\n\t pair.customizedName = true;\n\t $name.text( pair.name );\n\t }\n\t },\n\t\n\t /** unpair this pair */\n\t _clickUnpair : function( ev ){\n\t //if( !ev.currentTarget ){ return true; }\n\t var pairIndex = Math.floor( $( ev.currentTarget ).index( '.unpair-btn' ) );\n\t //this.debug( 'pair:', pairIndex );\n\t this._unpair( this.paired[ pairIndex ] );\n\t },\n\t\n\t // ........................................................................ paired - drag and drop re-ordering\n\t //_dragenterPairedColumns : function( ev ){\n\t // this.debug( '_dragenterPairedColumns:', ev );\n\t //},\n\t //_dragleavePairedColumns : function( ev ){\n\t // //this.debug( '_dragleavePairedColumns:', ev );\n\t //},\n\t /** track the mouse drag over the paired list adding a placeholder to show where the drop would occur */\n\t _dragoverPairedColumns : function( ev ){\n\t //this.debug( '_dragoverPairedColumns:', ev );\n\t ev.preventDefault();\n\t\n\t var $list = this.$( '.paired-columns .column-datasets' );\n\t this._checkForAutoscroll( $list, ev.originalEvent.clientY );\n\t //this.debug( ev.originalEvent.clientX, ev.originalEvent.clientY );\n\t var $nearest = this._getNearestPairedDatasetLi( ev.originalEvent.clientY );\n\t\n\t $( '.element-drop-placeholder' ).remove();\n\t var $placeholder = $( '
    ' );\n\t if( !$nearest.length ){\n\t $list.append( $placeholder );\n\t } else {\n\t $nearest.before( $placeholder );\n\t }\n\t },\n\t\n\t /** If the mouse is near enough to the list's top or bottom, scroll the list */\n\t _checkForAutoscroll : function( $element, y ){\n\t var AUTOSCROLL_SPEED = 2;\n\t var offset = $element.offset(),\n\t scrollTop = $element.scrollTop(),\n\t upperDist = y - offset.top,\n\t lowerDist = ( offset.top + $element.outerHeight() ) - y;\n\t //this.debug( '_checkForAutoscroll:', scrollTop, upperDist, lowerDist );\n\t if( upperDist >= 0 && upperDist < this.autoscrollDist ){\n\t $element.scrollTop( scrollTop - AUTOSCROLL_SPEED );\n\t } else if( lowerDist >= 0 && lowerDist < this.autoscrollDist ){\n\t $element.scrollTop( scrollTop + AUTOSCROLL_SPEED );\n\t }\n\t },\n\t\n\t /** get the nearest *previous* paired dataset PairView based on the mouse's Y coordinate.\n\t * If the y is at the end of the list, return an empty jQuery object.\n\t */\n\t _getNearestPairedDatasetLi : function( y ){\n\t var WIGGLE = 4,\n\t lis = this.$( '.paired-columns .column-datasets li' ).toArray();\n\t for( var i=0; i y && top - halfHeight < y ){\n\t //this.debug( y, top + halfHeight, top - halfHeight )\n\t return $li;\n\t }\n\t }\n\t return $();\n\t },\n\t /** drop (dragged/selected PairViews) onto the list, re-ordering both the DOM and the internal array of pairs */\n\t _dropPairedColumns : function( ev ){\n\t // both required for firefox\n\t ev.preventDefault();\n\t ev.dataTransfer.dropEffect = 'move';\n\t\n\t var $nearest = this._getNearestPairedDatasetLi( ev.originalEvent.clientY );\n\t if( $nearest.length ){\n\t this.$dragging.insertBefore( $nearest );\n\t\n\t } else {\n\t // no nearest before - insert after last element (unpair button)\n\t this.$dragging.insertAfter( this.$( '.paired-columns .unpair-btn' ).last() );\n\t }\n\t // resync the creator's list of paired based on the new DOM order\n\t this._syncPairsToDom();\n\t return false;\n\t },\n\t /** resync the creator's list of paired based on the DOM order of pairs */\n\t _syncPairsToDom : function(){\n\t var newPaired = [];\n\t //TODO: doesn't seem wise to use the dom to store these - can't we sync another way?\n\t this.$( '.paired-columns .dataset.paired' ).each( function(){\n\t newPaired.push( $( this ).data( 'pair' ) );\n\t });\n\t //this.debug( newPaired );\n\t this.paired = newPaired;\n\t this._renderPaired();\n\t },\n\t /** drag communication with pair sub-views: dragstart */\n\t _pairDragstart : function( ev, pair ){\n\t //this.debug( '_pairDragstart', ev, pair )\n\t // auto select the pair causing the event and move all selected\n\t pair.$el.addClass( 'selected' );\n\t var $selected = this.$( '.paired-columns .dataset.selected' );\n\t this.$dragging = $selected;\n\t },\n\t /** drag communication with pair sub-views: dragend - remove the placeholder */\n\t _pairDragend : function( ev, pair ){\n\t //this.debug( '_pairDragend', ev, pair )\n\t $( '.element-drop-placeholder' ).remove();\n\t this.$dragging = null;\n\t },\n\t\n\t // ........................................................................ footer\n\t toggleExtensions : function( force ){\n\t var creator = this;\n\t creator.removeExtensions = ( force !== undefined )?( force ):( !creator.removeExtensions );\n\t\n\t _.each( creator.paired, function( pair ){\n\t // don't overwrite custom names\n\t if( pair.customizedName ){ return; }\n\t pair.name = creator._guessNameForPair( pair.forward, pair.reverse );\n\t });\n\t\n\t creator._renderPaired();\n\t creator._renderFooter();\n\t },\n\t\n\t /** handle a collection name change */\n\t _changeName : function( ev ){\n\t this._validationWarning( 'name', !!this._getName() );\n\t },\n\t\n\t /** check for enter key press when in the collection name and submit */\n\t _nameCheckForEnter : function( ev ){\n\t if( ev.keyCode === 13 && !this.blocking ){\n\t this._clickCreate();\n\t }\n\t },\n\t\n\t /** get the current collection name */\n\t _getName : function(){\n\t return _.escape( this.$( '.collection-name' ).val() );\n\t },\n\t\n\t /** attempt to create the current collection */\n\t _clickCreate : function( ev ){\n\t var name = this._getName();\n\t if( !name ){\n\t this._validationWarning( 'name' );\n\t } else if( !this.blocking ){\n\t this.createList();\n\t }\n\t },\n\t\n\t // ------------------------------------------------------------------------ misc\n\t /** debug a dataset list */\n\t _printList : function( list ){\n\t var creator = this;\n\t _.each( list, function( e ){\n\t if( list === creator.paired ){\n\t creator._printPair( e );\n\t } else {\n\t //creator.debug( e );\n\t }\n\t });\n\t },\n\t\n\t /** print a pair Object */\n\t _printPair : function( pair ){\n\t this.debug( pair.forward.name, pair.reverse.name, ': ->', pair.name );\n\t },\n\t\n\t /** string rep */\n\t toString : function(){ return 'PairedCollectionCreator'; }\n\t});\n\t\n\t\n\t//TODO: move to require text plugin and load these as text\n\t//TODO: underscore currently unnecc. bc no vars are used\n\t//TODO: better way of localizing text-nodes in long strings\n\t/** underscore template fns attached to class */\n\tPairedCollectionCreator.templates = PairedCollectionCreator.templates || {\n\t\n\t /** the skeleton */\n\t main : _.template([\n\t '
    ',\n\t '
    ',\n\t '
    '\n\t ].join('')),\n\t\n\t /** the header (not including help text) */\n\t header : _.template([\n\t '
    ',\n\t '', _l( 'More help' ), '',\n\t '
    ',\n\t '', _l( 'Less' ), '',\n\t '
    ',\n\t '
    ',\n\t '
    ',\n\t '',\n\t '',\n\t '
    ',\n\t\n\t '
    ',\n\t '
    ',\n\t '
    ',\n\t '
    ',\n\t '', _l( 'Unpaired forward' ), '',\n\t '',\n\t '
    ',\n\t '
    ',\n\t '',\n\t '
    ',\n\t '
    ',\n\t '
    ',\n\t '',\n\t '
    ',\n\t '
    ',\n\t '
    ',\n\t '', _l( 'Unpaired reverse' ), '',\n\t '',\n\t '
    ',\n\t '
    ',\n\t '',\n\t '
    ',\n\t '
    ',\n\t '
    ',\n\t '
    '\n\t ].join('')),\n\t\n\t /** the middle: unpaired, divider, and paired */\n\t middle : _.template([\n\t // contains two flex rows (rows that fill available space) and a divider btwn\n\t '
    ',\n\t '
    ',\n\t '
      ',\n\t '
      ',\n\t '
      ',\n\t '
        ',\n\t '
        ',\n\t '
        ',\n\t '
          ',\n\t '
          ',\n\t '
          ',\n\t '
          ',\n\t '
          ',\n\t '
          ',\n\t '
          ',\n\t '',\n\t '
          ',\n\t '',\n\t _l( 'Unpair all' ),\n\t '',\n\t '
          ',\n\t '
          ',\n\t '
          ',\n\t '
            ',\n\t '
            '\n\t ].join('')),\n\t\n\t /** creation and cancel controls */\n\t footer : _.template([\n\t '
            ',\n\t '
            ',\n\t '',\n\t '
            ',\n\t '
            ',\n\t '',\n\t '
            ', _l( 'Name' ), ':
            ',\n\t '
            ',\n\t '
            ',\n\t\n\t '
            ',\n\t '
            ',\n\t '',\n\t '
            ',\n\t '',\n\t '',\n\t '
            ',\n\t '
            ',\n\t\n\t '
            ',\n\t '',\n\t '
            ',\n\t '
            '\n\t ].join('')),\n\t\n\t /** help content */\n\t helpContent : _.template([\n\t '

            ', _l([\n\t 'Collections of paired datasets are ordered lists of dataset pairs (often forward and reverse reads). ',\n\t 'These collections can be passed to tools and workflows in order to have analyses done on each member of ',\n\t 'the entire group. This interface allows you to create a collection, choose which datasets are paired, ',\n\t 'and re-order the final collection.'\n\t ].join( '' )), '

            ',\n\t '

            ', _l([\n\t 'Unpaired datasets are shown in the unpaired section ',\n\t '(hover over the underlined words to highlight below). ',\n\t 'Paired datasets are shown in the paired section.',\n\t '

              To pair datasets, you can:',\n\t '
            • Click a dataset in the ',\n\t 'forward column ',\n\t 'to select it then click a dataset in the ',\n\t 'reverse column.',\n\t '
            • ',\n\t '
            • Click one of the \"Pair these datasets\" buttons in the ',\n\t 'middle column ',\n\t 'to pair the datasets in a particular row.',\n\t '
            • ',\n\t '
            • Click \"Auto-pair\" ',\n\t 'to have your datasets automatically paired based on name.',\n\t '
            • ',\n\t '
            '\n\t ].join( '' )), '

            ',\n\t '

            ', _l([\n\t '

              You can filter what is shown in the unpaired sections by:',\n\t '
            • Entering partial dataset names in either the ',\n\t 'forward filter or ',\n\t 'reverse filter.',\n\t '
            • ',\n\t '
            • Choosing from a list of preset filters by clicking the ',\n\t '\"Choose filters\" link.',\n\t '
            • ',\n\t '
            • Entering regular expressions to match dataset names. See: ',\n\t 'MDN\\'s JavaScript Regular Expression Tutorial. ',\n\t 'Note: forward slashes (\\\\) are not needed.',\n\t '
            • ',\n\t '
            • Clearing the filters by clicking the ',\n\t '\"Clear filters\" link.',\n\t '
            • ',\n\t '
            '\n\t ].join( '' )), '

            ',\n\t '

            ', _l([\n\t 'To unpair individual dataset pairs, click the ',\n\t 'unpair buttons ( ). ',\n\t 'Click the \"Unpair all\" link to unpair all pairs.'\n\t ].join( '' )), '

            ',\n\t '

            ', _l([\n\t 'You can include or remove the file extensions (e.g. \".fastq\") from your pair names by toggling the ',\n\t '\"Remove file extensions from pair names?\" control.'\n\t ].join( '' )), '

            ',\n\t '

            ', _l([\n\t 'Once your collection is complete, enter a name and ',\n\t 'click \"Create list\". ',\n\t '(Note: you do not have to pair all unpaired datasets to finish.)'\n\t ].join( '' )), '

            '\n\t ].join(''))\n\t};\n\t\n\t\n\t//=============================================================================\n\t/** a modal version of the paired collection creator */\n\tvar pairedCollectionCreatorModal = function _pairedCollectionCreatorModal( datasets, options ){\n\t\n\t var deferred = jQuery.Deferred(),\n\t creator;\n\t\n\t options = _.defaults( options || {}, {\n\t datasets : datasets,\n\t oncancel : function(){\n\t Galaxy.modal.hide();\n\t deferred.reject( 'cancelled' );\n\t },\n\t oncreate : function( creator, response ){\n\t Galaxy.modal.hide();\n\t deferred.resolve( response );\n\t }\n\t });\n\t\n\t if( !window.Galaxy || !Galaxy.modal ){\n\t throw new Error( 'Galaxy or Galaxy.modal not found' );\n\t }\n\t\n\t creator = new PairedCollectionCreator( options );\n\t Galaxy.modal.show({\n\t title : 'Create a collection of paired datasets',\n\t body : creator.$el,\n\t width : '80%',\n\t height : '800px',\n\t closing_events: true\n\t });\n\t creator.render();\n\t window.creator = creator;\n\t\n\t //TODO: remove modal header\n\t return deferred;\n\t};\n\t\n\t\n\t//=============================================================================\n\tfunction createListOfPairsCollection( collection ){\n\t var elements = collection.toJSON();\n\t//TODO: validate elements\n\t return pairedCollectionCreatorModal( elements, {\n\t historyId : collection.historyId\n\t });\n\t}\n\t\n\t\n\t//=============================================================================\n\t return {\n\t PairedCollectionCreator : PairedCollectionCreator,\n\t pairedCollectionCreatorModal : pairedCollectionCreatorModal,\n\t createListOfPairsCollection : createListOfPairsCollection\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(2), __webpack_require__(1), __webpack_require__(1)))\n\n/***/ },\n/* 109 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, _, jQuery) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(31),\n\t __webpack_require__(39),\n\t __webpack_require__(6),\n\t __webpack_require__(5)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( LIST_CREATOR, HDCA, BASE_MVC, _l ){\n\t\n\t'use strict';\n\t\n\tvar logNamespace = 'collections';\n\t/*==============================================================================\n\tTODO:\n\t the paired creator doesn't really mesh with the list creator as parent\n\t it may be better to make an abstract super class for both\n\t composites may inherit from this (or vis-versa)\n\t PairedDatasetCollectionElementView doesn't make a lot of sense\n\t\n\t==============================================================================*/\n\t/** */\n\tvar PairedDatasetCollectionElementView = Backbone.View.extend( BASE_MVC.LoggableMixin ).extend({\n\t _logNamespace : logNamespace,\n\t\n\t//TODO: use proper class (DatasetDCE or NestedDCDCE (or the union of both))\n\t tagName : 'li',\n\t className : 'collection-element',\n\t\n\t initialize : function( attributes ){\n\t this.element = attributes.element || {};\n\t this.identifier = attributes.identifier;\n\t },\n\t\n\t render : function(){\n\t this.$el\n\t .attr( 'data-element-id', this.element.id )\n\t .html( this.template({ identifier: this.identifier, element: this.element }) );\n\t return this;\n\t },\n\t\n\t //TODO: lots of unused space in the element - possibly load details and display them horiz.\n\t template : _.template([\n\t '<%- identifier %>',\n\t '<%- element.name %>',\n\t ].join('')),\n\t\n\t /** remove the DOM and any listeners */\n\t destroy : function(){\n\t this.off();\n\t this.$el.remove();\n\t },\n\t\n\t /** string rep */\n\t toString : function(){\n\t return 'DatasetCollectionElementView()';\n\t }\n\t});\n\t\n\t\n\t// ============================================================================\n\tvar _super = LIST_CREATOR.ListCollectionCreator;\n\t\n\t/** An interface for building collections.\n\t */\n\tvar PairCollectionCreator = _super.extend({\n\t\n\t /** the class used to display individual elements */\n\t elementViewClass : PairedDatasetCollectionElementView,\n\t /** the class this creator will create and save */\n\t collectionClass : HDCA.HistoryPairDatasetCollection,\n\t className : 'pair-collection-creator collection-creator flex-row-container',\n\t\n\t /** override to no-op */\n\t _mangleDuplicateNames : function(){},\n\t\n\t // TODO: this whole pattern sucks. There needs to be two classes of problem area:\n\t // bad inital choices and\n\t // when the user has painted his/her self into a corner during creation/use-of-the-creator\n\t /** render the entire interface */\n\t render : function( speed, callback ){\n\t if( this.workingElements.length === 2 ){\n\t return _super.prototype.render.call( this, speed, callback );\n\t }\n\t return this._renderInvalid( speed, callback );\n\t },\n\t\n\t // ------------------------------------------------------------------------ rendering elements\n\t /** render forward/reverse */\n\t _renderList : function( speed, callback ){\n\t //this.debug( '-- _renderList' );\n\t //precondition: there are two valid elements in workingElements\n\t var creator = this,\n\t $tmp = jQuery( '
            ' ),\n\t $list = creator.$list();\n\t\n\t // lose the original views, create the new, append all at once, then call their renders\n\t _.each( this.elementViews, function( view ){\n\t view.destroy();\n\t creator.removeElementView( view );\n\t });\n\t $tmp.append( creator._createForwardElementView().$el );\n\t $tmp.append( creator._createReverseElementView().$el );\n\t $list.empty().append( $tmp.children() );\n\t _.invoke( creator.elementViews, 'render' );\n\t },\n\t\n\t /** create the forward element view */\n\t _createForwardElementView : function(){\n\t return this._createElementView( this.workingElements[0], { identifier: 'forward' } );\n\t },\n\t\n\t /** create the forward element view */\n\t _createReverseElementView : function(){\n\t return this._createElementView( this.workingElements[1], { identifier: 'reverse' } );\n\t },\n\t\n\t /** create an element view, cache in elementViews, and return */\n\t _createElementView : function( element, options ){\n\t var elementView = new this.elementViewClass( _.extend( options, {\n\t element : element,\n\t }));\n\t this.elementViews.push( elementView );\n\t return elementView;\n\t },\n\t\n\t /** swap the forward, reverse elements and re-render */\n\t swap : function(){\n\t this.workingElements = [\n\t this.workingElements[1],\n\t this.workingElements[0],\n\t ];\n\t this._renderList();\n\t },\n\t\n\t events : _.extend( _.clone( _super.prototype.events ), {\n\t 'click .swap' : 'swap',\n\t }),\n\t\n\t // ------------------------------------------------------------------------ templates\n\t //TODO: move to require text plugin and load these as text\n\t //TODO: underscore currently unnecc. bc no vars are used\n\t //TODO: better way of localizing text-nodes in long strings\n\t /** underscore template fns attached to class */\n\t templates : _.extend( _.clone( _super.prototype.templates ), {\n\t /** the middle: element list */\n\t middle : _.template([\n\t '',\n\t '
            ',\n\t '
            '\n\t ].join('')),\n\t\n\t /** help content */\n\t helpContent : _.template([\n\t '

            ', _l([\n\t 'Pair collections are permanent collections containing two datasets: one forward and one reverse. ',\n\t 'Often these are forward and reverse reads. The pair collections can be passed to tools and ',\n\t 'workflows in order to have analyses done on both datasets. This interface allows ',\n\t 'you to create a pair, name it, and swap which is forward and which reverse.'\n\t ].join( '' )), '

            ',\n\t '
              ',\n\t '
            • ', _l([\n\t 'Click the \"Swap\" link to make your forward dataset the reverse ',\n\t 'and the reverse dataset forward.'\n\t ].join( '' )), '
            • ',\n\t '
            • ', _l([\n\t 'Click the \"Cancel\" button to exit the interface.'\n\t ].join( '' )), '
            • ',\n\t '

            ',\n\t '

            ', _l([\n\t 'Once your collection is complete, enter a name and ',\n\t 'click \"Create list\".'\n\t ].join( '' )), '

            '\n\t ].join('')),\n\t\n\t /** a simplified page communicating what went wrong and why the user needs to reselect something else */\n\t invalidInitial : _.template([\n\t '
            ',\n\t '
            ',\n\t '',\n\t '<% if( _.size( problems ) ){ %>',\n\t _l( 'The following selections could not be included due to problems' ),\n\t '
              <% _.each( problems, function( problem ){ %>',\n\t '
            • <%- problem.element.name %>: <%- problem.text %>
            • ',\n\t '<% }); %>
            ',\n\t '<% } else if( _.size( elements ) === 0 ){ %>',\n\t _l( 'No datasets were selected' ), '.',\n\t '<% } else if( _.size( elements ) === 1 ){ %>',\n\t _l( 'Only one dataset was selected' ), ': <%- elements[0].name %>',\n\t '<% } else if( _.size( elements ) > 2 ){ %>',\n\t _l( 'Too many datasets were selected' ),\n\t ': <%- _.pluck( elements, \"name\" ).join( \", \") %>',\n\t '<% } %>',\n\t '
            ',\n\t _l( 'Two (and only two) elements are needed for the pair' ), '. ',\n\t _l( 'You may need to ' ),\n\t '', _l( 'cancel' ), ' ',\n\t _l( 'and reselect new elements' ), '.',\n\t '
            ',\n\t '
            ',\n\t '
            ',\n\t '
            ',\n\t '
            ',\n\t '
            ',\n\t '',\n\t // _l( 'Create a different kind of collection' ),\n\t '
            ',\n\t '
            ',\n\t '
            '\n\t ].join('')),\n\t }),\n\t\n\t // ------------------------------------------------------------------------ misc\n\t /** string rep */\n\t toString : function(){ return 'PairCollectionCreator'; }\n\t});\n\t\n\t\n\t//==============================================================================\n\t/** List collection flavor of collectionCreatorModal. */\n\tvar pairCollectionCreatorModal = function _pairCollectionCreatorModal( elements, options ){\n\t options = options || {};\n\t options.title = _l( 'Create a collection from a pair of datasets' );\n\t return LIST_CREATOR.collectionCreatorModal( elements, options, PairCollectionCreator );\n\t};\n\t\n\t\n\t//==============================================================================\n\t/** Use a modal to create a pair collection, then add it to the given history contents.\n\t * @returns {Deferred} resolved when the collection is added to the history.\n\t */\n\tfunction createPairCollection( contents ){\n\t var elements = contents.toJSON(),\n\t promise = pairCollectionCreatorModal( elements, {\n\t creationFn : function( elements, name ){\n\t elements = [\n\t { name: \"forward\", src: \"hda\", id: elements[0].id },\n\t { name: \"reverse\", src: \"hda\", id: elements[1].id }\n\t ];\n\t return contents.createHDCA( elements, 'paired', name );\n\t }\n\t });\n\t return promise;\n\t}\n\t\n\t//==============================================================================\n\t return {\n\t PairCollectionCreator : PairCollectionCreator,\n\t pairCollectionCreatorModal : pairCollectionCreatorModal,\n\t createPairCollection : createPairCollection,\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(2), __webpack_require__(1)))\n\n/***/ },\n/* 110 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(_, jQuery, $) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(8),\n\t __webpack_require__(78),\n\t __webpack_require__(5)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( MODAL, ERROR_MODAL, _l ){\n\t\n\t'use strict';\n\t\n\t//==============================================================================\n\t/**\n\t * A dialog/modal that allows copying a user history or 'importing' from user\n\t * another. Generally called via historyCopyDialog below.\n\t * @type {Object}\n\t */\n\tvar CopyDialog = {\n\t\n\t // language related strings/fns\n\t defaultName : _.template( \"Copy of '<%- name %>'\" ),\n\t title : _.template( _l( 'Copying history' ) + ' \"<%- name %>\"' ),\n\t submitLabel : _l( 'Copy' ),\n\t errorMessage : _l( 'History could not be copied.' ),\n\t progressive : _l( 'Copying history' ),\n\t activeLabel : _l( 'Copy only the active, non-deleted datasets' ),\n\t allLabel : _l( 'Copy all datasets including deleted ones' ),\n\t anonWarning : _l( 'As an anonymous user, unless you login or register, you will lose your current history ' ) +\n\t _l( 'after copying this history. ' ),\n\t\n\t // template for modal body\n\t _template : _.template([\n\t //TODO: remove inline styles\n\t // show a warning message for losing current to anon users\n\t '<% if( isAnon ){ %>',\n\t '
            ',\n\t '<%- anonWarning %>',\n\t _l( 'You can' ),\n\t ' ', _l( 'login here' ), ' ', _l( 'or' ), ' ',\n\t ' ', _l( 'register here' ), '.',\n\t '
            ',\n\t '<% } %>',\n\t '
            ',\n\t '
            ',\n\t // TODO: could use required here and the form validators\n\t // NOTE: use unescaped here if escaped in the modal function below\n\t '\" />',\n\t '

            ',\n\t _l( 'Please enter a valid history title' ),\n\t '

            ',\n\t // if allowAll, add the option to copy deleted datasets, too\n\t '<% if( allowAll ){ %>',\n\t '
            ',\n\t '

            ', _l( 'Choose which datasets from the original history to include:' ), '

            ',\n\t // copy non-deleted is the default\n\t '/>',\n\t '',\n\t '
            ',\n\t '/>',\n\t '',\n\t '<% } %>',\n\t '
            '\n\t ].join( '' )),\n\t\n\t // empty modal body and let the user know the copy is happening\n\t _showAjaxIndicator : function _showAjaxIndicator(){\n\t var indicator = '

            ' + this.progressive + '...

            ';\n\t this.modal.$( '.modal-body' ).empty().append( indicator ).css({ 'margin-top': '8px' });\n\t },\n\t\n\t // (sorta) public interface - display the modal, render the form, and potentially copy the history\n\t // returns a jQuery.Deferred done->history copied, fail->user cancelled\n\t dialog : function _dialog( modal, history, options ){\n\t options = options || {};\n\t\n\t var dialog = this,\n\t deferred = jQuery.Deferred(),\n\t // TODO: getting a little byzantine here\n\t defaultCopyNameFn = options.nameFn || this.defaultName,\n\t defaultCopyName = defaultCopyNameFn({ name: history.get( 'name' ) }),\n\t // TODO: these two might be simpler as one 3 state option (all,active,no-choice)\n\t defaultCopyWhat = options.allDatasets? 'copy-all' : 'copy-non-deleted',\n\t allowAll = !_.isUndefined( options.allowAll )? options.allowAll : true,\n\t autoClose = !_.isUndefined( options.autoClose )? options.autoClose : true;\n\t\n\t this.modal = modal;\n\t\n\t\n\t // validate the name and copy if good\n\t function checkNameAndCopy(){\n\t var name = modal.$( '#copy-modal-title' ).val();\n\t if( !name ){\n\t modal.$( '.invalid-title' ).show();\n\t return;\n\t }\n\t // get further settings, shut down and indicate the ajax call, then hide and resolve/reject\n\t var copyAllDatasets = modal.$( 'input[name=\"copy-what\"]:checked' ).val() === 'copy-all';\n\t modal.$( 'button' ).prop( 'disabled', true );\n\t dialog._showAjaxIndicator();\n\t history.copy( true, name, copyAllDatasets )\n\t .done( function( response ){\n\t deferred.resolve( response );\n\t })\n\t .fail( function( xhr, status, message ){\n\t var options = { name: name, copyAllDatasets: copyAllDatasets };\n\t ERROR_MODAL.ajaxErrorModal( history, xhr, options, dialog.errorMessage );\n\t deferred.rejectWith( deferred, arguments );\n\t })\n\t .done( function(){\n\t if( autoClose ){ modal.hide(); }\n\t });\n\t }\n\t\n\t var originalClosingCallback = options.closing_callback;\n\t modal.show( _.extend( options, {\n\t title : this.title({ name: history.get( 'name' ) }),\n\t body : $( dialog._template({\n\t name : defaultCopyName,\n\t isAnon : Galaxy.user.isAnonymous(),\n\t allowAll : allowAll,\n\t copyWhat : defaultCopyWhat,\n\t activeLabel : this.activeLabel,\n\t allLabel : this.allLabel,\n\t anonWarning : this.anonWarning,\n\t })),\n\t buttons : _.object([\n\t [ _l( 'Cancel' ), function(){ modal.hide(); } ],\n\t [ this.submitLabel, checkNameAndCopy ]\n\t ]),\n\t height : 'auto',\n\t closing_events : true,\n\t closing_callback: function _historyCopyClose( cancelled ){\n\t if( cancelled ){\n\t deferred.reject({ cancelled : true });\n\t }\n\t if( originalClosingCallback ){\n\t originalClosingCallback( cancelled );\n\t }\n\t }\n\t }));\n\t\n\t // set the default dataset copy, autofocus the title, and set up for a simple return\n\t modal.$( '#copy-modal-title' ).focus().select();\n\t modal.$( '#copy-modal-title' ).on( 'keydown', function( ev ){\n\t if( ev.keyCode === 13 ){\n\t ev.preventDefault();\n\t checkNameAndCopy();\n\t }\n\t });\n\t\n\t return deferred;\n\t },\n\t};\n\t\n\t//==============================================================================\n\t// maintain the (slight) distinction between copy and import\n\t/**\n\t * Subclass CopyDialog to use the import language.\n\t */\n\tvar ImportDialog = _.extend( {}, CopyDialog, {\n\t defaultName : _.template( \"imported: <%- name %>\" ),\n\t title : _.template( _l( 'Importing history' ) + ' \"<%- name %>\"' ),\n\t submitLabel : _l( 'Import' ),\n\t errorMessage : _l( 'History could not be imported.' ),\n\t progressive : _l( 'Importing history' ),\n\t activeLabel : _l( 'Import only the active, non-deleted datasets' ),\n\t allLabel : _l( 'Import all datasets including deleted ones' ),\n\t anonWarning : _l( 'As an anonymous user, unless you login or register, you will lose your current history ' ) +\n\t _l( 'after importing this history. ' ),\n\t\n\t});\n\t\n\t//==============================================================================\n\t/**\n\t * Main interface for both history import and history copy dialogs.\n\t * @param {Backbone.Model} history the history to copy\n\t * @param {Object} options a hash\n\t * @return {jQuery.Deferred} promise that fails on close and succeeds on copy\n\t *\n\t * options:\n\t * (this object is also passed to the modal used to display the dialog and accepts modal options)\n\t * {Function} nameFn if defined, use this to build the default name shown to the user\n\t * (the fn is passed: {name: })\n\t * {bool} useImport if true, use the 'import' language (instead of Copy)\n\t * {bool} allowAll if true, allow the user to choose between copying all datasets and\n\t * only non-deleted datasets\n\t * {String} allDatasets default initial checked radio button: 'copy-all' or 'copy-non-deleted',\n\t */\n\tvar historyCopyDialog = function( history, options ){\n\t options = options || {};\n\t // create our own modal if Galaxy doesn't have one (mako tab without use_panels)\n\t var modal = window.parent.Galaxy.modal || new MODAL.View({});\n\t return options.useImport?\n\t ImportDialog.dialog( modal, history, options ):\n\t CopyDialog.dialog( modal, history, options );\n\t};\n\t\n\t\n\t//==============================================================================\n\t return historyCopyDialog;\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(2), __webpack_require__(1), __webpack_require__(1)))\n\n/***/ },\n/* 111 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(jQuery, _) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(69),\n\t __webpack_require__(71),\n\t __webpack_require__(6),\n\t __webpack_require__(5)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( DATASET_LI_EDIT, HDA_LI, BASE_MVC, _l ){\n\t\n\t'use strict';\n\t\n\t//==============================================================================\n\tvar _super = DATASET_LI_EDIT.DatasetListItemEdit;\n\t/** @class Editing view for HistoryDatasetAssociation.\n\t */\n\tvar HDAListItemEdit = _super.extend(\n\t/** @lends HDAListItemEdit.prototype */{\n\t\n\t className : _super.prototype.className + \" history-content\",\n\t\n\t /** In this override, only get details if in the ready state, get rerunnable if in other states.\n\t * Note: fetch with no 'change' event triggering to prevent automatic rendering.\n\t */\n\t _fetchModelDetails : function(){\n\t var view = this;\n\t if( view.model.inReadyState() && !view.model.hasDetails() ){\n\t return view.model.fetch({ silent: true });\n\t\n\t // special case the need for the rerunnable and creating_job attributes\n\t // needed for rendering re-run button on queued, running datasets\n\t } else if( !view.model.has( 'rerunnable' ) ){\n\t return view.model.fetch({ silent: true, data: {\n\t // only fetch rerunnable and creating_job to keep overhead down\n\t keys: [ 'rerunnable', 'creating_job' ].join(',')\n\t }});\n\t }\n\t return jQuery.when();\n\t },\n\t\n\t /** event map */\n\t events : _.extend( _.clone( _super.prototype.events ), {\n\t 'click .unhide-link' : function( ev ){ this.model.unhide(); return false; }\n\t }),\n\t\n\t /** string rep */\n\t toString : function(){\n\t var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n\t return 'HDAListItemEdit(' + modelString + ')';\n\t }\n\t});\n\t\n\t\n\t// ............................................................................ TEMPLATES\n\t/** underscore templates */\n\tHDAListItemEdit.prototype.templates = (function(){\n\t\n\t var warnings = _.extend( {}, _super.prototype.templates.warnings, {\n\t hidden : BASE_MVC.wrapTemplate([\n\t '<% if( !dataset.visible ){ %>',\n\t // add a link to unhide a dataset\n\t '
            ',\n\t _l( 'This dataset has been hidden' ),\n\t '
            ', _l( 'Unhide it' ), '',\n\t '
            ',\n\t '<% } %>'\n\t ], 'dataset' )\n\t });\n\t\n\t return _.extend( {}, _super.prototype.templates, {\n\t //NOTE: *steal* the HDAListItemView titleBar\n\t titleBar : HDA_LI.HDAListItemView.prototype.templates.titleBar,\n\t warnings : warnings\n\t });\n\t}());\n\t\n\t\n\t//==============================================================================\n\t return {\n\t HDAListItemEdit : HDAListItemEdit\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(1), __webpack_require__(2)))\n\n/***/ },\n/* 112 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(73),\n\t __webpack_require__(107),\n\t __webpack_require__(22),\n\t __webpack_require__(5)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( HDCA_LI, DC_VIEW_EDIT, faIconButton, _l ){\n\t\n\t'use strict';\n\t\n\t//==============================================================================\n\tvar _super = HDCA_LI.HDCAListItemView;\n\t/** @class Editing view for HistoryDatasetCollectionAssociation.\n\t */\n\tvar HDCAListItemEdit = _super.extend(\n\t/** @lends HDCAListItemEdit.prototype */{\n\t\n\t /** logger used to record this.log messages, commonly set to console */\n\t //logger : console,\n\t\n\t /** Override to return editable versions of the collection panels */\n\t _getFoldoutPanelClass : function(){\n\t switch( this.model.get( 'collection_type' ) ){\n\t case 'list':\n\t return DC_VIEW_EDIT.ListCollectionViewEdit;\n\t case 'paired':\n\t return DC_VIEW_EDIT.PairCollectionViewEdit;\n\t case 'list:paired':\n\t return DC_VIEW_EDIT.ListOfPairsCollectionViewEdit;\n\t case 'list:list':\n\t return DC_VIEW_EDIT.ListOfListsCollectionViewEdit;\n\t }\n\t throw new TypeError( 'Uknown collection_type: ' + this.model.get( 'collection_type' ) );\n\t },\n\t\n\t // ......................................................................... delete\n\t /** In this override, add the delete button. */\n\t _renderPrimaryActions : function(){\n\t this.log( this + '._renderPrimaryActions' );\n\t // render the display, edit attr and delete icon-buttons\n\t return _super.prototype._renderPrimaryActions.call( this )\n\t .concat([\n\t this._renderDeleteButton()\n\t ]);\n\t },\n\t\n\t /** Render icon-button to delete this collection. */\n\t _renderDeleteButton : function(){\n\t var self = this,\n\t deleted = this.model.get( 'deleted' );\n\t return faIconButton({\n\t title : deleted? _l( 'Dataset collection is already deleted' ): _l( 'Delete' ),\n\t classes : 'delete-btn',\n\t faIcon : 'fa-times',\n\t disabled : deleted,\n\t onclick : function() {\n\t // ...bler... tooltips being left behind in DOM (hover out never called on deletion)\n\t self.$el.find( '.icon-btn.delete-btn' ).trigger( 'mouseout' );\n\t self.model[ 'delete' ]();\n\t }\n\t });\n\t },\n\t\n\t // ......................................................................... misc\n\t /** string rep */\n\t toString : function(){\n\t var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n\t return 'HDCAListItemEdit(' + modelString + ')';\n\t }\n\t});\n\t\n\t//==============================================================================\n\t return {\n\t HDCAListItemEdit : HDCAListItemEdit\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\n\n/***/ },\n/* 113 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(_, $, jQuery) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(75),\n\t __webpack_require__(114),\n\t __webpack_require__(6),\n\t __webpack_require__(5)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( HISTORY_MODEL, HISTORY_VIEW_EDIT, BASE_MVC, _l ){\n\t\n\t'use strict';\n\t\n\t// ============================================================================\n\t/** session storage for history panel preferences (and to maintain state)\n\t */\n\tvar HistoryViewPrefs = BASE_MVC.SessionStorageModel.extend(\n\t/** @lends HistoryViewPrefs.prototype */{\n\t defaults : {\n\t /** should the tags editor be shown or hidden initially? */\n\t tagsEditorShown : false,\n\t /** should the annotation editor be shown or hidden initially? */\n\t annotationEditorShown : false,\n\t ///** what is the currently focused content (dataset or collection) in the current history?\n\t // * (the history panel will highlight and scroll to the focused content view)\n\t // */\n\t //focusedContentId : null\n\t /** Current scroll position */\n\t scrollPosition : 0\n\t },\n\t toString : function(){\n\t return 'HistoryViewPrefs(' + JSON.stringify( this.toJSON() ) + ')';\n\t }\n\t});\n\t\n\t/** key string to store panel prefs (made accessible on class so you can access sessionStorage directly) */\n\tHistoryViewPrefs.storageKey = function storageKey(){\n\t return ( 'history-panel' );\n\t};\n\t\n\t/* =============================================================================\n\tTODO:\n\t\n\t============================================================================= */\n\tvar _super = HISTORY_VIEW_EDIT.HistoryViewEdit;\n\t// used in root/index.mako\n\t/** @class View/Controller for the user's current history model as used in the history\n\t * panel (current right hand panel) of the analysis page.\n\t *\n\t * The only history panel that:\n\t * will poll for updates.\n\t * displays datasets in reverse hid order.\n\t */\n\tvar CurrentHistoryView = _super.extend(/** @lends CurrentHistoryView.prototype */{\n\t\n\t className : _super.prototype.className + ' current-history-panel',\n\t\n\t /** override to use drilldown (and not foldout) for how collections are displayed */\n\t HDCAViewClass : _super.prototype.HDCAViewClass.extend({\n\t foldoutStyle : 'drilldown'\n\t }),\n\t\n\t emptyMsg : [\n\t _l( 'This history is empty' ), '. ',\n\t _l( 'You can ' ),\n\t '',\n\t _l( 'load your own data' ),\n\t '',\n\t _l( ' or ' ),\n\t '',\n\t _l( 'get data from an external source' ),\n\t ''\n\t ].join(''),\n\t\n\t // ......................................................................... SET UP\n\t /** Set up the view, set up storage, bind listeners to HistoryContents events */\n\t initialize : function( attributes ){\n\t attributes = attributes || {};\n\t\n\t // ---- persistent preferences\n\t /** maintain state / preferences over page loads */\n\t this.preferences = new HistoryViewPrefs( _.extend({\n\t id : HistoryViewPrefs.storageKey()\n\t }, _.pick( attributes, _.keys( HistoryViewPrefs.prototype.defaults ) )));\n\t\n\t _super.prototype.initialize.call( this, attributes );\n\t\n\t /** sub-views that will overlay this panel (collections) */\n\t this.panelStack = [];\n\t\n\t /** id of currently focused content */\n\t this.currentContentId = attributes.currentContentId || null;\n\t //NOTE: purposely not sent to localstorage since panel recreation roughly lines up with a reset of this value\n\t },\n\t\n\t /** Override to cache the current scroll position with a listener */\n\t _setUpListeners : function(){\n\t _super.prototype._setUpListeners.call( this );\n\t\n\t var panel = this;\n\t // reset scroll position when there's a new history\n\t this.on( 'new-model', function(){\n\t panel.preferences.set( 'scrollPosition', 0 );\n\t });\n\t },\n\t\n\t // ------------------------------------------------------------------------ loading history/item models\n\t // TODO: next three more appropriate moved to the app level\n\t /** (re-)loads the user's current history & contents w/ details */\n\t loadCurrentHistory : function(){\n\t return this.loadHistory( null, { url : Galaxy.root + 'history/current_history_json' });\n\t },\n\t\n\t /** loads a history & contents w/ details and makes them the current history */\n\t switchToHistory : function( historyId, attributes ){\n\t if( Galaxy.user.isAnonymous() ){\n\t this.trigger( 'error', _l( 'You must be logged in to switch histories' ), _l( 'Anonymous user' ) );\n\t return $.when();\n\t }\n\t return this.loadHistory( historyId, { url : Galaxy.root + 'history/set_as_current?id=' + historyId });\n\t },\n\t\n\t /** creates a new history on the server and sets it as the user's current history */\n\t createNewHistory : function( attributes ){\n\t if( Galaxy.user.isAnonymous() ){\n\t this.trigger( 'error', _l( 'You must be logged in to create histories' ), _l( 'Anonymous user' ) );\n\t return $.when();\n\t }\n\t return this.loadHistory( null, { url : Galaxy.root + 'history/create_new_current' });\n\t },\n\t\n\t /** release/free/shutdown old models and set up panel for new models */\n\t setModel : function( model, attributes, render ){\n\t _super.prototype.setModel.call( this, model, attributes, render );\n\t if( this.model && this.model.id ){\n\t this.log( 'checking for updates' );\n\t this.model.checkForUpdates();\n\t }\n\t return this;\n\t },\n\t\n\t // ------------------------------------------------------------------------ history/content event listening\n\t /** listening for history events */\n\t _setUpModelListeners : function(){\n\t _super.prototype._setUpModelListeners.call( this );\n\t // re-broadcast any model change events so that listeners don't have to re-bind to each history\n\t return this.listenTo( this.model, {\n\t 'change:nice_size change:size' : function(){\n\t this.trigger( 'history-size-change', this, this.model, arguments );\n\t },\n\t 'change:id' : function(){\n\t this.once( 'loading-done', function(){ this.model.checkForUpdates(); });\n\t }\n\t });\n\t },\n\t\n\t /** listening for collection events */\n\t _setUpCollectionListeners : function(){\n\t _super.prototype._setUpCollectionListeners.call( this );\n\t // if a hidden item is created (gen. by a workflow), moves thru the updater to the ready state,\n\t // then: remove it from the collection if the panel is set to NOT show hidden datasets\n\t this.listenTo( this.collection, 'state:ready', function( model, newState, oldState ){\n\t if( ( !model.get( 'visible' ) )\n\t && ( !this.collection.storage.includeHidden() ) ){\n\t this.removeItemView( model );\n\t }\n\t });\n\t },\n\t\n\t // ------------------------------------------------------------------------ panel rendering\n\t /** override to add a handler to capture the scroll position when the parent scrolls */\n\t _setUpBehaviors : function( $where ){\n\t $where = $where || this.$el;\n\t // console.log( '_setUpBehaviors', this.$scrollContainer( $where ).get(0), this.$list( $where ) );\n\t // we need to call this in _setUpBehaviors which is called after render since the $el\n\t // may not be attached to $el.parent and $scrollContainer() may not work\n\t var panel = this;\n\t _super.prototype._setUpBehaviors.call( panel, $where );\n\t\n\t // cache the handler to remove and re-add so we don't pile up the handlers\n\t if( !this._debouncedScrollCaptureHandler ){\n\t this._debouncedScrollCaptureHandler = _.debounce( function scrollCapture(){\n\t // cache the scroll position (only if visible)\n\t if( panel.$el.is( ':visible' ) ){\n\t panel.preferences.set( 'scrollPosition', $( this ).scrollTop() );\n\t }\n\t }, 40 );\n\t }\n\t\n\t panel.$scrollContainer( $where )\n\t .off( 'scroll', this._debouncedScrollCaptureHandler )\n\t .on( 'scroll', this._debouncedScrollCaptureHandler );\n\t return panel;\n\t },\n\t\n\t /** In this override, handle null models and move the search input to the top */\n\t _buildNewRender : function(){\n\t if( !this.model ){ return $(); }\n\t var $newRender = _super.prototype._buildNewRender.call( this );\n\t $newRender.find( '.search' ).prependTo( $newRender.find( '> .controls' ) );\n\t this._renderQuotaMessage( $newRender );\n\t return $newRender;\n\t },\n\t\n\t /** render the message displayed when a user is over quota and can't run jobs */\n\t _renderQuotaMessage : function( $whereTo ){\n\t $whereTo = $whereTo || this.$el;\n\t return $( this.templates.quotaMsg( {}, this ) ).prependTo( $whereTo.find( '.messages' ) );\n\t },\n\t\n\t /** In this override, get and set current panel preferences when editor is used */\n\t _renderTags : function( $where ){\n\t var panel = this;\n\t // render tags and show/hide based on preferences\n\t _super.prototype._renderTags.call( panel, $where );\n\t if( panel.preferences.get( 'tagsEditorShown' ) ){\n\t panel.tagsEditor.toggle( true );\n\t }\n\t // store preference when shown or hidden\n\t panel.listenTo( panel.tagsEditor, 'hiddenUntilActivated:shown hiddenUntilActivated:hidden',\n\t function( tagsEditor ){\n\t panel.preferences.set( 'tagsEditorShown', tagsEditor.hidden );\n\t }\n\t );\n\t },\n\t\n\t /** In this override, get and set current panel preferences when editor is used */\n\t _renderAnnotation : function( $where ){\n\t var panel = this;\n\t // render annotation and show/hide based on preferences\n\t _super.prototype._renderAnnotation.call( panel, $where );\n\t if( panel.preferences.get( 'annotationEditorShown' ) ){\n\t panel.annotationEditor.toggle( true );\n\t }\n\t // store preference when shown or hidden\n\t panel.listenTo( panel.annotationEditor, 'hiddenUntilActivated:shown hiddenUntilActivated:hidden',\n\t function( annotationEditor ){\n\t panel.preferences.set( 'annotationEditorShown', annotationEditor.hidden );\n\t }\n\t );\n\t },\n\t\n\t /** Override to scroll to cached position (in prefs) after swapping */\n\t _swapNewRender : function( $newRender ){\n\t _super.prototype._swapNewRender.call( this, $newRender );\n\t var panel = this;\n\t _.delay( function(){\n\t var pos = panel.preferences.get( 'scrollPosition' );\n\t if( pos ){\n\t panel.scrollTo( pos, 0 );\n\t }\n\t }, 10 );\n\t //TODO: is this enough of a delay on larger histories?\n\t\n\t return this;\n\t },\n\t\n\t // ------------------------------------------------------------------------ sub-views\n\t /** Override to add the current-content highlight class to currentContentId's view */\n\t _attachItems : function( $whereTo ){\n\t _super.prototype._attachItems.call( this, $whereTo );\n\t var panel = this;\n\t if( panel.currentContentId ){\n\t panel._setCurrentContentById( panel.currentContentId );\n\t }\n\t return this;\n\t },\n\t\n\t /** Override to remove any drill down panels */\n\t addItemView : function( model, collection, options ){\n\t var view = _super.prototype.addItemView.call( this, model, collection, options );\n\t if( !view ){ return view; }\n\t if( this.panelStack.length ){ return this._collapseDrilldownPanel(); }\n\t return view;\n\t },\n\t\n\t // ------------------------------------------------------------------------ collection sub-views\n\t /** In this override, add/remove expanded/collapsed model ids to/from web storage */\n\t _setUpItemViewListeners : function( view ){\n\t var panel = this;\n\t _super.prototype._setUpItemViewListeners.call( panel, view );\n\t // use pub-sub to: handle drilldown expansion and collapse\n\t return panel.listenTo( view, {\n\t 'expanded:drilldown' : function( v, drilldown ){\n\t this._expandDrilldownPanel( drilldown );\n\t },\n\t 'collapsed:drilldown' : function( v, drilldown ){\n\t this._collapseDrilldownPanel( drilldown );\n\t },\n\t });\n\t },\n\t\n\t /** display 'current content': add a visible highlight and store the id of a content item */\n\t setCurrentContent : function( view ){\n\t this.$( '.history-content.current-content' ).removeClass( 'current-content' );\n\t if( view ){\n\t view.$el.addClass( 'current-content' );\n\t this.currentContentId = view.model.id;\n\t } else {\n\t this.currentContentId = null;\n\t }\n\t },\n\t\n\t /** find the view with the id and then call setCurrentContent on it */\n\t _setCurrentContentById : function( id ){\n\t var view = this.viewFromModelId( id ) || null;\n\t this.setCurrentContent( view );\n\t },\n\t\n\t /** Handle drill down by hiding this panels list and controls and showing the sub-panel */\n\t _expandDrilldownPanel : function( drilldown ){\n\t this.panelStack.push( drilldown );\n\t // hide this panel's controls and list, set the name for back navigation, and attach to the $el\n\t this.$controls().add( this.$list() ).hide();\n\t drilldown.parentName = this.model.get( 'name' );\n\t drilldown.delegateEvents().render().$el.appendTo( this.$el );\n\t },\n\t\n\t /** Handle drilldown close by freeing the panel and re-rendering this panel */\n\t _collapseDrilldownPanel : function( drilldown ){\n\t this.panelStack.pop();\n\t //TODO: MEM: free the panel\n\t this.$controls().add( this.$list() ).show();\n\t },\n\t\n\t // ........................................................................ panel events\n\t /** event map */\n\t events : _.extend( _.clone( _super.prototype.events ), {\n\t // the two links in the empty message\n\t 'click .uploader-link' : function( ev ){ Galaxy.upload.show( ev ); },\n\t 'click .get-data-link' : function( ev ){\n\t var $toolMenu = $( '.toolMenuContainer' );\n\t $toolMenu.parent().scrollTop( 0 );\n\t $toolMenu.find( 'span:contains(\"Get Data\")' ).click();\n\t }\n\t }),\n\t\n\t // ........................................................................ external objects/MVC\n\t listenToGalaxy : function( galaxy ){\n\t this.listenTo( galaxy, {\n\t // when the galaxy_main iframe is loaded with a new page,\n\t // compare the url to the following list and if there's a match\n\t // pull the id from url and indicate in the history view that\n\t // the dataset with that id is the 'current'ly active dataset\n\t 'galaxy_main:load': function( data ){\n\t var pathToMatch = data.fullpath;\n\t var hdaId = null;\n\t var useToURLRegexMap = {\n\t 'display' : /datasets\\/([a-f0-9]+)\\/display/,\n\t 'edit' : /datasets\\/([a-f0-9]+)\\/edit/,\n\t 'report_error' : /dataset\\/errors\\?id=([a-f0-9]+)/,\n\t 'rerun' : /tool_runner\\/rerun\\?id=([a-f0-9]+)/,\n\t 'show_params' : /datasets\\/([a-f0-9]+)\\/show_params/,\n\t // no great way to do this here? (leave it in the dataset event handlers above?)\n\t // 'visualization' : 'visualization',\n\t };\n\t _.find( useToURLRegexMap, function( regex, use ){\n\t // grab the more specific match result (1), save, and use it as the find flag\n\t hdaId = _.result( pathToMatch.match( regex ), 1 );\n\t return hdaId;\n\t });\n\t // need to type mangle to go from web route to history contents\n\t this._setCurrentContentById( hdaId? ( 'dataset-' + hdaId ) : null );\n\t },\n\t // when the center panel is given a new view, clear the current indicator\n\t 'center-panel:load': function( view ){\n\t this._setCurrentContentById();\n\t }\n\t });\n\t },\n\t\n\t //TODO: remove quota meter from panel and remove this\n\t /** add listeners to an external quota meter (mvc/user/user-quotameter.js) */\n\t connectToQuotaMeter : function( quotaMeter ){\n\t if( !quotaMeter ){\n\t return this;\n\t }\n\t // show/hide the 'over quota message' in the history when the meter tells it to\n\t this.listenTo( quotaMeter, 'quota:over', this.showQuotaMessage );\n\t this.listenTo( quotaMeter, 'quota:under', this.hideQuotaMessage );\n\t\n\t // having to add this to handle re-render of hview while overquota (the above do not fire)\n\t this.on( 'rendered rendered:initial', function(){\n\t if( quotaMeter && quotaMeter.isOverQuota() ){\n\t this.showQuotaMessage();\n\t }\n\t });\n\t return this;\n\t },\n\t\n\t /** Override to preserve the quota message */\n\t clearMessages : function( ev ){\n\t var $target = !_.isUndefined( ev )?\n\t $( ev.currentTarget )\n\t :this.$messages().children( '[class$=\"message\"]' );\n\t $target = $target.not( '.quota-message' );\n\t $target.fadeOut( this.fxSpeed, function(){\n\t $( this ).remove();\n\t });\n\t return this;\n\t },\n\t\n\t /** Show the over quota message (which happens to be in the history panel).\n\t */\n\t showQuotaMessage : function(){\n\t var $msg = this.$( '.quota-message' );\n\t if( $msg.is( ':hidden' ) ){ $msg.slideDown( this.fxSpeed ); }\n\t },\n\t\n\t /** Hide the over quota message (which happens to be in the history panel).\n\t */\n\t hideQuotaMessage : function(){\n\t var $msg = this.$( '.quota-message' );\n\t if( !$msg.is( ':hidden' ) ){ $msg.slideUp( this.fxSpeed ); }\n\t },\n\t\n\t // ........................................................................ options menu\n\t //TODO: remove to batch\n\t /** unhide any hidden datasets */\n\t unhideHidden : function() {\n\t var self = this;\n\t if( confirm( _l( 'Really unhide all hidden datasets?' ) ) ){\n\t // get all hidden, regardless of deleted/purged\n\t return self.model.contents._filterAndUpdate(\n\t { visible: false, deleted: '', purged: '' },\n\t { visible : true }\n\t ).done( function(){\n\t // TODO: would be better to render these as they're unhidden instead of all at once\n\t if( !self.model.contents.includeHidden ){\n\t self.renderItems();\n\t }\n\t });\n\t }\n\t return jQuery.when();\n\t },\n\t\n\t /** delete any hidden datasets */\n\t deleteHidden : function() {\n\t var self = this;\n\t if( confirm( _l( 'Really delete all hidden datasets?' ) ) ){\n\t return self.model.contents._filterAndUpdate(\n\t // get all hidden, regardless of deleted/purged\n\t { visible: false, deleted: '', purged: '' },\n\t // both delete *and* unhide them\n\t { deleted : true, visible: true }\n\t );\n\t }\n\t return jQuery.when();\n\t },\n\t\n\t /** Return a string rep of the history */\n\t toString : function(){\n\t return 'CurrentHistoryView(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';\n\t }\n\t});\n\t\n\t\n\t//------------------------------------------------------------------------------ TEMPLATES\n\tCurrentHistoryView.prototype.templates = (function(){\n\t\n\t var quotaMsgTemplate = BASE_MVC.wrapTemplate([\n\t '
            ',\n\t _l( 'You are over your disk quota' ), '. ',\n\t _l( 'Tool execution is on hold until your disk usage drops below your allocated quota' ), '.',\n\t '
            '\n\t ], 'history' );\n\t return _.extend( _.clone( _super.prototype.templates ), {\n\t quotaMsg : quotaMsgTemplate\n\t });\n\t\n\t}());\n\t\n\t\n\t//==============================================================================\n\t return {\n\t CurrentHistoryView : CurrentHistoryView\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(2), __webpack_require__(1), __webpack_require__(1)))\n\n/***/ },\n/* 114 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(jQuery, _, $) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(115),\n\t __webpack_require__(40),\n\t __webpack_require__(12),\n\t __webpack_require__(72),\n\t __webpack_require__(111),\n\t __webpack_require__(112),\n\t __webpack_require__(77),\n\t __webpack_require__(66),\n\t __webpack_require__(31),\n\t __webpack_require__(109),\n\t __webpack_require__(108),\n\t __webpack_require__(22),\n\t __webpack_require__(79),\n\t __webpack_require__(6),\n\t __webpack_require__(5),\n\t __webpack_require__(15),\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function(\n\t HISTORY_VIEW,\n\t HISTORY_CONTENTS,\n\t STATES,\n\t HDA_MODEL,\n\t HDA_LI_EDIT,\n\t HDCA_LI_EDIT,\n\t TAGS,\n\t ANNOTATIONS,\n\t LIST_COLLECTION_CREATOR,\n\t PAIR_COLLECTION_CREATOR,\n\t LIST_OF_PAIRS_COLLECTION_CREATOR,\n\t faIconButton,\n\t PopupMenu,\n\t BASE_MVC,\n\t _l\n\t){\n\t\n\t'use strict';\n\t\n\t/* =============================================================================\n\tTODO:\n\t\n\t============================================================================= */\n\tvar _super = HISTORY_VIEW.HistoryView;\n\t// base class for history-view-edit-current and used as-is in history/view.mako\n\t/** @class Editable View/Controller for the history model.\n\t *\n\t * Allows:\n\t * (everything HistoryView allows)\n\t * changing the name\n\t * displaying and editing tags and annotations\n\t * multi-selection and operations on mulitple content items\n\t */\n\tvar HistoryViewEdit = _super.extend(\n\t/** @lends HistoryViewEdit.prototype */{\n\t\n\t /** class to use for constructing the HistoryDatasetAssociation views */\n\t HDAViewClass : HDA_LI_EDIT.HDAListItemEdit,\n\t /** class to use for constructing the HistoryDatasetCollectionAssociation views */\n\t HDCAViewClass : HDCA_LI_EDIT.HDCAListItemEdit,\n\t\n\t // ......................................................................... SET UP\n\t /** Set up the view, set up storage, bind listeners to HistoryContents events\n\t * @param {Object} attributes\n\t */\n\t initialize : function( attributes ){\n\t attributes = attributes || {};\n\t _super.prototype.initialize.call( this, attributes );\n\t\n\t // ---- set up instance vars\n\t /** editor for tags - sub-view */\n\t this.tagsEditor = null;\n\t /** editor for annotations - sub-view */\n\t this.annotationEditor = null;\n\t\n\t /** allow user purge of dataset files? */\n\t this.purgeAllowed = attributes.purgeAllowed || false;\n\t\n\t // states/modes the panel can be in\n\t /** is the panel currently showing the dataset selection controls? */\n\t this.annotationEditorShown = attributes.annotationEditorShown || false;\n\t this.tagsEditorShown = attributes.tagsEditorShown || false;\n\t },\n\t\n\t /** Override to handle history as drag-drop target */\n\t _setUpListeners : function(){\n\t _super.prototype._setUpListeners.call( this );\n\t return this.on({\n\t 'droptarget:drop': function( ev, data ){\n\t // process whatever was dropped and re-hide the drop target\n\t this.dataDropped( data );\n\t this.dropTargetOff();\n\t },\n\t 'view:attached view:removed': function(){\n\t this._renderCounts();\n\t },\n\t 'search:loading-progress': this._renderSearchProgress,\n\t 'search:searching': this._renderSearchFindings,\n\t });\n\t },\n\t\n\t // ------------------------------------------------------------------------ listeners\n\t /** listening for history and HDA events */\n\t _setUpModelListeners : function(){\n\t _super.prototype._setUpModelListeners.call( this );\n\t this.listenTo( this.model, 'change:size', this.updateHistoryDiskSize );\n\t return this;\n\t },\n\t\n\t /** listening for collection events */\n\t _setUpCollectionListeners : function(){\n\t _super.prototype._setUpCollectionListeners.call( this );\n\t this.listenTo( this.collection, {\n\t 'change:deleted': this._handleItemDeletedChange,\n\t 'change:visible': this._handleItemVisibleChange,\n\t 'change:purged' : function( model ){\n\t // hafta get the new nice-size w/o the purged model\n\t this.model.fetch();\n\t },\n\t // loading indicators for deleted/hidden\n\t 'fetching-deleted' : function( collection ){\n\t this.$( '> .controls .deleted-count' )\n\t .html( '' + _l( 'loading...' ) + '' );\n\t },\n\t 'fetching-hidden' : function( collection ){\n\t this.$( '> .controls .hidden-count' )\n\t .html( '' + _l( 'loading...' ) + '' );\n\t },\n\t 'fetching-deleted-done fetching-hidden-done' : this._renderCounts,\n\t });\n\t return this;\n\t },\n\t\n\t // ------------------------------------------------------------------------ panel rendering\n\t /** In this override, add tag and annotation editors and a btn to toggle the selectors */\n\t _buildNewRender : function(){\n\t // create a new render using a skeleton template, render title buttons, render body, and set up events, etc.\n\t var $newRender = _super.prototype._buildNewRender.call( this );\n\t if( !this.model ){ return $newRender; }\n\t\n\t if( Galaxy && Galaxy.user && Galaxy.user.id && Galaxy.user.id === this.model.get( 'user_id' ) ){\n\t this._renderTags( $newRender );\n\t this._renderAnnotation( $newRender );\n\t }\n\t return $newRender;\n\t },\n\t\n\t /** Update the history size display (curr. upper right of panel). */\n\t updateHistoryDiskSize : function(){\n\t this.$( '.history-size' ).text( this.model.get( 'nice_size' ) );\n\t },\n\t\n\t /** override to render counts when the items are rendered */\n\t renderItems : function( $whereTo ){\n\t var views = _super.prototype.renderItems.call( this, $whereTo );\n\t if( !this.searchFor ){ this._renderCounts( $whereTo ); }\n\t return views;\n\t },\n\t\n\t /** override to show counts, what's deleted/hidden, and links to toggle those */\n\t _renderCounts : function( $whereTo ){\n\t $whereTo = $whereTo instanceof jQuery? $whereTo : this.$el;\n\t var html = this.templates.counts( this.model.toJSON(), this );\n\t return $whereTo.find( '> .controls .subtitle' ).html( html );\n\t },\n\t\n\t /** render the tags sub-view controller */\n\t _renderTags : function( $where ){\n\t var panel = this;\n\t this.tagsEditor = new TAGS.TagsEditor({\n\t model : this.model,\n\t el : $where.find( '.controls .tags-display' ),\n\t onshowFirstTime : function(){ this.render(); },\n\t // show hide sub-view tag editors when this is shown/hidden\n\t onshow : function(){\n\t panel.toggleHDATagEditors( true, panel.fxSpeed );\n\t },\n\t onhide : function(){\n\t panel.toggleHDATagEditors( false, panel.fxSpeed );\n\t },\n\t $activator : faIconButton({\n\t title : _l( 'Edit history tags' ),\n\t classes : 'history-tag-btn',\n\t faIcon : 'fa-tags'\n\t }).appendTo( $where.find( '.controls .actions' ) )\n\t });\n\t },\n\t /** render the annotation sub-view controller */\n\t _renderAnnotation : function( $where ){\n\t var panel = this;\n\t this.annotationEditor = new ANNOTATIONS.AnnotationEditor({\n\t model : this.model,\n\t el : $where.find( '.controls .annotation-display' ),\n\t onshowFirstTime : function(){ this.render(); },\n\t // show hide sub-view view annotation editors when this is shown/hidden\n\t onshow : function(){\n\t panel.toggleHDAAnnotationEditors( true, panel.fxSpeed );\n\t },\n\t onhide : function(){\n\t panel.toggleHDAAnnotationEditors( false, panel.fxSpeed );\n\t },\n\t $activator : faIconButton({\n\t title : _l( 'Edit history annotation' ),\n\t classes : 'history-annotate-btn',\n\t faIcon : 'fa-comment'\n\t }).appendTo( $where.find( '.controls .actions' ) )\n\t });\n\t },\n\t\n\t /** Set up HistoryViewEdit js/widget behaviours\n\t * In this override, make the name editable\n\t */\n\t _setUpBehaviors : function( $where ){\n\t $where = $where || this.$el;\n\t _super.prototype._setUpBehaviors.call( this, $where );\n\t if( !this.model ){ return; }\n\t\n\t // anon users shouldn't have access to any of the following\n\t if( ( !Galaxy.user || Galaxy.user.isAnonymous() )\n\t || ( Galaxy.user.id !== this.model.get( 'user_id' ) ) ){\n\t return;\n\t }\n\t\n\t var panel = this,\n\t nameSelector = '> .controls .name';\n\t $where.find( nameSelector )\n\t .attr( 'title', _l( 'Click to rename history' ) )\n\t .tooltip({ placement: 'bottom' })\n\t .make_text_editable({\n\t on_finish: function( newName ){\n\t var previousName = panel.model.get( 'name' );\n\t if( newName && newName !== previousName ){\n\t panel.$el.find( nameSelector ).text( newName );\n\t panel.model.save({ name: newName })\n\t .fail( function(){\n\t panel.$el.find( nameSelector ).text( panel.model.previous( 'name' ) );\n\t });\n\t } else {\n\t panel.$el.find( nameSelector ).text( previousName );\n\t }\n\t }\n\t });\n\t },\n\t\n\t /** return a new popup menu for choosing a multi selection action\n\t * ajax calls made for multiple datasets are queued\n\t */\n\t multiselectActions : function(){\n\t var panel = this,\n\t actions = [\n\t { html: _l( 'Hide datasets' ), func: function(){\n\t var action = HDA_MODEL.HistoryDatasetAssociation.prototype.hide;\n\t panel.getSelectedModels().ajaxQueue( action );\n\t }\n\t },\n\t { html: _l( 'Unhide datasets' ), func: function(){\n\t var action = HDA_MODEL.HistoryDatasetAssociation.prototype.unhide;\n\t panel.getSelectedModels().ajaxQueue( action );\n\t }\n\t },\n\t { html: _l( 'Delete datasets' ), func: function(){\n\t var action = HDA_MODEL.HistoryDatasetAssociation.prototype['delete'];\n\t panel.getSelectedModels().ajaxQueue( action );\n\t }\n\t },\n\t { html: _l( 'Undelete datasets' ), func: function(){\n\t var action = HDA_MODEL.HistoryDatasetAssociation.prototype.undelete;\n\t panel.getSelectedModels().ajaxQueue( action );\n\t }\n\t }\n\t ];\n\t if( panel.purgeAllowed ){\n\t actions.push({\n\t html: _l( 'Permanently delete datasets' ), func: function(){\n\t if( confirm( _l( 'This will permanently remove the data in your datasets. Are you sure?' ) ) ){\n\t var action = HDA_MODEL.HistoryDatasetAssociation.prototype.purge;\n\t panel.getSelectedModels().ajaxQueue( action );\n\t }\n\t }\n\t });\n\t }\n\t actions = actions.concat( panel._collectionActions() );\n\t return actions;\n\t },\n\t\n\t /** */\n\t _collectionActions : function(){\n\t var panel = this;\n\t return [\n\t { html: _l( 'Build Dataset List' ), func: function() {\n\t LIST_COLLECTION_CREATOR.createListCollection( panel.getSelectedModels() )\n\t .done( function(){ panel.model.refresh(); });\n\t }\n\t },\n\t // TODO: Only show quick pair if two things selected.\n\t { html: _l( 'Build Dataset Pair' ), func: function() {\n\t PAIR_COLLECTION_CREATOR.createPairCollection( panel.getSelectedModels() )\n\t .done( function(){ panel.model.refresh(); });\n\t }\n\t },\n\t { html: _l( 'Build List of Dataset Pairs' ), func: function() {\n\t LIST_OF_PAIRS_COLLECTION_CREATOR.createListOfPairsCollection( panel.getSelectedModels() )\n\t .done( function(){ panel.model.refresh(); });\n\t }\n\t },\n\t ];\n\t },\n\t\n\t // ------------------------------------------------------------------------ sub-views\n\t /** In this override, add purgeAllowed and whether tags/annotation editors should be shown */\n\t _getItemViewOptions : function( model ){\n\t var options = _super.prototype._getItemViewOptions.call( this, model );\n\t _.extend( options, {\n\t purgeAllowed : this.purgeAllowed,\n\t tagsEditorShown : ( this.tagsEditor && !this.tagsEditor.hidden ),\n\t annotationEditorShown : ( this.annotationEditor && !this.annotationEditor.hidden )\n\t });\n\t return options;\n\t },\n\t\n\t /** If this item is deleted and we're not showing deleted items, remove the view\n\t * @param {Model} the item model to check\n\t */\n\t _handleItemDeletedChange : function( itemModel ){\n\t if( itemModel.get( 'deleted' ) ){\n\t this._handleItemDeletion( itemModel );\n\t } else {\n\t this._handleItemUndeletion( itemModel );\n\t }\n\t this._renderCounts();\n\t },\n\t\n\t _handleItemDeletion : function( itemModel ){\n\t var contentsShown = this.model.get( 'contents_active' );\n\t contentsShown.deleted += 1;\n\t contentsShown.active -= 1;\n\t if( !this.model.contents.includeDeleted ){\n\t this.removeItemView( itemModel );\n\t }\n\t this.model.set( 'contents_active', contentsShown );\n\t },\n\t\n\t _handleItemUndeletion : function( itemModel ){\n\t var contentsShown = this.model.get( 'contents_active' );\n\t contentsShown.deleted -= 1;\n\t if( !this.model.contents.includeDeleted ){\n\t contentsShown.active -= 1;\n\t }\n\t this.model.set( 'contents_active', contentsShown );\n\t },\n\t\n\t /** If this item is hidden and we're not showing hidden items, remove the view\n\t * @param {Model} the item model to check\n\t */\n\t _handleItemVisibleChange : function( itemModel ){\n\t if( itemModel.hidden() ){\n\t this._handleItemHidden( itemModel );\n\t } else {\n\t this._handleItemUnhidden( itemModel );\n\t }\n\t this._renderCounts();\n\t },\n\t\n\t _handleItemHidden : function( itemModel ){\n\t var contentsShown = this.model.get( 'contents_active' );\n\t contentsShown.hidden += 1;\n\t contentsShown.active -= 1;\n\t if( !this.model.contents.includeHidden ){\n\t this.removeItemView( itemModel );\n\t }\n\t this.model.set( 'contents_active', contentsShown );\n\t },\n\t\n\t _handleItemUnhidden : function( itemModel ){\n\t var contentsShown = this.model.get( 'contents_active' );\n\t contentsShown.hidden -= 1;\n\t if( !this.model.contents.includeHidden ){\n\t contentsShown.active -= 1;\n\t }\n\t this.model.set( 'contents_active', contentsShown );\n\t },\n\t\n\t /** toggle the visibility of each content's tagsEditor applying all the args sent to this function */\n\t toggleHDATagEditors : function( showOrHide, speed ){\n\t _.each( this.views, function( view ){\n\t if( view.tagsEditor ){\n\t view.tagsEditor.toggle( showOrHide, speed );\n\t }\n\t });\n\t },\n\t\n\t /** toggle the visibility of each content's annotationEditor applying all the args sent to this function */\n\t toggleHDAAnnotationEditors : function( showOrHide, speed ){\n\t _.each( this.views, function( view ){\n\t if( view.annotationEditor ){\n\t view.annotationEditor.toggle( showOrHide, speed );\n\t }\n\t });\n\t },\n\t\n\t // ------------------------------------------------------------------------ panel events\n\t /** event map */\n\t events : _.extend( _.clone( _super.prototype.events ), {\n\t 'click .show-selectors-btn' : 'toggleSelectors',\n\t 'click .toggle-deleted-link' : function( ev ){ this.toggleShowDeleted(); },\n\t 'click .toggle-hidden-link' : function( ev ){ this.toggleShowHidden(); }\n\t }),\n\t\n\t // ------------------------------------------------------------------------ search\n\t _renderSearchProgress : function( limit, offset ){\n\t var stop = limit + offset;\n\t return this.$( '> .controls .subtitle' ).html([\n\t '',\n\t _l( 'Searching ' ), stop, '/', this.model.contentsShown(),\n\t ''\n\t ].join(''));\n\t },\n\t\n\t /** override to display number found in subtitle */\n\t _renderSearchFindings : function(){\n\t this.$( '> .controls .subtitle' ).html([\n\t _l( 'Found' ), this.views.length\n\t ].join(' '));\n\t return this;\n\t },\n\t\n\t // ------------------------------------------------------------------------ as drop target\n\t /** turn all the drag and drop handlers on and add some help text above the drop area */\n\t dropTargetOn : function(){\n\t if( this.dropTarget ){ return this; }\n\t this.dropTarget = true;\n\t\n\t //TODO: to init\n\t var dropHandlers = {\n\t 'dragenter' : _.bind( this.dragenter, this ),\n\t 'dragover' : _.bind( this.dragover, this ),\n\t 'dragleave' : _.bind( this.dragleave, this ),\n\t 'drop' : _.bind( this.drop, this )\n\t };\n\t\n\t var $dropTarget = this._renderDropTarget();\n\t this.$list().before([ this._renderDropTargetHelp(), $dropTarget ]);\n\t for( var evName in dropHandlers ){\n\t if( dropHandlers.hasOwnProperty( evName ) ){\n\t //console.debug( evName, dropHandlers[ evName ] );\n\t $dropTarget.on( evName, dropHandlers[ evName ] );\n\t }\n\t }\n\t return this;\n\t },\n\t\n\t /** render a box to serve as a 'drop here' area on the history */\n\t _renderDropTarget : function(){\n\t this.$( '.history-drop-target' ).remove();\n\t return $( '
            ' ).addClass( 'history-drop-target' );\n\t },\n\t\n\t /** tell the user how it works */\n\t _renderDropTargetHelp : function(){\n\t this.$( '.history-drop-target-help' ).remove();\n\t return $( '
            ' ).addClass( 'history-drop-target-help' )\n\t .text( _l( 'Drag datasets here to copy them to the current history' ) );\n\t },\n\t\n\t /** shut down drag and drop event handlers and remove drop target */\n\t dropTargetOff : function(){\n\t if( !this.dropTarget ){ return this; }\n\t //this.log( 'dropTargetOff' );\n\t this.dropTarget = false;\n\t var dropTarget = this.$( '.history-drop-target' ).get(0);\n\t for( var evName in this._dropHandlers ){\n\t if( this._dropHandlers.hasOwnProperty( evName ) ){\n\t dropTarget.off( evName, this._dropHandlers[ evName ] );\n\t }\n\t }\n\t this.$( '.history-drop-target' ).remove();\n\t this.$( '.history-drop-target-help' ).remove();\n\t return this;\n\t },\n\t /** toggle the target on/off */\n\t dropTargetToggle : function(){\n\t if( this.dropTarget ){\n\t this.dropTargetOff();\n\t } else {\n\t this.dropTargetOn();\n\t }\n\t return this;\n\t },\n\t\n\t dragenter : function( ev ){\n\t //console.debug( 'dragenter:', this, ev );\n\t ev.preventDefault();\n\t ev.stopPropagation();\n\t this.$( '.history-drop-target' ).css( 'border', '2px solid black' );\n\t },\n\t dragover : function( ev ){\n\t ev.preventDefault();\n\t ev.stopPropagation();\n\t },\n\t dragleave : function( ev ){\n\t //console.debug( 'dragleave:', this, ev );\n\t ev.preventDefault();\n\t ev.stopPropagation();\n\t this.$( '.history-drop-target' ).css( 'border', '1px dashed black' );\n\t },\n\t /** when (text) is dropped try to parse as json and trigger an event */\n\t drop : function( ev ){\n\t ev.preventDefault();\n\t //ev.stopPropagation();\n\t\n\t var self = this;\n\t var dataTransfer = ev.originalEvent.dataTransfer;\n\t var data = dataTransfer.getData( \"text\" );\n\t\n\t dataTransfer.dropEffect = 'move';\n\t try {\n\t data = JSON.parse( data );\n\t } catch( err ){\n\t self.warn( 'error parsing JSON from drop:', data );\n\t }\n\t\n\t self.trigger( 'droptarget:drop', ev, data, self );\n\t return false;\n\t },\n\t\n\t /** handler that copies data into the contents */\n\t dataDropped : function( data ){\n\t var self = this;\n\t // HDA: dropping will copy it to the history\n\t if( _.isObject( data ) && data.model_class === 'HistoryDatasetAssociation' && data.id ){\n\t if( self.contents.currentPage !== 0 ){\n\t return self.contents.fetchPage( 0 )\n\t .then( function(){\n\t return self.model.contents.copy( data.id );\n\t });\n\t }\n\t return self.model.contents.copy( data.id );\n\t }\n\t return jQuery.when();\n\t },\n\t\n\t // ........................................................................ misc\n\t /** Return a string rep of the history */\n\t toString : function(){\n\t return 'HistoryViewEdit(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';\n\t }\n\t});\n\t\n\t//------------------------------------------------------------------------------ TEMPLATES\n\tHistoryViewEdit.prototype.templates = (function(){\n\t\n\t var countsTemplate = BASE_MVC.wrapTemplate([\n\t '<% var shown = Math.max( view.views.length, history.contents_active.active ) %>',\n\t '<% if( shown ){ %>',\n\t '',\n\t '<%- shown %> ', _l( 'shown' ),\n\t '',\n\t '<% } %>',\n\t\n\t '<% if( history.contents_active.deleted ){ %>',\n\t '',\n\t '<% if( view.model.contents.includeDeleted ){ %>',\n\t '',\n\t _l( 'hide deleted' ),\n\t '',\n\t '<% } else { %>',\n\t '<%- history.contents_active.deleted %> ',\n\t '',\n\t _l( 'deleted' ),\n\t '',\n\t '<% } %>',\n\t '',\n\t '<% } %>',\n\t\n\t '<% if( history.contents_active.hidden ){ %>',\n\t '',\n\t '<% if( view.model.contents.includeHidden ){ %>',\n\t '',\n\t _l( 'hide hidden' ),\n\t '',\n\t '<% } else { %>',\n\t '<%- history.contents_active.hidden %> ',\n\t '',\n\t _l( 'hidden' ),\n\t '',\n\t '<% } %>',\n\t '',\n\t '<% } %>',\n\t ], 'history' );\n\t\n\t return _.extend( _.clone( _super.prototype.templates ), {\n\t counts : countsTemplate\n\t });\n\t}());\n\t\n\t\n\t//==============================================================================\n\t return {\n\t HistoryViewEdit : HistoryViewEdit\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(1), __webpack_require__(2), __webpack_require__(1)))\n\n/***/ },\n/* 115 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(_, $) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(76),\n\t __webpack_require__(75),\n\t __webpack_require__(40),\n\t __webpack_require__(41),\n\t __webpack_require__(71),\n\t __webpack_require__(73),\n\t __webpack_require__(82),\n\t __webpack_require__(78),\n\t __webpack_require__(22),\n\t __webpack_require__(6),\n\t __webpack_require__(5),\n\t __webpack_require__(85)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function(\n\t LIST_VIEW,\n\t HISTORY_MODEL,\n\t HISTORY_CONTENTS,\n\t HISTORY_PREFS,\n\t HDA_LI,\n\t HDCA_LI,\n\t USER,\n\t ERROR_MODAL,\n\t faIconButton,\n\t BASE_MVC,\n\t _l\n\t){\n\t'use strict';\n\t\n\t/* =============================================================================\n\tTODO:\n\t\n\t============================================================================= */\n\t/** @class non-editable, read-only View/Controller for a history model.\n\t * Allows:\n\t * changing the loaded history\n\t * displaying data, info, and download\n\t * tracking history attrs: size, tags, annotations, name, etc.\n\t * Does not allow:\n\t * changing the name\n\t */\n\tvar _super = LIST_VIEW.ModelListPanel;\n\tvar HistoryView = _super.extend(\n\t/** @lends HistoryView.prototype */{\n\t _logNamespace : 'history',\n\t\n\t /** class to use for constructing the HDA views */\n\t HDAViewClass : HDA_LI.HDAListItemView,\n\t /** class to use for constructing the HDCA views */\n\t HDCAViewClass : HDCA_LI.HDCAListItemView,\n\t /** class to used for constructing collection of sub-view models */\n\t collectionClass : HISTORY_CONTENTS.HistoryContents,\n\t /** key of attribute in model to assign to this.collection */\n\t modelCollectionKey : 'contents',\n\t\n\t tagName : 'div',\n\t className : _super.prototype.className + ' history-panel',\n\t\n\t /** string to display when the collection is empty */\n\t emptyMsg : _l( 'This history is empty' ),\n\t /** displayed when no items match the search terms */\n\t noneFoundMsg : _l( 'No matching datasets found' ),\n\t /** string used for search placeholder */\n\t searchPlaceholder : _l( 'search datasets' ),\n\t\n\t /** @type {Number} ms to wait after history load to fetch/decorate hdcas with element_count */\n\t FETCH_COLLECTION_COUNTS_DELAY : 2000,\n\t\n\t // ......................................................................... SET UP\n\t /** Set up the view, bind listeners.\n\t * @param {Object} attributes optional settings for the panel\n\t */\n\t initialize : function( attributes ){\n\t _super.prototype.initialize.call( this, attributes );\n\t // ---- instance vars\n\t // control contents/behavior based on where (and in what context) the panel is being used\n\t /** where should pages from links be displayed? (default to new tab/window) */\n\t this.linkTarget = attributes.linkTarget || '_blank';\n\t },\n\t\n\t /** create and return a collection for when none is initially passed */\n\t _createDefaultCollection : function(){\n\t // override\n\t return new this.collectionClass([], { history: this.model });\n\t },\n\t\n\t /** In this override, clear the update timer on the model */\n\t freeModel : function(){\n\t _super.prototype.freeModel.call( this );\n\t if( this.model ){\n\t this.model.clearUpdateTimeout();\n\t }\n\t return this;\n\t },\n\t\n\t /** create any event listeners for the panel\n\t * @fires: rendered:initial on the first render\n\t * @fires: empty-history when switching to a history with no contents or creating a new history\n\t */\n\t _setUpListeners : function(){\n\t _super.prototype._setUpListeners.call( this );\n\t this.on({\n\t error : function( model, xhr, options, msg, details ){\n\t this.errorHandler( model, xhr, options, msg, details );\n\t },\n\t 'loading-done' : function(){\n\t var self = this;\n\t // after the initial load, decorate with more time consuming fields (like HDCA element_counts)\n\t _.delay( function(){\n\t self.model.contents.fetchCollectionCounts();\n\t }, self.FETCH_COLLECTION_COUNTS_DELAY );\n\t },\n\t 'views:ready view:attached view:removed' : function( view ){\n\t this._renderSelectButton();\n\t },\n\t 'view:attached' : function( view ){\n\t this.scrollTo(0);\n\t },\n\t });\n\t // this.on( 'all', function(){ console.debug( arguments ); });\n\t },\n\t\n\t // ------------------------------------------------------------------------ loading history/hda models\n\t /** load the history with the given id then it's contents, sending ajax options to both */\n\t loadHistory : function( historyId, options, contentsOptions ){\n\t contentsOptions = _.extend( contentsOptions || { silent: true });\n\t this.info( 'loadHistory:', historyId, options, contentsOptions );\n\t var self = this;\n\t self.setModel( new HISTORY_MODEL.History({ id : historyId }) );\n\t\n\t contentsOptions.silent = true;\n\t self.trigger( 'loading' );\n\t return self.model\n\t .fetchWithContents( options, contentsOptions )\n\t .always( function(){\n\t self.render();\n\t self.trigger( 'loading-done' );\n\t });\n\t },\n\t\n\t /** convenience alias to the model. Updates the item list only (not the history) */\n\t refreshContents : function( options ){\n\t if( this.model ){\n\t return this.model.refresh( options );\n\t }\n\t // may have callbacks - so return an empty promise\n\t return $.when();\n\t },\n\t\n\t /** Override to reset web storage when the id changes (since it needs the id) */\n\t _setUpCollectionListeners : function(){\n\t _super.prototype._setUpCollectionListeners.call( this );\n\t return this.listenTo( this.collection, {\n\t // 'all' : function(){ console.log( this.collection + ':', arguments ); },\n\t 'fetching-more' : function(){\n\t this._toggleContentsLoadingIndicator( true );\n\t this.$emptyMessage().hide();\n\t },\n\t 'fetching-more-done': function(){ this._toggleContentsLoadingIndicator( false ); },\n\t });\n\t },\n\t\n\t // ------------------------------------------------------------------------ panel rendering\n\t /** hide the $el and display a loading indicator (in the $el's parent) when loading new data */\n\t _showLoadingIndicator : function( msg, speed, callback ){\n\t var $indicator = $( '
            ' );\n\t this.$el.html( $indicator.text( msg ).slideDown( !_.isUndefined( speed )? speed : this.fxSpeed ) );\n\t },\n\t\n\t /** hide the loading indicator */\n\t _hideLoadingIndicator : function( speed ){\n\t // make speed a bit slower to compensate for slow rendering of up to 500 contents\n\t this.$( '.loading-indicator' ).slideUp( !_.isUndefined( speed )? speed : ( this.fxSpeed + 200 ), function(){\n\t $( this ).remove();\n\t });\n\t },\n\t\n\t /** In this override, add a btn to toggle the selectors */\n\t _buildNewRender : function(){\n\t var $newRender = _super.prototype._buildNewRender.call( this );\n\t this._renderSelectButton( $newRender );\n\t return $newRender;\n\t },\n\t\n\t /** button for starting select mode */\n\t _renderSelectButton : function( $where ){\n\t $where = $where || this.$el;\n\t // do not render selector option if no actions\n\t if( !this.multiselectActions().length ){\n\t return null;\n\t }\n\t // do not render (and remove even) if nothing to select\n\t if( !this.views.length ){\n\t this.hideSelectors();\n\t $where.find( '.controls .actions .show-selectors-btn' ).remove();\n\t return null;\n\t }\n\t // don't bother rendering if there's one already\n\t var $existing = $where.find( '.controls .actions .show-selectors-btn' );\n\t if( $existing.length ){\n\t return $existing;\n\t }\n\t\n\t return faIconButton({\n\t title : _l( 'Operations on multiple datasets' ),\n\t classes : 'show-selectors-btn',\n\t faIcon : 'fa-check-square-o'\n\t }).prependTo( $where.find( '.controls .actions' ) );\n\t },\n\t\n\t /** override to avoid showing intial empty message using contents_active */\n\t _renderEmptyMessage : function( $whereTo ){\n\t var self = this;\n\t var $emptyMsg = self.$emptyMessage( $whereTo );\n\t\n\t var empty = self.model.get( 'contents_active' ).active <= 0;\n\t if( empty ){\n\t return $emptyMsg.empty().append( self.emptyMsg ).show();\n\t\n\t } else if( self.searchFor && self.model.contents.haveSearchDetails() && !self.views.length ){\n\t return $emptyMsg.empty().append( self.noneFoundMsg ).show();\n\t }\n\t $emptyMsg.hide();\n\t return $();\n\t },\n\t\n\t /** the scroll container for this panel - can be $el, $el.parent(), or grandparent depending on context */\n\t $scrollContainer : function( $where ){\n\t // override or set via attributes.$scrollContainer\n\t return this.$list( $where );\n\t },\n\t\n\t // ------------------------------------------------------------------------ subviews\n\t _toggleContentsLoadingIndicator : function( show ){\n\t if( !show ){\n\t this.$list().find( '.contents-loading-indicator' ).remove();\n\t } else {\n\t this.$list().html( '
            '\n\t + '
            ' );\n\t }\n\t },\n\t\n\t /** override to render pagination also */\n\t renderItems: function( $whereTo ){\n\t // console.log( this + '.renderItems-----------------', new Date() );\n\t $whereTo = $whereTo || this.$el;\n\t var self = this;\n\t var $list = self.$list( $whereTo );\n\t\n\t // TODO: bootstrap hack to remove orphaned tooltips\n\t $( '.tooltip' ).remove();\n\t\n\t $list.empty();\n\t self.views = [];\n\t\n\t var models = self._filterCollection();\n\t if( models.length ){\n\t self._renderPagination( $whereTo );\n\t self.views = self._renderSomeItems( models, $list );\n\t } else {\n\t // TODO: consolidate with _renderPagination above by (???) passing in models/length?\n\t $whereTo.find( '> .controls .list-pagination' ).empty();\n\t }\n\t self._renderEmptyMessage( $whereTo ).toggle( !models.length );\n\t\n\t self.trigger( 'views:ready', self.views );\n\t return self.views;\n\t },\n\t\n\t /** render pagination controls if not searching and contents says we're paginating */\n\t _renderPagination: function( $whereTo ){\n\t var $paginationControls = $whereTo.find( '> .controls .list-pagination' );\n\t if( this.searchFor || !this.model.contents.shouldPaginate() ) return $paginationControls.empty();\n\t\n\t $paginationControls.html( this.templates.pagination({\n\t // pagination is 1-based for the user\n\t current : this.model.contents.currentPage + 1,\n\t last : this.model.contents.getLastPage() + 1,\n\t }, this ));\n\t $paginationControls.find( 'select.pages' ).tooltip();\n\t return $paginationControls;\n\t },\n\t\n\t /** render a subset of the entire collection (client-side pagination) */\n\t _renderSomeItems: function( models, $list ){\n\t var self = this;\n\t var views = [];\n\t $list.append( models.map( function( m ){\n\t var view = self._createItemView( m );\n\t views.push( view );\n\t return self._renderItemView$el( view );\n\t }));\n\t return views;\n\t },\n\t\n\t // ------------------------------------------------------------------------ sub-views\n\t /** in this override, check if the contents would also display based on includeDeleted/hidden */\n\t _filterItem : function( model ){\n\t var self = this;\n\t var contents = self.model.contents;\n\t return ( contents.includeHidden || !model.hidden() )\n\t && ( contents.includeDeleted || !model.isDeletedOrPurged() )\n\t && ( _super.prototype._filterItem.call( self, model ) );\n\t },\n\t\n\t /** In this override, since history contents are mixed,\n\t * get the appropo view class based on history_content_type\n\t */\n\t _getItemViewClass : function( model ){\n\t var contentType = model.get( \"history_content_type\" );\n\t switch( contentType ){\n\t case 'dataset':\n\t return this.HDAViewClass;\n\t case 'dataset_collection':\n\t return this.HDCAViewClass;\n\t }\n\t throw new TypeError( 'Unknown history_content_type: ' + contentType );\n\t },\n\t\n\t /** in this override, add a linktarget, and expand if id is in web storage */\n\t _getItemViewOptions : function( model ){\n\t var options = _super.prototype._getItemViewOptions.call( this, model );\n\t return _.extend( options, {\n\t linkTarget : this.linkTarget,\n\t expanded : this.model.contents.storage.isExpanded( model.id ),\n\t hasUser : this.model.ownedByCurrUser()\n\t });\n\t },\n\t\n\t /** In this override, add/remove expanded/collapsed model ids to/from web storage */\n\t _setUpItemViewListeners : function( view ){\n\t var panel = this;\n\t _super.prototype._setUpItemViewListeners.call( panel, view );\n\t //TODO: send from content view: this.model.collection.storage.addExpanded\n\t // maintain a list of items whose bodies are expanded\n\t return panel.listenTo( view, {\n\t 'expanded': function( v ){\n\t panel.model.contents.storage.addExpanded( v.model );\n\t },\n\t 'collapsed': function( v ){\n\t panel.model.contents.storage.removeExpanded( v.model );\n\t }\n\t });\n\t },\n\t\n\t /** override to remove expandedIds from webstorage */\n\t collapseAll : function(){\n\t this.model.contents.storage.clearExpanded();\n\t _super.prototype.collapseAll.call( this );\n\t },\n\t\n\t // ------------------------------------------------------------------------ selection\n\t /** Override to correctly set the historyId of the new collection */\n\t getSelectedModels : function(){\n\t var collection = _super.prototype.getSelectedModels.call( this );\n\t collection.historyId = this.collection.historyId;\n\t return collection;\n\t },\n\t\n\t\n\t // ------------------------------------------------------------------------ panel events\n\t /** event map */\n\t events : _.extend( _.clone( _super.prototype.events ), {\n\t 'click .show-selectors-btn' : 'toggleSelectors',\n\t 'click > .controls .prev' : '_clickPrevPage',\n\t 'click > .controls .next' : '_clickNextPage',\n\t 'change > .controls .pages' : '_changePageSelect',\n\t // allow (error) messages to be clicked away\n\t 'click .messages [class$=message]' : 'clearMessages',\n\t }),\n\t\n\t _clickPrevPage : function( ev ){\n\t this.model.contents.fetchPrevPage();\n\t },\n\t\n\t _clickNextPage : function( ev ){\n\t this.model.contents.fetchNextPage();\n\t },\n\t\n\t _changePageSelect : function( ev ){\n\t var page = $( ev.currentTarget ).val();\n\t this.model.contents.fetchPage( page );\n\t },\n\t\n\t /** Toggle and store the deleted visibility and re-render items\n\t * @returns {Boolean} new setting\n\t */\n\t toggleShowDeleted : function( show, options ){\n\t show = ( show !== undefined )?( show ):( !this.model.contents.includeDeleted );\n\t var self = this;\n\t var contents = self.model.contents;\n\t contents.setIncludeDeleted( show, options );\n\t self.trigger( 'show-deleted', show );\n\t\n\t contents.fetchCurrentPage({ renderAll: true });\n\t return show;\n\t },\n\t\n\t /** Toggle and store whether to render explicity hidden contents\n\t * @returns {Boolean} new setting\n\t */\n\t toggleShowHidden : function( show, store, options ){\n\t // console.log( 'toggleShowHidden', show, store );\n\t show = ( show !== undefined )?( show ):( !this.model.contents.includeHidden );\n\t var self = this;\n\t var contents = self.model.contents;\n\t contents.setIncludeHidden( show, options );\n\t self.trigger( 'show-hidden', show );\n\t\n\t contents.fetchCurrentPage({ renderAll: true });\n\t return show;\n\t },\n\t\n\t /** On the first search, if there are no details - load them, then search */\n\t _firstSearch : function( searchFor ){\n\t var self = this;\n\t var inputSelector = '> .controls .search-input';\n\t this.log( 'onFirstSearch', searchFor );\n\t\n\t // if the contents already have enough details to search, search and return now\n\t if( self.model.contents.haveSearchDetails() ){\n\t self.searchItems( searchFor );\n\t return;\n\t }\n\t\n\t // otherwise, load the details progressively here\n\t self.$( inputSelector ).searchInput( 'toggle-loading' );\n\t // set this now so that only results will show during progress\n\t self.searchFor = searchFor;\n\t var xhr = self.model.contents.progressivelyFetchDetails({ silent: true })\n\t .progress( function( response, limit, offset ){\n\t self.renderItems();\n\t self.trigger( 'search:loading-progress', limit, offset );\n\t })\n\t .always( function(){\n\t self.$el.find( inputSelector ).searchInput( 'toggle-loading' );\n\t })\n\t .done( function(){\n\t self.searchItems( searchFor, 'force' );\n\t });\n\t },\n\t\n\t /** clear the search filters and show all views that are normally shown */\n\t clearSearch : function( searchFor ){\n\t var self = this;\n\t if( !self.searchFor ) return self;\n\t //self.log( 'onSearchClear', self );\n\t self.searchFor = '';\n\t self.trigger( 'search:clear', self );\n\t self.$( '> .controls .search-query' ).val( '' );\n\t // NOTE: silent + render prevents collection update event with merge only\n\t // - which causes an empty page due to event handler above\n\t self.model.contents.fetchCurrentPage({ silent: true })\n\t .done( function(){\n\t self.renderItems();\n\t });\n\t return self;\n\t },\n\t\n\t // ........................................................................ error handling\n\t /** Event handler for errors (from the panel, the history, or the history's contents)\n\t * Alternately use two strings for model and xhr to use custom message and title (respectively)\n\t * @param {Model or View} model the (Backbone) source of the error\n\t * @param {XMLHTTPRequest} xhr any ajax obj. assoc. with the error\n\t * @param {Object} options the options map commonly used with bbone ajax\n\t */\n\t errorHandler : function( model, xhr, options ){\n\t //TODO: to mixin or base model\n\t // interrupted ajax or no connection\n\t if( xhr && xhr.status === 0 && xhr.readyState === 0 ){\n\t // return ERROR_MODAL.offlineErrorModal();\n\t // fail silently\n\t return;\n\t }\n\t // otherwise, leave something to report in the console\n\t this.error( model, xhr, options );\n\t // and feedback to a modal\n\t // if sent two strings (and possibly details as 'options'), use those as message and title\n\t if( _.isString( model ) && _.isString( xhr ) ){\n\t var message = model;\n\t var title = xhr;\n\t return ERROR_MODAL.errorModal( message, title, options );\n\t }\n\t // bad gateway\n\t // TODO: possibly to global handler\n\t if( xhr && xhr.status === 502 ){\n\t return ERROR_MODAL.badGatewayErrorModal();\n\t }\n\t return ERROR_MODAL.ajaxErrorModal( model, xhr, options );\n\t },\n\t\n\t /** Remove all messages from the panel. */\n\t clearMessages : function( ev ){\n\t var $target = !_.isUndefined( ev )?\n\t $( ev.currentTarget )\n\t :this.$messages().children( '[class$=\"message\"]' );\n\t $target.fadeOut( this.fxSpeed, function(){\n\t $( this ).remove();\n\t });\n\t return this;\n\t },\n\t\n\t // ........................................................................ scrolling\n\t /** Scrolls the panel to show the content sub-view with the given hid.\n\t * @param {Integer} hid the hid of item to scroll into view\n\t * @returns {HistoryView} the panel\n\t */\n\t scrollToHid : function( hid ){\n\t return this.scrollToItem( _.first( this.viewsWhereModel({ hid: hid }) ) );\n\t },\n\t\n\t // ........................................................................ misc\n\t /** utility for adding -st, -nd, -rd, -th to numbers */\n\t ordinalIndicator : function( number ){\n\t var numStr = number + '';\n\t switch( numStr.charAt( numStr.length - 1 )){\n\t case '1': return numStr + 'st';\n\t case '2': return numStr + 'nd';\n\t case '3': return numStr + 'rd';\n\t default : return numStr + 'th';\n\t }\n\t },\n\t\n\t /** Return a string rep of the history */\n\t toString : function(){\n\t return 'HistoryView(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';\n\t }\n\t});\n\t\n\t\n\t//------------------------------------------------------------------------------ TEMPLATES\n\tHistoryView.prototype.templates = (function(){\n\t\n\t var mainTemplate = BASE_MVC.wrapTemplate([\n\t // temp container\n\t '
            ',\n\t '
            ',\n\t '
              ',\n\t '
              ',\n\t '
              '\n\t ]);\n\t\n\t var controlsTemplate = BASE_MVC.wrapTemplate([\n\t '
              ',\n\t '
              ',\n\t '
              <%- history.name %>
              ',\n\t '
              ',\n\t '
              ',\n\t '
              <%- history.nice_size %>
              ',\n\t\n\t '
              ',\n\t\n\t '
              ',\n\t '<% if( history.deleted && history.purged ){ %>',\n\t '
              ',\n\t _l( 'This history has been purged and deleted' ),\n\t '
              ',\n\t '<% } else if( history.deleted ){ %>',\n\t '
              ',\n\t _l( 'This history has been deleted' ),\n\t '
              ',\n\t '<% } else if( history.purged ){ %>',\n\t '
              ',\n\t _l( 'This history has been purged' ),\n\t '
              ',\n\t '<% } %>',\n\t\n\t '<% if( history.message ){ %>',\n\t // should already be localized\n\t '
              messagesmall\">',\n\t '<%= history.message.text %>',\n\t '
              ',\n\t '<% } %>',\n\t '
              ',\n\t\n\t // add tags and annotations\n\t '
              ',\n\t '
              ',\n\t\n\t '
              ',\n\t '
              ',\n\t '
              ',\n\t\n\t '
              ',\n\t '
              ',\n\t '',\n\t '',\n\t '
              ',\n\t '
              ',\n\t '
              ',\n\t '
              ',\n\t '
              ',\n\t '
              '\n\t ], 'history' );\n\t\n\t var paginationTemplate = BASE_MVC.wrapTemplate([\n\t '',\n\t '',\n\t '',\n\t ], 'pages' );\n\t\n\t return _.extend( _.clone( _super.prototype.templates ), {\n\t el : mainTemplate,\n\t controls : controlsTemplate,\n\t pagination : paginationTemplate,\n\t });\n\t}());\n\t\n\t\n\t//==============================================================================\n\t return {\n\t HistoryView: HistoryView\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(2), __webpack_require__(1)))\n\n/***/ },\n/* 116 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(_) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(79),\n\t __webpack_require__(110),\n\t __webpack_require__(6),\n\t __webpack_require__(5)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( PopupMenu, historyCopyDialog, BASE_MVC, _l ){\n\t\n\t'use strict';\n\t\n\t// ============================================================================\n\tvar menu = [\n\t {\n\t html : _l( 'History Lists' ),\n\t header : true\n\t },\n\t {\n\t html : _l( 'Saved Histories' ),\n\t href : 'history/list',\n\t },\n\t {\n\t html : _l( 'Histories Shared with Me' ),\n\t href : 'history/list_shared'\n\t },\n\t\n\t {\n\t html : _l( 'History Actions' ),\n\t header : true,\n\t anon : true\n\t },\n\t {\n\t html : _l( 'Create New' ),\n\t func : function(){ Galaxy.currHistoryPanel.createNewHistory(); }\n\t },\n\t {\n\t html : _l( 'Copy History' ),\n\t func : function(){\n\t historyCopyDialog( Galaxy.currHistoryPanel.model )\n\t .done( function(){\n\t Galaxy.currHistoryPanel.loadCurrentHistory();\n\t });\n\t },\n\t },\n\t {\n\t html : _l( 'Share or Publish' ),\n\t href : 'history/sharing',\n\t },\n\t {\n\t html : _l( 'Show Structure' ),\n\t href : 'history/display_structured',\n\t anon : true,\n\t },\n\t {\n\t html : _l( 'Extract Workflow' ),\n\t href : 'workflow/build_from_current_history',\n\t },\n\t {\n\t html : _l( 'Delete' ),\n\t anon : true,\n\t func : function() {\n\t if( Galaxy && Galaxy.currHistoryPanel && confirm( _l( 'Really delete the current history?' ) ) ){\n\t galaxy_main.window.location.href = 'history/delete?id=' + Galaxy.currHistoryPanel.model.id;\n\t }\n\t },\n\t },\n\t {\n\t html : _l( 'Delete Permanently' ),\n\t purge : true,\n\t anon : true,\n\t func : function() {\n\t if( Galaxy && Galaxy.currHistoryPanel\n\t && confirm( _l( 'Really delete the current history permanently? This cannot be undone.' ) ) ){\n\t galaxy_main.window.location.href = 'history/delete?purge=True&id=' + Galaxy.currHistoryPanel.model.id;\n\t }\n\t },\n\t },\n\t\n\t\n\t {\n\t html : _l( 'Dataset Actions' ),\n\t header : true,\n\t anon : true\n\t },\n\t {\n\t html : _l( 'Copy Datasets' ),\n\t href : 'dataset/copy_datasets',\n\t },\n\t {\n\t html : _l( 'Dataset Security' ),\n\t href : 'root/history_set_default_permissions',\n\t },\n\t {\n\t html : _l( 'Resume Paused Jobs' ),\n\t href : 'history/resume_paused_jobs?current=True',\n\t anon : true,\n\t },\n\t {\n\t html : _l( 'Collapse Expanded Datasets' ),\n\t func : function(){ Galaxy.currHistoryPanel.collapseAll(); }\n\t },\n\t {\n\t html : _l( 'Unhide Hidden Datasets' ),\n\t anon : true,\n\t func : function(){ Galaxy.currHistoryPanel.unhideHidden(); }\n\t },\n\t {\n\t html : _l( 'Delete Hidden Datasets' ),\n\t anon : true,\n\t func : function(){ Galaxy.currHistoryPanel.deleteHidden(); }\n\t },\n\t {\n\t html : _l( 'Purge Deleted Datasets' ),\n\t confirm : _l( 'Really delete all deleted datasets permanently? This cannot be undone.' ),\n\t href : 'history/purge_deleted_datasets',\n\t purge : true,\n\t anon : true,\n\t },\n\t\n\t {\n\t html : _l( 'Downloads' ),\n\t header : true\n\t },\n\t {\n\t html : _l( 'Export Tool Citations' ),\n\t href : 'history/citations',\n\t anon : true,\n\t },\n\t {\n\t html : _l( 'Export History to File' ),\n\t href : 'history/export_archive?preview=True',\n\t anon : true,\n\t },\n\t\n\t {\n\t html : _l( 'Other Actions' ),\n\t header : true\n\t },\n\t {\n\t html : _l( 'Import from File' ),\n\t href : 'history/import_archive',\n\t }\n\t];\n\t\n\tfunction buildMenu( isAnon, purgeAllowed, urlRoot ){\n\t return _.clone( menu ).filter( function( menuOption ){\n\t if( isAnon && !menuOption.anon ){\n\t return false;\n\t }\n\t if( !purgeAllowed && menuOption.purge ){\n\t return false;\n\t }\n\t\n\t //TODO:?? hard-coded galaxy_main\n\t if( menuOption.href ){\n\t menuOption.href = urlRoot + menuOption.href;\n\t menuOption.target = 'galaxy_main';\n\t }\n\t\n\t if( menuOption.confirm ){\n\t menuOption.func = function(){\n\t if( confirm( menuOption.confirm ) ){\n\t galaxy_main.location = menuOption.href;\n\t }\n\t };\n\t }\n\t return true;\n\t });\n\t}\n\t\n\tvar create = function( $button, options ){\n\t options = options || {};\n\t var isAnon = options.anonymous === undefined? true : options.anonymous,\n\t purgeAllowed = options.purgeAllowed || false,\n\t menu = buildMenu( isAnon, purgeAllowed, Galaxy.root );\n\t //console.debug( 'menu:', menu );\n\t return new PopupMenu( $button, menu );\n\t};\n\t\n\t\n\t// ============================================================================\n\t return create;\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(2)))\n\n/***/ },\n/* 117 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, $, _) {/**\n\t * Renders tabs e.g. used in the Charts editor, behaves similar to repeat and section rendering\n\t */\n\t!(__WEBPACK_AMD_DEFINE_ARRAY__ = [ __webpack_require__(4) ], __WEBPACK_AMD_DEFINE_RESULT__ = function( Utils ) {\n\tvar View = Backbone.View.extend({\n\t initialize : function( options ) {\n\t var self = this;\n\t this.visible = false;\n\t this.$nav = null;\n\t this.$content = null;\n\t this.first_tab = null;\n\t this.current_id = null;\n\t this.list = {};\n\t this.options = Utils.merge( options, {\n\t title_new : '',\n\t operations : null,\n\t onnew : null,\n\t max : null,\n\t onchange : null\n\t });\n\t this.setElement( $( this._template( this.options ) ) );\n\t this.$nav = this.$( '.tab-navigation' );\n\t this.$content = this.$( '.tab-content' );\n\t this.$operations = this.$nav.find( '.tab-operations' );\n\t\n\t // Renders tab operations\n\t if ( this.options.operations ) {\n\t $.each( this.options.operations, function( name, item ) {\n\t item.$el.prop( 'id', name );\n\t self.$operations.append( item.$el );\n\t });\n\t }\n\t\n\t // Allows user to add new tabs\n\t this.options.onnew && this.$nav.append( $( this._template_tab_new( this.options ) )\n\t .tooltip( { title: 'Add a new tab', placement: 'bottom', container: self.$el } )\n\t .on( 'click', function( e ) { self.options.onnew() } )\n\t );\n\t this.$tabnew = this.$nav.find( '.tab-new' );\n\t\n\t // Remove all tooltips on click\n\t this.$el.on( 'click', function() { $( '.tooltip' ).hide() } );\n\t },\n\t\n\t /** Returns current number of tabs */\n\t size: function() {\n\t return _.size( this.list );\n\t },\n\t\n\t /** Returns tab id for currently shown tab */\n\t current: function() {\n\t return this.$el.find( '.tab-pane.active' ).attr( 'id' );\n\t },\n\t\n\t /** Adds a new tab */\n\t add: function( options ) {\n\t var self = this;\n\t var id = options.id;\n\t var $tab_title = $( this._template_tab( options ) );\n\t var $tab_content = $( '
              ' ).attr( 'id', options.id ).addClass( 'tab-pane' );\n\t\n\t // hide new tab if maximum number of tabs has been reached\n\t this.list[ id ] = true;\n\t if ( this.options.max && this.size() >= this.options.max ) {\n\t this.$tabnew.hide();\n\t }\n\t\n\t // insert tab before new tab or as last tab\n\t if ( this.options.onnew ) {\n\t this.$tabnew.before( $tab_title );\n\t } else {\n\t this.$nav.append( $tab_title );\n\t }\n\t\n\t // assing delete callback if provided\n\t if ( options.ondel ) {\n\t $tab_title.find( '.tab-delete' ).tooltip( { title: 'Delete this tab', placement: 'bottom', container: self.$el } )\n\t .on( 'click', function() { options.ondel() } );\n\t } else {\n\t $tab_title.tooltip( { title: options.tooltip, placement: 'bottom', container: self.$el } );\n\t }\n\t $tab_title.on( 'click', function( e ) {\n\t e.preventDefault();\n\t options.onclick ? options.onclick() : self.show( id );\n\t });\n\t this.$content.append( $tab_content.append( options.$el ) );\n\t\n\t // assign current/first tab\n\t if ( this.size() == 1 ) {\n\t $tab_title.addClass( 'active' );\n\t $tab_content.addClass( 'active' );\n\t this.first_tab = id;\n\t }\n\t if ( !this.current_id ) {\n\t this.current_id = id;\n\t }\n\t },\n\t\n\t /** Delete tab */\n\t del: function( id ) {\n\t this.$( '#tab-' + id ).remove();\n\t this.$( '#' + id ).remove();\n\t this.first_tab = this.first_tab == id ? null : this.first_tab;\n\t this.first_tab != null && this.show( this.first_tab );\n\t this.list[ id ] && delete this.list[ id ];\n\t if ( this.size() < this.options.max ) {\n\t this.$el.find( '.ui-tabs-new' ).show();\n\t }\n\t },\n\t\n\t /** Delete all tabs */\n\t delAll: function() {\n\t for ( var id in this.list ) {\n\t this.del( id );\n\t }\n\t },\n\t\n\t /** Show tab view and highlight a tab by id */\n\t show: function( id ){\n\t this.$el.fadeIn( 'fast' );\n\t this.visible = true;\n\t if ( id ) {\n\t this.$( '#tab-' + this.current_id ).removeClass('active' );\n\t this.$( '#' + this.current_id ).removeClass('active' );\n\t this.$( '#tab-' + id ).addClass( 'active' );\n\t this.$( '#' + id ).addClass( 'active' );\n\t this.current_id = id;\n\t }\n\t this.options.onchange && this.options.onchange( id );\n\t },\n\t \n\t /** Hide tab view */\n\t hide: function(){\n\t this.$el.fadeOut( 'fast' );\n\t this.visible = false;\n\t },\n\t\n\t /** Show tab */\n\t showTab: function( id ) {\n\t this.$( '#tab-' + id ).show();\n\t },\n\t\n\t /** hide tab */\n\t hideTab: function( id ) {\n\t this.$( '#tab-' + id ).hide();\n\t },\n\t\n\t /** Hide operation by id */\n\t hideOperation: function( id ) {\n\t this.$nav.find( '#' + id ).hide();\n\t },\n\t\n\t /** Show operation by id */\n\t showOperation: function( id ) {\n\t this.$nav.find( '#' + id ).show();\n\t },\n\t\n\t /** Reassign an operation to a new callback */\n\t setOperation: function( id, callback ) {\n\t this.$nav.find( '#' + id ).off('click').on( 'click', callback );\n\t },\n\t\n\t /** Set/Get title */\n\t title: function( id, new_title ) {\n\t var $el = this.$( '#tab-title-text-' + id );\n\t new_title && $el.html( new_title );\n\t return $el.html();\n\t },\n\t\n\t /** Enumerate titles */\n\t retitle: function( new_title ) {\n\t var index = 0;\n\t for ( var id in this.list ) {\n\t this.title( id, ++index + ': ' + new_title );\n\t }\n\t },\n\t\n\t /** Main template */\n\t _template: function( options ) {\n\t return $( '
              ' ).addClass( 'ui-tabs tabbable tabs-left' )\n\t .append( $( '
            • ' +\n\t '
              ' +\n\t '
              ' +\n\t '
              ' +\n\t '
              You can tell Galaxy to download data from web by entering URL in this box (one per line). You can also directly paste the contents of a file.
              ' +\n\t '',\n '
              ',\n '
              '\n ].join( '' );\n }\n});\n\n//==============================================================================\nreturn {\n CitationView : CitationView,\n CitationListView : CitationListView\n};\n\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/citation/citation-view.js\n ** module id = 28\n ** module chunks = 0 3\n **/","define([\n \"mvc/list/list-item\",\n \"mvc/dataset/dataset-li\",\n \"mvc/base-mvc\",\n \"utils/localization\"\n], function( LIST_ITEM, DATASET_LI, BASE_MVC, _l ){\n\n'use strict';\n//==============================================================================\nvar FoldoutListItemView = LIST_ITEM.FoldoutListItemView,\n ListItemView = LIST_ITEM.ListItemView;\n/** @class Read only view for DatasetCollection.\n */\nvar DCListItemView = FoldoutListItemView.extend(\n/** @lends DCListItemView.prototype */{\n\n className : FoldoutListItemView.prototype.className + \" dataset-collection\",\n id : function(){\n return [ 'dataset_collection', this.model.get( 'id' ) ].join( '-' );\n },\n\n /** override to add linkTarget */\n initialize : function( attributes ){\n this.linkTarget = attributes.linkTarget || '_blank';\n this.hasUser = attributes.hasUser;\n FoldoutListItemView.prototype.initialize.call( this, attributes );\n },\n\n /** event listeners */\n _setUpListeners : function(){\n FoldoutListItemView.prototype._setUpListeners.call( this );\n this.listenTo( this.model, 'change', function( model, options ){\n // if the model has changed deletion status render it entirely\n if( _.has( model.changed, 'deleted' ) ){\n this.render();\n\n // if the model has been decorated after the fact with the element count,\n // render the subtitle where the count is displayed\n } else if( _.has( model.changed, 'element_count' ) ){\n this.$( '> .title-bar .subtitle' ).replaceWith( this._renderSubtitle() );\n }\n });\n },\n\n // ......................................................................... rendering\n /** render a subtitle to show the user what sort of collection this is */\n _renderSubtitle : function(){\n return $( this.templates.subtitle( this.model.toJSON(), this ) );\n },\n\n // ......................................................................... foldout\n /** override to add linktarget to sub-panel */\n _getFoldoutPanelOptions : function(){\n var options = FoldoutListItemView.prototype._getFoldoutPanelOptions.call( this );\n return _.extend( options, {\n linkTarget : this.linkTarget,\n hasUser : this.hasUser\n });\n },\n\n /** override to not catch sub-panel selectors */\n $selector : function(){\n return this.$( '> .selector' );\n },\n\n // ......................................................................... misc\n /** String representation */\n toString : function(){\n var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n return 'DCListItemView(' + modelString + ')';\n }\n});\n\n// ............................................................................ TEMPLATES\n/** underscore templates */\nDCListItemView.prototype.templates = (function(){\n\n var warnings = _.extend( {}, FoldoutListItemView.prototype.templates.warnings, {\n error : BASE_MVC.wrapTemplate([\n // error during index fetch - show error on dataset\n '<% if( model.error ){ %>',\n '
              ',\n _l( 'There was an error getting the data for this collection' ), ': <%- model.error %>',\n '
              ',\n '<% } %>'\n ]),\n purged : BASE_MVC.wrapTemplate([\n '<% if( model.purged ){ %>',\n '
              ',\n _l( 'This collection has been deleted and removed from disk' ),\n '
              ',\n '<% } %>'\n ]),\n deleted : BASE_MVC.wrapTemplate([\n // deleted not purged\n '<% if( model.deleted && !model.purged ){ %>',\n '
              ',\n _l( 'This collection has been deleted' ),\n '
              ',\n '<% } %>'\n ])\n });\n\n // use element identifier\n var titleBarTemplate = BASE_MVC.wrapTemplate([\n '
              ',\n '
              ',\n '<%- collection.element_identifier || collection.name %>',\n '
              ',\n '
              ',\n '
              '\n ], 'collection' );\n\n // use element identifier\n var subtitleTemplate = BASE_MVC.wrapTemplate([\n '
              ',\n '<% var countText = collection.element_count? ( collection.element_count + \" \" ) : \"\"; %>',\n '<% if( collection.collection_type === \"list\" ){ %>',\n _l( 'a list of <%- countText %>datasets' ),\n '<% } else if( collection.collection_type === \"paired\" ){ %>',\n _l( 'a pair of datasets' ),\n '<% } else if( collection.collection_type === \"list:paired\" ){ %>',\n _l( 'a list of <%- countText %>dataset pairs' ),\n '<% } else if( collection.collection_type === \"list:list\" ){ %>',\n _l( 'a list of <%- countText %>dataset lists' ),\n '<% } %>',\n '
              '\n ], 'collection' );\n\n return _.extend( {}, FoldoutListItemView.prototype.templates, {\n warnings : warnings,\n titleBar : titleBarTemplate,\n subtitle : subtitleTemplate\n });\n}());\n\n\n//==============================================================================\n/** @class Read only view for DatasetCollectionElement.\n */\nvar DCEListItemView = ListItemView.extend(\n/** @lends DCEListItemView.prototype */{\n\n /** add the DCE class to the list item */\n className : ListItemView.prototype.className + \" dataset-collection-element\",\n\n /** set up */\n initialize : function( attributes ){\n if( attributes.logger ){ this.logger = this.model.logger = attributes.logger; }\n this.log( 'DCEListItemView.initialize:', attributes );\n ListItemView.prototype.initialize.call( this, attributes );\n },\n\n // ......................................................................... misc\n /** String representation */\n toString : function(){\n var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n return 'DCEListItemView(' + modelString + ')';\n }\n});\n\n// ............................................................................ TEMPLATES\n/** underscore templates */\nDCEListItemView.prototype.templates = (function(){\n\n // use the element identifier here - since that will persist and the user will need it\n var titleBarTemplate = BASE_MVC.wrapTemplate([\n '
              ',\n '
              ',\n '<%- element.element_identifier %>',\n '
              ',\n '
              ',\n '
              '\n ], 'element' );\n\n return _.extend( {}, ListItemView.prototype.templates, {\n titleBar : titleBarTemplate\n });\n}());\n\n\n//==============================================================================\n/** @class Read only view for a DatasetCollectionElement that is also an DatasetAssociation\n * (a dataset contained in a dataset collection).\n */\nvar DatasetDCEListItemView = DATASET_LI.DatasetListItemView.extend(\n/** @lends DatasetDCEListItemView.prototype */{\n\n className : DATASET_LI.DatasetListItemView.prototype.className + \" dataset-collection-element\",\n\n /** set up */\n initialize : function( attributes ){\n if( attributes.logger ){ this.logger = this.model.logger = attributes.logger; }\n this.log( 'DatasetDCEListItemView.initialize:', attributes );\n DATASET_LI.DatasetListItemView.prototype.initialize.call( this, attributes );\n },\n\n /** In this override, only get details if in the ready state.\n * Note: fetch with no 'change' event triggering to prevent automatic rendering.\n */\n _fetchModelDetails : function(){\n var view = this;\n if( view.model.inReadyState() && !view.model.hasDetails() ){\n return view.model.fetch({ silent: true });\n }\n return jQuery.when();\n },\n\n // ......................................................................... misc\n /** String representation */\n toString : function(){\n var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n return 'DatasetDCEListItemView(' + modelString + ')';\n }\n});\n\n// ............................................................................ TEMPLATES\n/** underscore templates */\nDatasetDCEListItemView.prototype.templates = (function(){\n\n // use the element identifier here and not the dataset name\n //TODO:?? can we steal the DCE titlebar?\n var titleBarTemplate = BASE_MVC.wrapTemplate([\n '
              ',\n '',\n '
              ',\n '<%- element.element_identifier %>',\n '
              ',\n '
              '\n ], 'element' );\n\n return _.extend( {}, DATASET_LI.DatasetListItemView.prototype.templates, {\n titleBar : titleBarTemplate\n });\n}());\n\n\n//==============================================================================\n/** @class Read only view for a DatasetCollectionElement that is also a DatasetCollection\n * (a nested DC).\n */\nvar NestedDCDCEListItemView = DCListItemView.extend(\n/** @lends NestedDCDCEListItemView.prototype */{\n\n className : DCListItemView.prototype.className + \" dataset-collection-element\",\n\n /** In this override, add the state as a class for use with state-based CSS */\n _swapNewRender : function( $newRender ){\n DCListItemView.prototype._swapNewRender.call( this, $newRender );\n var state = this.model.get( 'state' ) || 'ok';\n this.$el.addClass( 'state-' + state );\n return this.$el;\n },\n\n // ......................................................................... misc\n /** String representation */\n toString : function(){\n var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n return 'NestedDCDCEListItemView(' + modelString + ')';\n }\n});\n\n\n//==============================================================================\n return {\n DCListItemView : DCListItemView,\n DCEListItemView : DCEListItemView,\n DatasetDCEListItemView : DatasetDCEListItemView,\n NestedDCDCEListItemView : NestedDCDCEListItemView\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/collection/collection-li.js\n ** module id = 29\n ** module chunks = 3\n **/","define([\n \"mvc/dataset/dataset-model\",\n \"mvc/base-mvc\",\n \"utils/localization\"\n], function( DATASET_MODEL, BASE_MVC, _l ){\n'use strict';\n\n//==============================================================================\n/*\nNotes:\n\nTerminology:\n DatasetCollection/DC : a container of datasets or nested DatasetCollections\n Element/DatasetCollectionElement/DCE : an item contained in a DatasetCollection\n HistoryDatasetCollectionAssociation/HDCA: a DatasetCollection contained in a history\n\n\nThis all seems too complex unfortunately:\n\n- Terminology collision between DatasetCollections (DCs) and Backbone Collections.\n- In the DatasetCollections API JSON, DC Elements use a 'Has A' stucture to *contain*\n either a dataset or a nested DC. This would make the hierarchy much taller. I've\n decided to merge the contained JSON with the DC element json - making the 'has a'\n relation into an 'is a' relation. This seems simpler to me and allowed a lot of\n DRY in both models and views, but may make tracking or tracing within these models\n more difficult (since DatasetCollectionElements are now *also* DatasetAssociations\n or DatasetCollections (nested)). This also violates the rule of thumb about\n favoring aggregation over inheritance.\n- Currently, there are three DatasetCollection subclasses: List, Pair, and ListPaired.\n These each should a) be usable on their own, b) be usable in the context of\n nesting within a collection model (at least in the case of ListPaired), and\n c) be usable within the context of other container models (like History or\n LibraryFolder, etc.). I've tried to separate/extract classes in order to\n handle those three situations, but it's proven difficult to do in a simple,\n readable manner.\n- Ideally, histories and libraries would inherit from the same server models as\n dataset collections do since they are (in essence) dataset collections themselves -\n making the whole nested structure simpler. This would be a large, error-prone\n refactoring and migration.\n\nMany of the classes and heirarchy are meant as extension points so, while the\nrelations and flow may be difficult to understand initially, they'll allow us to\nhandle the growth or flux dataset collection in the future (w/o actually implementing\nany YAGNI).\n\n*/\n//_________________________________________________________________________________________________ ELEMENTS\n/** @class mixin for Dataset collection elements.\n * When collection elements are passed from the API, the underlying element is\n * in a sub-object 'object' (IOW, a DCE representing an HDA will have HDA json in element.object).\n * This mixin uses the constructor and parse methods to merge that JSON with the DCE attribtues\n * effectively changing a DCE from a container to a subclass (has a --> is a).\n */\nvar DatasetCollectionElementMixin = {\n\n /** default attributes used by elements in a dataset collection */\n defaults : {\n model_class : 'DatasetCollectionElement',\n element_identifier : null,\n element_index : null,\n element_type : null\n },\n\n /** merge the attributes of the sub-object 'object' into this model */\n _mergeObject : function( attributes ){\n // if we don't preserve and correct ids here, the element id becomes the object id\n // and collision in backbone's _byId will occur and only\n _.extend( attributes, attributes.object, { element_id: attributes.id });\n delete attributes.object;\n return attributes;\n },\n\n /** override to merge this.object into this */\n constructor : function( attributes, options ){\n // console.debug( '\\t DatasetCollectionElement.constructor:', attributes, options );\n attributes = this._mergeObject( attributes );\n this.idAttribute = 'element_id';\n Backbone.Model.apply( this, arguments );\n },\n\n /** when the model is fetched, merge this.object into this */\n parse : function( response, options ){\n var attributes = response;\n attributes = this._mergeObject( attributes );\n return attributes;\n }\n};\n\n/** @class Concrete class of Generic DatasetCollectionElement */\nvar DatasetCollectionElement = Backbone.Model\n .extend( BASE_MVC.LoggableMixin )\n .extend( DatasetCollectionElementMixin )\n .extend({ _logNamespace : 'collections' });\n\n\n//==============================================================================\n/** @class Base/Abstract Backbone collection for Generic DCEs. */\nvar DCECollection = Backbone.Collection.extend( BASE_MVC.LoggableMixin ).extend(\n/** @lends DCECollection.prototype */{\n _logNamespace : 'collections',\n\n model: DatasetCollectionElement,\n\n /** String representation. */\n toString : function(){\n return ([ 'DatasetCollectionElementCollection(', this.length, ')' ].join( '' ));\n }\n});\n\n\n//==============================================================================\n/** @class Backbone model for a dataset collection element that is a dataset (HDA).\n */\nvar DatasetDCE = DATASET_MODEL.DatasetAssociation.extend( BASE_MVC.mixin( DatasetCollectionElementMixin,\n/** @lends DatasetDCE.prototype */{\n\n /** url fn */\n url : function(){\n // won't always be an hda\n if( !this.has( 'history_id' ) ){\n console.warn( 'no endpoint for non-hdas within a collection yet' );\n // (a little silly since this api endpoint *also* points at hdas)\n return Galaxy.root + 'api/datasets';\n }\n return Galaxy.root + 'api/histories/' + this.get( 'history_id' ) + '/contents/' + this.get( 'id' );\n },\n\n defaults : _.extend( {},\n DATASET_MODEL.DatasetAssociation.prototype.defaults,\n DatasetCollectionElementMixin.defaults\n ),\n\n // because all objects have constructors (as this hashmap would even if this next line wasn't present)\n // the constructor in hcontentMixin won't be attached by BASE_MVC.mixin to this model\n // - re-apply manually for now\n /** call the mixin constructor */\n constructor : function( attributes, options ){\n this.debug( '\\t DatasetDCE.constructor:', attributes, options );\n //DATASET_MODEL.DatasetAssociation.prototype.constructor.call( this, attributes, options );\n DatasetCollectionElementMixin.constructor.call( this, attributes, options );\n },\n\n /** Does this model already contain detailed data (as opposed to just summary level data)? */\n hasDetails : function(){\n return this.elements && this.elements.length;\n },\n\n /** String representation. */\n toString : function(){\n var objStr = this.get( 'element_identifier' );\n return ([ 'DatasetDCE(', objStr, ')' ].join( '' ));\n }\n}));\n\n\n//==============================================================================\n/** @class DCECollection of DatasetDCE's (a list of datasets, a pair of datasets).\n */\nvar DatasetDCECollection = DCECollection.extend(\n/** @lends DatasetDCECollection.prototype */{\n model: DatasetDCE,\n\n /** String representation. */\n toString : function(){\n return ([ 'DatasetDCECollection(', this.length, ')' ].join( '' ));\n }\n});\n\n\n//_________________________________________________________________________________________________ COLLECTIONS\n/** @class Backbone model for Dataset Collections.\n * The DC API returns an array of JSON objects under the attribute elements.\n * This model:\n * - removes that array/attribute ('elements') from the model,\n * - creates a bbone collection (of the class defined in the 'collectionClass' attribute),\n * - passes that json onto the bbone collection\n * - caches the bbone collection in this.elements\n */\nvar DatasetCollection = Backbone.Model\n .extend( BASE_MVC.LoggableMixin )\n .extend( BASE_MVC.SearchableModelMixin )\n .extend(/** @lends DatasetCollection.prototype */{\n _logNamespace : 'collections',\n\n /** default attributes for a model */\n defaults : {\n /* 'list', 'paired', or 'list:paired' */\n collection_type : null,\n //??\n deleted : false\n },\n\n /** Which class to use for elements */\n collectionClass : DCECollection,\n\n /** set up: create elements instance var and (on changes to elements) update them */\n initialize : function( model, options ){\n this.debug( this + '(DatasetCollection).initialize:', model, options, this );\n this.elements = this._createElementsModel();\n this.on( 'change:elements', function(){\n this.log( 'change:elements' );\n //TODO: prob. better to update the collection instead of re-creating it\n this.elements = this._createElementsModel();\n });\n },\n\n /** move elements model attribute to full collection */\n _createElementsModel : function(){\n this.debug( this + '._createElementsModel', this.collectionClass, this.get( 'elements' ), this.elements );\n //TODO: same patterns as DatasetCollectionElement _createObjectModel - refactor to BASE_MVC.hasSubModel?\n var elements = this.get( 'elements' ) || [];\n this.unset( 'elements', { silent: true });\n this.elements = new this.collectionClass( elements );\n //this.debug( 'collectionClass:', this.collectionClass + '', this.elements );\n return this.elements;\n },\n\n // ........................................................................ common queries\n /** pass the elements back within the model json when this is serialized */\n toJSON : function(){\n var json = Backbone.Model.prototype.toJSON.call( this );\n if( this.elements ){\n json.elements = this.elements.toJSON();\n }\n return json;\n },\n\n /** Is this collection in a 'ready' state no processing (for the collection) is left\n * to do on the server.\n */\n inReadyState : function(){\n var populated = this.get( 'populated' );\n return ( this.isDeletedOrPurged() || populated );\n },\n\n //TODO:?? the following are the same interface as DatasetAssociation - can we combine?\n /** Does the DC contain any elements yet? Is a fetch() required? */\n hasDetails : function(){\n return this.elements.length !== 0;\n },\n\n /** Given the filters, what models in this.elements would be returned? */\n getVisibleContents : function( filters ){\n // filters unused for now\n return this.elements;\n },\n\n // ........................................................................ ajax\n /** override to use actual Dates objects for create/update times */\n parse : function( response, options ){\n var parsed = Backbone.Model.prototype.parse.call( this, response, options );\n if( parsed.create_time ){\n parsed.create_time = new Date( parsed.create_time );\n }\n if( parsed.update_time ){\n parsed.update_time = new Date( parsed.update_time );\n }\n return parsed;\n },\n\n /** save this dataset, _Mark_ing it as deleted (just a flag) */\n 'delete' : function( options ){\n if( this.get( 'deleted' ) ){ return jQuery.when(); }\n return this.save( { deleted: true }, options );\n },\n /** save this dataset, _Mark_ing it as undeleted */\n undelete : function( options ){\n if( !this.get( 'deleted' ) || this.get( 'purged' ) ){ return jQuery.when(); }\n return this.save( { deleted: false }, options );\n },\n\n /** Is this collection deleted or purged? */\n isDeletedOrPurged : function(){\n return ( this.get( 'deleted' ) || this.get( 'purged' ) );\n },\n\n // ........................................................................ searchable\n /** searchable attributes for collections */\n searchAttributes : [\n 'name'\n ],\n\n // ........................................................................ misc\n /** String representation */\n toString : function(){\n var idAndName = [ this.get( 'id' ), this.get( 'name' ) || this.get( 'element_identifier' ) ];\n return 'DatasetCollection(' + ( idAndName.join(',') ) + ')';\n }\n});\n\n\n//==============================================================================\n/** Model for a DatasetCollection containing datasets (non-nested).\n */\nvar ListDatasetCollection = DatasetCollection.extend(\n/** @lends ListDatasetCollection.prototype */{\n\n /** override since we know the collection will only contain datasets */\n collectionClass : DatasetDCECollection,\n\n /** String representation. */\n toString : function(){ return 'List' + DatasetCollection.prototype.toString.call( this ); }\n});\n\n\n//==============================================================================\n/** Model for a DatasetCollection containing fwd/rev datasets (a list of 2).\n */\nvar PairDatasetCollection = ListDatasetCollection.extend(\n/** @lends PairDatasetCollection.prototype */{\n\n /** String representation. */\n toString : function(){ return 'Pair' + DatasetCollection.prototype.toString.call( this ); }\n});\n\n\n//_________________________________________________________________________________________________ NESTED COLLECTIONS\n// this is where things get weird, man. Weird.\n//TODO: it might be possible to compact all the following...I think.\n//==============================================================================\n/** @class Backbone model for a Generic DatasetCollectionElement that is also a DatasetCollection\n * (a nested collection). Currently only list:paired.\n */\nvar NestedDCDCE = DatasetCollection.extend( BASE_MVC.mixin( DatasetCollectionElementMixin,\n/** @lends NestedDCDCE.prototype */{\n\n // because all objects have constructors (as this hashmap would even if this next line wasn't present)\n // the constructor in hcontentMixin won't be attached by BASE_MVC.mixin to this model\n // - re-apply manually it now\n /** call the mixin constructor */\n constructor : function( attributes, options ){\n this.debug( '\\t NestedDCDCE.constructor:', attributes, options );\n DatasetCollectionElementMixin.constructor.call( this, attributes, options );\n },\n\n /** String representation. */\n toString : function(){\n var objStr = ( this.object )?( '' + this.object ):( this.get( 'element_identifier' ) );\n return ([ 'NestedDCDCE(', objStr, ')' ].join( '' ));\n }\n}));\n\n\n//==============================================================================\n/** @class Backbone collection containing Generic NestedDCDCE's (nested dataset collections).\n */\nvar NestedDCDCECollection = DCECollection.extend(\n/** @lends NestedDCDCECollection.prototype */{\n\n /** This is a collection of nested collections */\n model: NestedDCDCE,\n\n /** String representation. */\n toString : function(){\n return ([ 'NestedDCDCECollection(', this.length, ')' ].join( '' ));\n }\n});\n\n\n//==============================================================================\n/** @class Backbone model for a paired dataset collection within a list:paired dataset collection.\n */\nvar NestedPairDCDCE = PairDatasetCollection.extend( BASE_MVC.mixin( DatasetCollectionElementMixin,\n/** @lends NestedPairDCDCE.prototype */{\n//TODO:?? possibly rename to NestedDatasetCollection?\n\n // because all objects have constructors (as this hashmap would even if this next line wasn't present)\n // the constructor in hcontentMixin won't be attached by BASE_MVC.mixin to this model\n // - re-apply manually it now\n /** This is both a collection and a collection element - call the constructor */\n constructor : function( attributes, options ){\n this.debug( '\\t NestedPairDCDCE.constructor:', attributes, options );\n //DatasetCollection.constructor.call( this, attributes, options );\n DatasetCollectionElementMixin.constructor.call( this, attributes, options );\n },\n\n /** String representation. */\n toString : function(){\n var objStr = ( this.object )?( '' + this.object ):( this.get( 'element_identifier' ) );\n return ([ 'NestedPairDCDCE(', objStr, ')' ].join( '' ));\n }\n}));\n\n\n//==============================================================================\n/** @class Backbone collection for a backbone collection containing paired dataset collections.\n */\nvar NestedPairDCDCECollection = NestedDCDCECollection.extend(\n/** @lends PairDCDCECollection.prototype */{\n\n /** We know this collection is composed of only nested pair collections */\n model: NestedPairDCDCE,\n\n /** String representation. */\n toString : function(){\n return ([ 'NestedPairDCDCECollection(', this.length, ')' ].join( '' ));\n }\n});\n\n\n//==============================================================================\n/** @class Backbone Model for a DatasetCollection (list) that contains DatasetCollections (pairs).\n */\nvar ListPairedDatasetCollection = DatasetCollection.extend(\n/** @lends ListPairedDatasetCollection.prototype */{\n\n /** list:paired is the only collection that itself contains collections */\n collectionClass : NestedPairDCDCECollection,\n\n /** String representation. */\n toString : function(){\n return ([ 'ListPairedDatasetCollection(', this.get( 'name' ), ')' ].join( '' ));\n }\n});\n\n\n//==============================================================================\n/** @class Backbone model for a list dataset collection within a list:list dataset collection. */\nvar NestedListDCDCE = ListDatasetCollection.extend( BASE_MVC.mixin( DatasetCollectionElementMixin,\n/** @lends NestedListDCDCE.prototype */{\n\n /** This is both a collection and a collection element - call the constructor */\n constructor : function( attributes, options ){\n this.debug( '\\t NestedListDCDCE.constructor:', attributes, options );\n DatasetCollectionElementMixin.constructor.call( this, attributes, options );\n },\n\n /** String representation. */\n toString : function(){\n var objStr = ( this.object )?( '' + this.object ):( this.get( 'element_identifier' ) );\n return ([ 'NestedListDCDCE(', objStr, ')' ].join( '' ));\n }\n}));\n\n\n//==============================================================================\n/** @class Backbone collection containing list dataset collections. */\nvar NestedListDCDCECollection = NestedDCDCECollection.extend({\n\n /** We know this collection is composed of only nested pair collections */\n model: NestedListDCDCE,\n\n /** String representation. */\n toString : function(){\n return ([ 'NestedListDCDCECollection(', this.length, ')' ].join( '' ));\n }\n});\n\n\n//==============================================================================\n/** @class Backbone Model for a DatasetCollection (list) that contains other lists. */\nvar ListOfListsDatasetCollection = DatasetCollection.extend({\n\n /** list:paired is the only collection that itself contains collections */\n collectionClass : NestedListDCDCECollection,\n\n /** String representation. */\n toString : function(){\n return ([ 'ListOfListsDatasetCollection(', this.get( 'name' ), ')' ].join( '' ));\n }\n});\n\n\n//==============================================================================\n return {\n ListDatasetCollection : ListDatasetCollection,\n PairDatasetCollection : PairDatasetCollection,\n ListPairedDatasetCollection : ListPairedDatasetCollection,\n ListOfListsDatasetCollection: ListOfListsDatasetCollection\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/collection/collection-model.js\n ** module id = 30\n ** module chunks = 3\n **/","\ndefine([\n \"mvc/history/hdca-model\",\n \"mvc/dataset/states\",\n \"mvc/base-mvc\",\n \"mvc/ui/ui-modal\",\n \"utils/natural-sort\",\n \"utils/localization\",\n \"ui/hoverhighlight\"\n], function( HDCA, STATES, BASE_MVC, UI_MODAL, naturalSort, _l ){\n\n'use strict';\n\nvar logNamespace = 'collections';\n/*==============================================================================\nTODO:\n use proper Element model and not just json\n straighten out createFn, collection.createHDCA\n possibly stop using modals for this\n It would be neat to do a drag and drop\n\n==============================================================================*/\n/** A view for both DatasetDCEs and NestedDCDCEs\n * (things that implement collection-model:DatasetCollectionElementMixin)\n */\nvar DatasetCollectionElementView = Backbone.View.extend( BASE_MVC.LoggableMixin ).extend({\n _logNamespace : logNamespace,\n\n//TODO: use proper class (DatasetDCE or NestedDCDCE (or the union of both))\n tagName : 'li',\n className : 'collection-element',\n\n initialize : function( attributes ){\n this.element = attributes.element || {};\n this.selected = attributes.selected || false;\n },\n\n render : function(){\n this.$el\n .attr( 'data-element-id', this.element.id )\n .attr( 'draggable', true )\n .html( this.template({ element: this.element }) );\n if( this.selected ){\n this.$el.addClass( 'selected' );\n }\n return this;\n },\n\n //TODO: lots of unused space in the element - possibly load details and display them horiz.\n template : _.template([\n '',\n '<%- element.name %>',\n '',\n '',\n ].join('')),\n\n /** select this element and pub */\n select : function( toggle ){\n this.$el.toggleClass( 'selected', toggle );\n this.trigger( 'select', {\n source : this,\n selected : this.$el.hasClass( 'selected' )\n });\n },\n\n /** animate the removal of this element and pub */\n discard : function(){\n var view = this,\n parentWidth = this.$el.parent().width();\n this.$el.animate({ 'margin-right' : parentWidth }, 'fast', function(){\n view.trigger( 'discard', {\n source : view\n });\n view.destroy();\n });\n },\n\n /** remove the DOM and any listeners */\n destroy : function(){\n this.off();\n this.$el.remove();\n },\n\n events : {\n 'click' : '_click',\n 'click .name' : '_clickName',\n 'click .discard': '_clickDiscard',\n\n 'dragstart' : '_dragstart',\n 'dragend' : '_dragend',\n 'dragover' : '_sendToParent',\n 'drop' : '_sendToParent'\n },\n\n /** select when the li is clicked */\n _click : function( ev ){\n ev.stopPropagation();\n this.select( ev );\n },\n\n /** rename a pair when the name is clicked */\n _clickName : function( ev ){\n ev.stopPropagation();\n ev.preventDefault();\n var promptString = [ _l( 'Enter a new name for the element' ), ':\\n(',\n _l( 'Note that changing the name here will not rename the dataset' ), ')' ].join( '' ),\n response = prompt( _l( 'Enter a new name for the element' ) + ':', this.element.name );\n if( response ){\n this.element.name = response;\n this.render();\n }\n //TODO: cancelling with ESC leads to closure of the creator...\n },\n\n /** discard when the discard button is clicked */\n _clickDiscard : function( ev ){\n ev.stopPropagation();\n this.discard();\n },\n\n /** dragging pairs for re-ordering */\n _dragstart : function( ev ){\n if( ev.originalEvent ){ ev = ev.originalEvent; }\n ev.dataTransfer.effectAllowed = 'move';\n ev.dataTransfer.setData( 'text/plain', JSON.stringify( this.element ) );\n\n this.$el.addClass( 'dragging' );\n this.$el.parent().trigger( 'collection-element.dragstart', [ this ] );\n },\n\n /** dragging for re-ordering */\n _dragend : function( ev ){\n this.$el.removeClass( 'dragging' );\n this.$el.parent().trigger( 'collection-element.dragend', [ this ] );\n },\n\n /** manually bubble up an event to the parent/container */\n _sendToParent : function( ev ){\n this.$el.parent().trigger( ev );\n },\n\n /** string rep */\n toString : function(){\n return 'DatasetCollectionElementView()';\n }\n});\n\n\n// ============================================================================\n/** An interface for building collections.\n */\nvar ListCollectionCreator = Backbone.View.extend( BASE_MVC.LoggableMixin ).extend({\n _logNamespace : logNamespace,\n\n /** the class used to display individual elements */\n elementViewClass : DatasetCollectionElementView,\n /** the class this creator will create and save */\n collectionClass : HDCA.HistoryListDatasetCollection,\n className : 'list-collection-creator collection-creator flex-row-container',\n\n /** minimum number of valid elements to start with in order to build a collection of this type */\n minElements : 1,\n\n defaultAttributes : {\n//TODO: remove - use new collectionClass().save()\n /** takes elements and creates the proper collection - returns a promise */\n creationFn : function(){ throw new TypeError( 'no creation fn for creator' ); },\n /** fn to call when the collection is created (scoped to this) */\n oncreate : function(){},\n /** fn to call when the cancel button is clicked (scoped to this) - if falsy, no btn is displayed */\n oncancel : function(){},\n /** distance from list edge to begin autoscrolling list */\n autoscrollDist : 24,\n /** Color passed to hoverhighlight */\n highlightClr : 'rgba( 64, 255, 255, 1.0 )'\n },\n\n /** set up initial options, instance vars, behaviors */\n initialize : function( attributes ){\n this.metric( 'ListCollectionCreator.initialize', attributes );\n var creator = this;\n _.each( this.defaultAttributes, function( value, key ){\n value = attributes[ key ] || value;\n creator[ key ] = value;\n });\n\n /** unordered, original list - cache to allow reversal */\n creator.initialElements = attributes.elements || [];\n\n this._instanceSetUp();\n this._elementsSetUp();\n this._setUpBehaviors();\n },\n\n /** set up instance vars */\n _instanceSetUp : function(){\n /** Ids of elements that have been selected by the user - to preserve over renders */\n this.selectedIds = {};\n /** DOM elements currently being dragged */\n this.$dragging = null;\n /** Used for blocking UI events during ajax/operations (don't post twice) */\n this.blocking = false;\n },\n\n // ------------------------------------------------------------------------ process raw list\n /** set up main data */\n _elementsSetUp : function(){\n //this.debug( '-- _dataSetUp' );\n /** a list of invalid elements and the reasons they aren't valid */\n this.invalidElements = [];\n//TODO: handle fundamental problem of syncing DOM, views, and list here\n /** data for list in progress */\n this.workingElements = [];\n /** views for workingElements */\n this.elementViews = [];\n\n // copy initial list, sort, add ids if needed\n this.workingElements = this.initialElements.slice( 0 );\n this._ensureElementIds();\n this._validateElements();\n this._mangleDuplicateNames();\n this._sortElements();\n },\n\n /** add ids to dataset objs in initial list if none */\n _ensureElementIds : function(){\n this.workingElements.forEach( function( element ){\n if( !element.hasOwnProperty( 'id' ) ){\n element.id = _.uniqueId();\n }\n });\n return this.workingElements;\n },\n\n /** separate working list into valid and invalid elements for this collection */\n _validateElements : function(){\n var creator = this,\n existingNames = {};\n creator.invalidElements = [];\n\n this.workingElements = this.workingElements.filter( function( element ){\n var problem = creator._isElementInvalid( element );\n if( problem ){\n creator.invalidElements.push({\n element : element,\n text : problem\n });\n }\n return !problem;\n });\n return this.workingElements;\n },\n\n /** describe what is wrong with a particular element if anything */\n _isElementInvalid : function( element ){\n if( element.history_content_type !== 'dataset' ){\n return _l( \"is not a dataset\" );\n }\n if( element.state !== STATES.OK ){\n if( _.contains( STATES.NOT_READY_STATES, element.state ) ){\n return _l( \"hasn't finished running yet\" );\n }\n return _l( \"has errored, is paused, or is not accessible\" );\n }\n if( element.deleted || element.purged ){\n return _l( \"has been deleted or purged\" );\n }\n return null;\n },\n\n /** mangle duplicate names using a mac-like '(counter)' addition to any duplicates */\n _mangleDuplicateNames : function(){\n var SAFETY = 900,\n counter = 1,\n existingNames = {};\n this.workingElements.forEach( function( element ){\n var currName = element.name;\n while( existingNames.hasOwnProperty( currName ) ){\n currName = element.name + ' (' + counter + ')';\n counter += 1;\n if( counter >= SAFETY ){\n throw new Error( 'Safety hit in while loop - thats impressive' );\n }\n }\n element.name = currName;\n existingNames[ element.name ] = true;\n });\n },\n\n /** sort a list of elements */\n _sortElements : function( list ){\n // // currently only natural sort by name\n // this.workingElements.sort( function( a, b ){ return naturalSort( a.name, b.name ); });\n // return this.workingElements;\n },\n\n // ------------------------------------------------------------------------ rendering\n // templates : ListCollectionCreator.templates,\n /** render the entire interface */\n render : function( speed, callback ){\n //this.debug( '-- _render' );\n if( this.workingElements.length < this.minElements ){\n return this._renderInvalid( speed, callback );\n }\n\n this.$el.empty().html( this.templates.main() );\n this._renderHeader( speed );\n this._renderMiddle( speed );\n this._renderFooter( speed );\n this._addPluginComponents();\n this.$( '.collection-name' ).focus();\n this.trigger( 'rendered', this );\n return this;\n },\n\n\n /** render a simplified interface aimed at telling the user why they can't move forward */\n _renderInvalid : function( speed, callback ){\n //this.debug( '-- _render' );\n this.$el.empty().html( this.templates.invalidInitial({\n problems: this.invalidElements,\n elements: this.workingElements,\n }));\n if( typeof this.oncancel === 'function' ){\n this.$( '.cancel-create.btn' ).show();\n }\n this.trigger( 'rendered', this );\n return this;\n },\n\n /** render the header section */\n _renderHeader : function( speed, callback ){\n var $header = this.$( '.header' ).empty().html( this.templates.header() )\n .find( '.help-content' ).prepend( $( this.templates.helpContent() ) );\n //TODO: should only show once despite calling _renderHeader again\n if( this.invalidElements.length ){\n this._invalidElementsAlert();\n }\n return $header;\n },\n\n /** render the middle including the elements */\n _renderMiddle : function( speed, callback ){\n var $middle = this.$( '.middle' ).empty().html( this.templates.middle() );\n this._renderList( speed );\n return $middle;\n },\n\n /** render the footer, completion controls, and cancel controls */\n _renderFooter : function( speed, callback ){\n var $footer = this.$( '.footer' ).empty().html( this.templates.footer() );\n if( typeof this.oncancel === 'function' ){\n this.$( '.cancel-create.btn' ).show();\n }\n return $footer;\n },\n\n /** add any jQuery/bootstrap/custom plugins to elements rendered */\n _addPluginComponents : function(){\n this.$( '.help-content i' ).hoverhighlight( '.collection-creator', this.highlightClr );\n },\n\n /** build and show an alert describing any elements that could not be included due to problems */\n _invalidElementsAlert : function(){\n this._showAlert( this.templates.invalidElements({ problems: this.invalidElements }), 'alert-warning' );\n },\n\n /** add (or clear if clear is truthy) a validation warning to the DOM element described in what */\n _validationWarning : function( what, clear ){\n var VALIDATION_CLASS = 'validation-warning';\n if( what === 'name' ){\n what = this.$( '.collection-name' ).add( this.$( '.collection-name-prompt' ) );\n this.$( '.collection-name' ).focus().select();\n }\n if( clear ){\n what = what || this.$( '.' + VALIDATION_CLASS );\n what.removeClass( VALIDATION_CLASS );\n } else {\n what.addClass( VALIDATION_CLASS );\n }\n },\n\n _disableNameAndCreate : function( disable ){\n disable = !_.isUndefined( disable )? disable : true;\n if( disable ){\n this.$( '.collection-name' ).prop( 'disabled', true );\n this.$( '.create-collection' ).toggleClass( 'disabled', true );\n // } else {\n // this.$( '.collection-name' ).prop( 'disabled', false );\n // this.$( '.create-collection' ).removeClass( 'disable' );\n }\n },\n\n // ------------------------------------------------------------------------ rendering elements\n /** conv. to the main list display DOM */\n $list : function(){\n return this.$( '.collection-elements' );\n },\n\n /** show or hide the clear selected control based on the num of selected elements */\n _renderClearSelected : function(){\n if( _.size( this.selectedIds ) ){\n this.$( '.collection-elements-controls > .clear-selected' ).show();\n } else {\n this.$( '.collection-elements-controls > .clear-selected' ).hide();\n }\n },\n\n /** render the elements in order (or a warning if no elements found) */\n _renderList : function( speed, callback ){\n //this.debug( '-- _renderList' );\n var creator = this,\n $tmp = jQuery( '
              ' ),\n $list = creator.$list();\n\n _.each( this.elementViews, function( view ){\n view.destroy();\n creator.removeElementView( view );\n });\n\n // if( !this.workingElements.length ){\n // this._renderNoValidElements();\n // return;\n // }\n\n creator.workingElements.forEach( function( element ){\n var elementView = creator._createElementView( element );\n $tmp.append( elementView.$el );\n });\n\n creator._renderClearSelected();\n $list.empty().append( $tmp.children() );\n _.invoke( creator.elementViews, 'render' );\n\n if( $list.height() > $list.css( 'max-height' ) ){\n $list.css( 'border-width', '1px 0px 1px 0px' );\n } else {\n $list.css( 'border-width', '0px' );\n }\n },\n\n /** create an element view, cache in elementViews, set up listeners, and return */\n _createElementView : function( element ){\n var elementView = new this.elementViewClass({\n//TODO: use non-generic class or not all\n // model : COLLECTION.DatasetDCE( element )\n element : element,\n selected: _.has( this.selectedIds, element.id )\n });\n this.elementViews.push( elementView );\n this._listenToElementView( elementView );\n return elementView;\n },\n\n /** listen to any element events */\n _listenToElementView : function( view ){\n var creator = this;\n creator.listenTo( view, {\n select : function( data ){\n var element = data.source.element;\n if( data.selected ){\n creator.selectedIds[ element.id ] = true;\n } else {\n delete creator.selectedIds[ element.id ];\n }\n creator.trigger( 'elements:select', data );\n },\n discard : function( data ){\n creator.trigger( 'elements:discard', data );\n }\n });\n },\n\n /** add a new element view based on the json in element */\n addElementView : function( element ){\n//TODO: workingElements is sorted, add element in appropo index\n // add element, sort elements, find element index\n // var view = this._createElementView( element );\n // return view;\n },\n\n /** stop listening to view and remove from caches */\n removeElementView : function( view ){\n delete this.selectedIds[ view.element.id ];\n this._renderClearSelected();\n\n this.elementViews = _.without( this.elementViews, view );\n this.stopListening( view );\n },\n\n /** render a message in the list that no elements remain to create a collection */\n _renderNoElementsLeft : function(){\n this._disableNameAndCreate( true );\n this.$( '.collection-elements' ).append( this.templates.noElementsLeft() );\n },\n\n // /** render a message in the list that no valid elements were found to create a collection */\n // _renderNoValidElements : function(){\n // this._disableNameAndCreate( true );\n // this.$( '.collection-elements' ).append( this.templates.noValidElements() );\n // },\n\n // ------------------------------------------------------------------------ API\n /** convert element into JSON compatible with the collections API */\n _elementToJSON : function( element ){\n // return element.toJSON();\n return element;\n },\n\n /** create the collection via the API\n * @returns {jQuery.xhr Object} the jquery ajax request\n */\n createList : function( name ){\n if( !this.workingElements.length ){\n var message = _l( 'No valid elements for final list' ) + '. ';\n message += '' + _l( 'Cancel' ) + ' ';\n message += _l( 'or' );\n message += ' ' + _l( 'start over' ) + '.';\n this._showAlert( message );\n return;\n }\n\n var creator = this,\n elements = this.workingElements.map( function( element ){\n return creator._elementToJSON( element );\n });\n\n creator.blocking = true;\n return creator.creationFn( elements, name )\n .always( function(){\n creator.blocking = false;\n })\n .fail( function( xhr, status, message ){\n creator.trigger( 'error', {\n xhr : xhr,\n status : status,\n message : _l( 'An error occurred while creating this collection' )\n });\n })\n .done( function( response, message, xhr ){\n creator.trigger( 'collection:created', response, message, xhr );\n creator.metric( 'collection:created', response );\n if( typeof creator.oncreate === 'function' ){\n creator.oncreate.call( this, response, message, xhr );\n }\n });\n },\n\n // ------------------------------------------------------------------------ events\n /** set up event handlers on self */\n _setUpBehaviors : function(){\n this.on( 'error', this._errorHandler );\n\n this.once( 'rendered', function(){\n this.trigger( 'rendered:initial', this );\n });\n\n this.on( 'elements:select', function( data ){\n this._renderClearSelected();\n });\n\n this.on( 'elements:discard', function( data ){\n var element = data.source.element;\n this.removeElementView( data.source );\n\n this.workingElements = _.without( this.workingElements, element );\n if( !this.workingElements.length ){\n this._renderNoElementsLeft();\n }\n });\n\n //this.on( 'all', function(){\n // this.info( arguments );\n //});\n return this;\n },\n\n /** handle errors with feedback and details to the user (if available) */\n _errorHandler : function( data ){\n this.error( data );\n\n var creator = this;\n content = data.message || _l( 'An error occurred' );\n if( data.xhr ){\n var xhr = data.xhr,\n message = data.message;\n if( xhr.readyState === 0 && xhr.status === 0 ){\n content += ': ' + _l( 'Galaxy could not be reached and may be updating.' ) +\n _l( ' Try again in a few minutes.' );\n } else if( xhr.responseJSON ){\n content += ':
              ' + JSON.stringify( xhr.responseJSON ) + '
              ';\n } else {\n content += ': ' + message;\n }\n }\n creator._showAlert( content, 'alert-danger' );\n },\n\n events : {\n // header\n 'click .more-help' : '_clickMoreHelp',\n 'click .less-help' : '_clickLessHelp',\n 'click .main-help' : '_toggleHelp',\n 'click .header .alert button' : '_hideAlert',\n\n 'click .reset' : 'reset',\n 'click .clear-selected' : 'clearSelectedElements',\n\n // elements - selection\n 'click .collection-elements' : 'clearSelectedElements',\n\n // elements - drop target\n // 'dragenter .collection-elements': '_dragenterElements',\n // 'dragleave .collection-elements': '_dragleaveElements',\n 'dragover .collection-elements' : '_dragoverElements',\n 'drop .collection-elements' : '_dropElements',\n\n // these bubble up from the elements as custom events\n 'collection-element.dragstart .collection-elements' : '_elementDragstart',\n 'collection-element.dragend .collection-elements' : '_elementDragend',\n\n // footer\n 'change .collection-name' : '_changeName',\n 'keydown .collection-name' : '_nameCheckForEnter',\n 'click .cancel-create' : function( ev ){\n if( typeof this.oncancel === 'function' ){\n this.oncancel.call( this );\n }\n },\n 'click .create-collection' : '_clickCreate'//,\n },\n\n // ........................................................................ header\n /** expand help */\n _clickMoreHelp : function( ev ){\n ev.stopPropagation();\n this.$( '.main-help' ).addClass( 'expanded' );\n this.$( '.more-help' ).hide();\n },\n /** collapse help */\n _clickLessHelp : function( ev ){\n ev.stopPropagation();\n this.$( '.main-help' ).removeClass( 'expanded' );\n this.$( '.more-help' ).show();\n },\n /** toggle help */\n _toggleHelp : function( ev ){\n ev.stopPropagation();\n this.$( '.main-help' ).toggleClass( 'expanded' );\n this.$( '.more-help' ).toggle();\n },\n\n /** show an alert on the top of the interface containing message (alertClass is bootstrap's alert-*) */\n _showAlert : function( message, alertClass ){\n alertClass = alertClass || 'alert-danger';\n this.$( '.main-help' ).hide();\n this.$( '.header .alert' )\n .attr( 'class', 'alert alert-dismissable' ).addClass( alertClass ).show()\n .find( '.alert-message' ).html( message );\n },\n /** hide the alerts at the top */\n _hideAlert : function( message ){\n this.$( '.main-help' ).show();\n this.$( '.header .alert' ).hide();\n },\n\n // ........................................................................ elements\n /** reset all data to the initial state */\n reset : function(){\n this._instanceSetUp();\n this._elementsSetUp();\n this.render();\n },\n\n /** deselect all elements */\n clearSelectedElements : function( ev ){\n this.$( '.collection-elements .collection-element' ).removeClass( 'selected' );\n this.$( '.collection-elements-controls > .clear-selected' ).hide();\n },\n\n //_dragenterElements : function( ev ){\n // //this.debug( '_dragenterElements:', ev );\n //},\n//TODO: if selected are dragged out of the list area - remove the placeholder - cuz it won't work anyway\n // _dragleaveElements : function( ev ){\n // //this.debug( '_dragleaveElements:', ev );\n // },\n\n /** track the mouse drag over the list adding a placeholder to show where the drop would occur */\n _dragoverElements : function( ev ){\n //this.debug( '_dragoverElements:', ev );\n ev.preventDefault();\n\n var $list = this.$list();\n this._checkForAutoscroll( $list, ev.originalEvent.clientY );\n var $nearest = this._getNearestElement( ev.originalEvent.clientY );\n\n //TODO: no need to re-create - move instead\n this.$( '.element-drop-placeholder' ).remove();\n var $placeholder = $( '
              ' );\n if( !$nearest.length ){\n $list.append( $placeholder );\n } else {\n $nearest.before( $placeholder );\n }\n },\n\n /** If the mouse is near enough to the list's top or bottom, scroll the list */\n _checkForAutoscroll : function( $element, y ){\n var AUTOSCROLL_SPEED = 2,\n offset = $element.offset(),\n scrollTop = $element.scrollTop(),\n upperDist = y - offset.top,\n lowerDist = ( offset.top + $element.outerHeight() ) - y;\n if( upperDist >= 0 && upperDist < this.autoscrollDist ){\n $element.scrollTop( scrollTop - AUTOSCROLL_SPEED );\n } else if( lowerDist >= 0 && lowerDist < this.autoscrollDist ){\n $element.scrollTop( scrollTop + AUTOSCROLL_SPEED );\n }\n },\n\n /** get the nearest element based on the mouse's Y coordinate.\n * If the y is at the end of the list, return an empty jQuery object.\n */\n _getNearestElement : function( y ){\n var WIGGLE = 4,\n lis = this.$( '.collection-elements li.collection-element' ).toArray();\n for( var i=0; i y && top - halfHeight < y ){\n return $li;\n }\n }\n return $();\n },\n\n /** drop (dragged/selected elements) onto the list, re-ordering the internal list */\n _dropElements : function( ev ){\n if( ev.originalEvent ){ ev = ev.originalEvent; }\n // both required for firefox\n ev.preventDefault();\n ev.dataTransfer.dropEffect = 'move';\n\n // insert before the nearest element or after the last.\n var $nearest = this._getNearestElement( ev.clientY );\n if( $nearest.length ){\n this.$dragging.insertBefore( $nearest );\n } else {\n // no nearest before - insert after last element\n this.$dragging.insertAfter( this.$( '.collection-elements .collection-element' ).last() );\n }\n // resync the creator's list based on the new DOM order\n this._syncOrderToDom();\n return false;\n },\n\n /** resync the creator's list of elements based on the DOM order */\n _syncOrderToDom : function(){\n var creator = this,\n newElements = [];\n //TODO: doesn't seem wise to use the dom to store these - can't we sync another way?\n this.$( '.collection-elements .collection-element' ).each( function(){\n var id = $( this ).attr( 'data-element-id' ),\n element = _.findWhere( creator.workingElements, { id: id });\n if( element ){\n newElements.push( element );\n } else {\n console.error( 'missing element: ', id );\n }\n });\n this.workingElements = newElements;\n this._renderList();\n },\n\n /** drag communication with element sub-views: dragstart */\n _elementDragstart : function( ev, element ){\n // auto select the element causing the event and move all selected\n element.select( true );\n this.$dragging = this.$( '.collection-elements .collection-element.selected' );\n },\n\n /** drag communication with element sub-views: dragend - remove the placeholder */\n _elementDragend : function( ev, element ){\n $( '.element-drop-placeholder' ).remove();\n this.$dragging = null;\n },\n\n // ........................................................................ footer\n /** handle a collection name change */\n _changeName : function( ev ){\n this._validationWarning( 'name', !!this._getName() );\n },\n\n /** check for enter key press when in the collection name and submit */\n _nameCheckForEnter : function( ev ){\n if( ev.keyCode === 13 && !this.blocking ){\n this._clickCreate();\n }\n },\n\n /** get the current collection name */\n _getName : function(){\n return _.escape( this.$( '.collection-name' ).val() );\n },\n\n /** attempt to create the current collection */\n _clickCreate : function( ev ){\n var name = this._getName();\n if( !name ){\n this._validationWarning( 'name' );\n } else if( !this.blocking ){\n this.createList( name );\n }\n },\n\n // ------------------------------------------------------------------------ templates\n //TODO: move to require text plugin and load these as text\n //TODO: underscore currently unnecc. bc no vars are used\n //TODO: better way of localizing text-nodes in long strings\n /** underscore template fns attached to class */\n templates : {\n /** the skeleton */\n main : _.template([\n '
              ',\n '
              ',\n '
              '\n ].join('')),\n\n /** the header (not including help text) */\n header : _.template([\n '
              ',\n '', _l( 'More help' ), '',\n '
              ',\n '', _l( 'Less' ), '',\n '
              ',\n '
              ',\n '
              ',\n '',\n '',\n '
              ',\n ].join('')),\n\n /** the middle: element list */\n middle : _.template([\n '',\n '
              ',\n '
              '\n ].join('')),\n\n /** creation and cancel controls */\n footer : _.template([\n '
              ',\n '
              ',\n '',\n '
              ', _l( 'Name' ), ':
              ',\n '
              ',\n '
              ',\n\n '
              ',\n '
              ',\n '',\n '
              ',\n '',\n '',\n '
              ',\n '
              ',\n\n '
              ',\n '',\n '
              ',\n '
              '\n ].join('')),\n\n /** help content */\n helpContent : _.template([\n '

              ', _l([\n 'Collections of datasets are permanent, ordered lists of datasets that can be passed to tools and ',\n 'workflows in order to have analyses done on each member of the entire group. This interface allows ',\n 'you to create a collection and re-order the final collection.'\n ].join( '' )), '

              ',\n '
                ',\n '
              • ', _l([\n 'Rename elements in the list by clicking on ',\n 'the existing name.'\n ].join( '' )), '
              • ',\n '
              • ', _l([\n 'Discard elements from the final created list by clicking on the ',\n '\"Discard\" button.'\n ].join( '' )), '
              • ',\n '
              • ', _l([\n 'Reorder the list by clicking and dragging elements. Select multiple elements by clicking on ',\n 'them and you can then move those selected by dragging the ',\n 'entire group. Deselect them by clicking them again or by clicking the ',\n 'the \"Clear selected\" link.'\n ].join( '' )), '
              • ',\n '
              • ', _l([\n 'Click the \"Start over\" link to begin again as if you had just opened ',\n 'the interface.'\n ].join( '' )), '
              • ',\n '
              • ', _l([\n 'Click the \"Cancel\" button to exit the interface.'\n ].join( '' )), '
              • ',\n '

              ',\n '

              ', _l([\n 'Once your collection is complete, enter a name and ',\n 'click \"Create list\".'\n ].join( '' )), '

              '\n ].join('')),\n\n /** shown in list when all elements are discarded */\n invalidElements : _.template([\n _l( 'The following selections could not be included due to problems:' ),\n '
                <% _.each( problems, function( problem ){ %>',\n '
              • <%- problem.element.name %>: <%- problem.text %>
              • ',\n '<% }); %>
              '\n ].join('')),\n\n /** shown in list when all elements are discarded */\n noElementsLeft : _.template([\n '
            • ',\n _l( 'No elements left! ' ),\n _l( 'Would you like to ' ), '', _l( 'start over' ), '?',\n '
            • '\n ].join('')),\n\n /** a simplified page communicating what went wrong and why the user needs to reselect something else */\n invalidInitial : _.template([\n '
              ',\n '
              ',\n '',\n '<% if( _.size( problems ) ){ %>',\n _l( 'The following selections could not be included due to problems' ), ':',\n '
                <% _.each( problems, function( problem ){ %>',\n '
              • <%- problem.element.name %>: <%- problem.text %>
              • ',\n '<% }); %>
              ',\n '<% } else if( _.size( elements ) < 1 ){ %>',\n _l( 'No datasets were selected' ), '.',\n '<% } %>',\n '
              ',\n _l( 'At least one element is needed for the collection' ), '. ',\n _l( 'You may need to ' ),\n '', _l( 'cancel' ), ' ',\n _l( 'and reselect new elements' ), '.',\n '
              ',\n '
              ',\n '
              ',\n '
              ',\n '
              ',\n '
              ',\n '',\n // _l( 'Create a different kind of collection' ),\n '
              ',\n '
              ',\n '
              '\n ].join('')),\n },\n\n // ------------------------------------------------------------------------ misc\n /** string rep */\n toString : function(){ return 'ListCollectionCreator'; }\n});\n\n\n\n//=============================================================================\n/** Create a modal and load its body with the given CreatorClass creator type\n * @returns {Deferred} resolved when creator has built a collection.\n */\nvar collectionCreatorModal = function _collectionCreatorModal( elements, options, CreatorClass ){\n\n var deferred = jQuery.Deferred(),\n modal = Galaxy.modal || ( new UI_MODAL.View() ),\n creator;\n\n options = _.defaults( options || {}, {\n elements : elements,\n oncancel : function(){\n modal.hide();\n deferred.reject( 'cancelled' );\n },\n oncreate : function( creator, response ){\n modal.hide();\n deferred.resolve( response );\n }\n });\n\n creator = new CreatorClass( options );\n modal.show({\n title : options.title || _l( 'Create a collection' ),\n body : creator.$el,\n width : '80%',\n height : '100%',\n closing_events: true\n });\n creator.render();\n window._collectionCreator = creator;\n\n //TODO: remove modal header\n return deferred;\n};\n\n/** List collection flavor of collectionCreatorModal. */\nvar listCollectionCreatorModal = function _listCollectionCreatorModal( elements, options ){\n options = options || {};\n options.title = _l( 'Create a collection from a list of datasets' );\n return collectionCreatorModal( elements, options, ListCollectionCreator );\n};\n\n\n//==============================================================================\n/** Use a modal to create a list collection, then add it to the given history contents.\n * @returns {Deferred} resolved when the collection is added to the history.\n */\nfunction createListCollection( contents ){\n var elements = contents.toJSON(),\n promise = listCollectionCreatorModal( elements, {\n creationFn : function( elements, name ){\n elements = elements.map( function( element ){\n return {\n id : element.id,\n name : element.name,\n //TODO: this allows for list:list even if the filter above does not - reconcile\n src : ( element.history_content_type === 'dataset'? 'hda' : 'hdca' )\n };\n });\n return contents.createHDCA( elements, 'list', name );\n }\n });\n return promise;\n}\n\n//==============================================================================\n return {\n DatasetCollectionElementView: DatasetCollectionElementView,\n ListCollectionCreator : ListCollectionCreator,\n\n collectionCreatorModal : collectionCreatorModal,\n listCollectionCreatorModal : listCollectionCreatorModal,\n createListCollection : createListCollection\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/collection/list-collection-creator.js\n ** module id = 31\n ** module chunks = 3\n **/","define([\n \"mvc/list/list-item\",\n \"mvc/dataset/states\",\n \"ui/fa-icon-button\",\n \"mvc/base-mvc\",\n \"utils/localization\"\n], function( LIST_ITEM, STATES, faIconButton, BASE_MVC, _l ){\n'use strict';\n\nvar logNamespace = 'dataset';\n/*==============================================================================\nTODO:\n straighten out state rendering and templates used\n inaccessible/STATES.NOT_VIEWABLE is a special case\n simplify button rendering\n\n==============================================================================*/\nvar _super = LIST_ITEM.ListItemView;\n/** @class Read only list view for either LDDAs, HDAs, or HDADCEs.\n * Roughly, any DatasetInstance (and not a raw Dataset).\n */\nvar DatasetListItemView = _super.extend(\n/** @lends DatasetListItemView.prototype */{\n _logNamespace : logNamespace,\n\n className : _super.prototype.className + \" dataset\",\n //TODO:?? doesn't exactly match an hda's type_id\n id : function(){\n return [ 'dataset', this.model.get( 'id' ) ].join( '-' );\n },\n\n /** Set up: instance vars, options, and event handlers */\n initialize : function( attributes ){\n if( attributes.logger ){ this.logger = this.model.logger = attributes.logger; }\n this.log( this + '.initialize:', attributes );\n _super.prototype.initialize.call( this, attributes );\n\n /** where should pages from links be displayed? (default to new tab/window) */\n this.linkTarget = attributes.linkTarget || '_blank';\n },\n\n /** event listeners */\n _setUpListeners : function(){\n _super.prototype._setUpListeners.call( this );\n var self = this;\n\n // re-rendering on any model changes\n return self.listenTo( self.model, {\n 'change': function( model, options ){\n // if the model moved into the ready state and is expanded without details, fetch those details now\n if( self.model.changedAttributes().state\n && self.model.inReadyState()\n && self.expanded\n && !self.model.hasDetails() ){\n // normally, will render automatically (due to fetch -> change),\n // but! setting_metadata sometimes doesn't cause any other changes besides state\n // so, not rendering causes it to seem frozen in setting_metadata state\n self.model.fetch({ silent : true })\n .done( function(){ self.render(); });\n\n } else {\n self.render();\n }\n }\n });\n },\n\n // ......................................................................... expandable\n /** In this override, only get details if in the ready state, get rerunnable if in other states.\n * Note: fetch with no 'change' event triggering to prevent automatic rendering.\n */\n _fetchModelDetails : function(){\n var view = this;\n if( view.model.inReadyState() && !view.model.hasDetails() ){\n return view.model.fetch({ silent: true });\n }\n return jQuery.when();\n },\n\n // ......................................................................... removal\n /** Remove this view's html from the DOM and remove all event listeners.\n * @param {Number or String} speed jq effect speed\n * @param {Function} callback an optional function called when removal is done (scoped to this view)\n */\n remove : function( speed, callback ){\n var view = this;\n speed = speed || this.fxSpeed;\n this.$el.fadeOut( speed, function(){\n Backbone.View.prototype.remove.call( view );\n if( callback ){ callback.call( view ); }\n });\n },\n\n // ......................................................................... rendering\n /* TODO:\n dataset states are the issue primarily making dataset rendering complex\n each state should have it's own way of displaying/set of details\n often with different actions that can be applied\n throw in deleted/purged/visible and things get complicated easily\n I've considered (a couple of times) - creating a view for each state\n - but recreating the view during an update...seems wrong\n */\n /** In this override, add the dataset state as a class for use with state-based CSS */\n _swapNewRender : function( $newRender ){\n _super.prototype._swapNewRender.call( this, $newRender );\n if( this.model.has( 'state' ) ){\n this.$el.addClass( 'state-' + this.model.get( 'state' ) );\n }\n return this.$el;\n },\n\n // ................................................................................ titlebar\n /** In this override, add the dataset display button. */\n _renderPrimaryActions : function(){\n // render just the display for read-only\n return [ this._renderDisplayButton() ];\n },\n\n /** Render icon-button to display dataset data */\n _renderDisplayButton : function(){\n // don't show display if not viewable or not accessible\n var state = this.model.get( 'state' );\n if( ( state === STATES.NOT_VIEWABLE )\n || ( state === STATES.DISCARDED )\n || ( !this.model.get( 'accessible' ) ) ){\n return null;\n }\n\n var displayBtnData = {\n target : this.linkTarget,\n classes : 'display-btn'\n };\n\n // show a disabled display if the data's been purged\n if( this.model.get( 'purged' ) ){\n displayBtnData.disabled = true;\n displayBtnData.title = _l( 'Cannot display datasets removed from disk' );\n\n // disable if still uploading\n } else if( state === STATES.UPLOAD ){\n displayBtnData.disabled = true;\n displayBtnData.title = _l( 'This dataset must finish uploading before it can be viewed' );\n\n // disable if still new\n } else if( state === STATES.NEW ){\n displayBtnData.disabled = true;\n displayBtnData.title = _l( 'This dataset is not yet viewable' );\n\n } else {\n displayBtnData.title = _l( 'View data' );\n\n // default link for dataset\n displayBtnData.href = this.model.urls.display;\n\n // add frame manager option onclick event\n var self = this;\n displayBtnData.onclick = function( ev ){\n if (Galaxy.frame && Galaxy.frame.active) {\n // Add dataset to frames.\n Galaxy.frame.addDataset(self.model.get('id'));\n ev.preventDefault();\n }\n };\n }\n displayBtnData.faIcon = 'fa-eye';\n return faIconButton( displayBtnData );\n },\n\n // ......................................................................... rendering details\n /** Render the enclosing div of the hda body and, if expanded, the html in the body\n * @returns {jQuery} rendered DOM\n */\n _renderDetails : function(){\n //TODO: generalize to be allow different details for each state\n\n // no access - render nothing but a message\n if( this.model.get( 'state' ) === STATES.NOT_VIEWABLE ){\n return $( this.templates.noAccess( this.model.toJSON(), this ) );\n }\n\n var $details = _super.prototype._renderDetails.call( this );\n $details.find( '.actions .left' ).empty().append( this._renderSecondaryActions() );\n $details.find( '.summary' ).html( this._renderSummary() )\n .prepend( this._renderDetailMessages() );\n $details.find( '.display-applications' ).html( this._renderDisplayApplications() );\n\n this._setUpBehaviors( $details );\n return $details;\n },\n\n /** Defer to the appropo summary rendering fn based on state */\n _renderSummary : function(){\n var json = this.model.toJSON(),\n summaryRenderFn = this.templates.summaries[ json.state ];\n summaryRenderFn = summaryRenderFn || this.templates.summaries.unknown;\n return summaryRenderFn( json, this );\n },\n\n /** Render messages to be displayed only when the details are shown */\n _renderDetailMessages : function(){\n var view = this,\n $warnings = $( '
              ' ),\n json = view.model.toJSON();\n //TODO:! unordered (map)\n _.each( view.templates.detailMessages, function( templateFn ){\n $warnings.append( $( templateFn( json, view ) ) );\n });\n return $warnings;\n },\n\n /** Render the external display application links */\n _renderDisplayApplications : function(){\n if( this.model.isDeletedOrPurged() ){ return ''; }\n // render both old and new display apps using the same template\n return [\n this.templates.displayApplications( this.model.get( 'display_apps' ), this ),\n this.templates.displayApplications( this.model.get( 'display_types' ), this )\n ].join( '' );\n },\n\n // ......................................................................... secondary/details actions\n /** A series of links/buttons for less commonly used actions: re-run, info, etc. */\n _renderSecondaryActions : function(){\n this.debug( '_renderSecondaryActions' );\n switch( this.model.get( 'state' ) ){\n case STATES.NOT_VIEWABLE:\n return [];\n case STATES.OK:\n case STATES.FAILED_METADATA:\n case STATES.ERROR:\n return [ this._renderDownloadButton(), this._renderShowParamsButton() ];\n }\n return [ this._renderShowParamsButton() ];\n },\n\n /** Render icon-button to show the input and output (stdout/err) for the job that created this.\n * @returns {jQuery} rendered DOM\n */\n _renderShowParamsButton : function(){\n // gen. safe to show in all cases\n return faIconButton({\n title : _l( 'View details' ),\n classes : 'params-btn',\n href : this.model.urls.show_params,\n target : this.linkTarget,\n faIcon : 'fa-info-circle',\n onclick : function( ev ) {\n if ( Galaxy.frame && Galaxy.frame.active ) {\n Galaxy.frame.add( { title: 'Dataset details', url: this.href } );\n ev.preventDefault();\n ev.stopPropagation();\n }\n }\n });\n },\n\n /** Render icon-button/popupmenu to download the data (and/or the associated meta files (bai, etc.)) for this.\n * @returns {jQuery} rendered DOM\n */\n _renderDownloadButton : function(){\n // don't show anything if the data's been purged\n if( this.model.get( 'purged' ) || !this.model.hasData() ){ return null; }\n\n // return either: a popupmenu with links to download assoc. meta files (if there are meta files)\n // or a single download icon-button (if there are no meta files)\n if( !_.isEmpty( this.model.get( 'meta_files' ) ) ){\n return this._renderMetaFileDownloadButton();\n }\n\n return $([\n '',\n '',\n ''\n ].join( '' ));\n },\n\n /** Render the download button which opens a dropdown with links to download assoc. meta files (indeces, etc.) */\n _renderMetaFileDownloadButton : function(){\n var urls = this.model.urls;\n return $([\n '
              ',\n '',\n '',\n '',\n '',\n '
              '\n ].join( '\\n' ));\n },\n\n // ......................................................................... misc\n events : _.extend( _.clone( _super.prototype.events ), {\n 'click .display-btn' : function( ev ){ this.trigger( 'display', this, ev ); },\n 'click .params-btn' : function( ev ){ this.trigger( 'params', this, ev ); },\n 'click .download-btn' : function( ev ){ this.trigger( 'download', this, ev ); }\n }),\n\n // ......................................................................... misc\n /** String representation */\n toString : function(){\n var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n return 'DatasetListItemView(' + modelString + ')';\n }\n});\n\n// ............................................................................ TEMPLATES\n/** underscore templates */\nDatasetListItemView.prototype.templates = (function(){\n//TODO: move to require text! plugin\n\n var warnings = _.extend( {}, _super.prototype.templates.warnings, {\n failed_metadata : BASE_MVC.wrapTemplate([\n // failed metadata is rendered as a warning on an otherwise ok dataset view\n '<% if( model.state === \"failed_metadata\" ){ %>',\n '
              ',\n _l( 'An error occurred setting the metadata for this dataset' ),\n '
              ',\n '<% } %>'\n ]),\n error : BASE_MVC.wrapTemplate([\n // error during index fetch - show error on dataset\n '<% if( model.error ){ %>',\n '
              ',\n _l( 'There was an error getting the data for this dataset' ), ': <%- model.error %>',\n '
              ',\n '<% } %>'\n ]),\n purged : BASE_MVC.wrapTemplate([\n '<% if( model.purged ){ %>',\n '
              ',\n _l( 'This dataset has been deleted and removed from disk' ),\n '
              ',\n '<% } %>'\n ]),\n deleted : BASE_MVC.wrapTemplate([\n // deleted not purged\n '<% if( model.deleted && !model.purged ){ %>',\n '
              ',\n _l( 'This dataset has been deleted' ),\n '
              ',\n '<% } %>'\n ])\n\n //NOTE: hidden warning is only needed for HDAs\n });\n\n var detailsTemplate = BASE_MVC.wrapTemplate([\n '
              ',\n '
              ',\n\n '
              ',\n '
              ',\n '
              ',\n '
              ',\n\n // do not display tags, annotation, display apps, or peek when deleted\n '<% if( !dataset.deleted && !dataset.purged ){ %>',\n '
              ',\n '
              ',\n\n '
              ',\n\n '<% if( dataset.peek ){ %>',\n '
              <%= dataset.peek %>
              ',\n '<% } %>',\n '<% } %>',\n '
              '\n ], 'dataset' );\n\n var noAccessTemplate = BASE_MVC.wrapTemplate([\n '
              ',\n '
              ',\n _l( 'You do not have permission to view this dataset' ),\n '
              ',\n '
              '\n ], 'dataset' );\n\n//TODO: still toooooooooooooo complex - rework\n var summaryTemplates = {};\n summaryTemplates[ STATES.OK ] = summaryTemplates[ STATES.FAILED_METADATA ] = BASE_MVC.wrapTemplate([\n '<% if( dataset.misc_blurb ){ %>',\n '
              ',\n '<%- dataset.misc_blurb %>',\n '
              ',\n '<% } %>',\n\n '<% if( dataset.file_ext ){ %>',\n '
              ',\n '',\n '<%- dataset.file_ext %>',\n '
              ',\n '<% } %>',\n\n '<% if( dataset.metadata_dbkey ){ %>',\n '
              ',\n '',\n '',\n '<%- dataset.metadata_dbkey %>',\n '',\n '
              ',\n '<% } %>',\n\n '<% if( dataset.misc_info ){ %>',\n '
              ',\n '<%- dataset.misc_info %>',\n '
              ',\n '<% } %>'\n ], 'dataset' );\n summaryTemplates[ STATES.NEW ] = BASE_MVC.wrapTemplate([\n '
              ', _l( 'This is a new dataset and not all of its data are available yet' ), '
              '\n ], 'dataset' );\n summaryTemplates[ STATES.NOT_VIEWABLE ] = BASE_MVC.wrapTemplate([\n '
              ', _l( 'You do not have permission to view this dataset' ), '
              '\n ], 'dataset' );\n summaryTemplates[ STATES.DISCARDED ] = BASE_MVC.wrapTemplate([\n '
              ', _l( 'The job creating this dataset was cancelled before completion' ), '
              '\n ], 'dataset' );\n summaryTemplates[ STATES.QUEUED ] = BASE_MVC.wrapTemplate([\n '
              ', _l( 'This job is waiting to run' ), '
              '\n ], 'dataset' );\n summaryTemplates[ STATES.RUNNING ] = BASE_MVC.wrapTemplate([\n '
              ', _l( 'This job is currently running' ), '
              '\n ], 'dataset' );\n summaryTemplates[ STATES.UPLOAD ] = BASE_MVC.wrapTemplate([\n '
              ', _l( 'This dataset is currently uploading' ), '
              '\n ], 'dataset' );\n summaryTemplates[ STATES.SETTING_METADATA ] = BASE_MVC.wrapTemplate([\n '
              ', _l( 'Metadata is being auto-detected' ), '
              '\n ], 'dataset' );\n summaryTemplates[ STATES.PAUSED ] = BASE_MVC.wrapTemplate([\n '
              ', _l( 'This job is paused. Use the \"Resume Paused Jobs\" in the history menu to resume' ), '
              '\n ], 'dataset' );\n summaryTemplates[ STATES.ERROR ] = BASE_MVC.wrapTemplate([\n '<% if( !dataset.purged ){ %>',\n '
              <%- dataset.misc_blurb %>
              ',\n '<% } %>',\n '', _l( 'An error occurred with this dataset' ), ':',\n '
              <%- dataset.misc_info %>
              '\n ], 'dataset' );\n summaryTemplates[ STATES.EMPTY ] = BASE_MVC.wrapTemplate([\n '
              ', _l( 'No data' ), ': <%- dataset.misc_blurb %>
              '\n ], 'dataset' );\n summaryTemplates.unknown = BASE_MVC.wrapTemplate([\n '
              Error: unknown dataset state: \"<%- dataset.state %>\"
              '\n ], 'dataset' );\n\n // messages to be displayed only within the details section ('below the fold')\n var detailMessageTemplates = {\n resubmitted : BASE_MVC.wrapTemplate([\n // deleted not purged\n '<% if( model.resubmitted ){ %>',\n '
              ',\n _l( 'The job creating this dataset has been resubmitted' ),\n '
              ',\n '<% } %>'\n ])\n };\n\n // this is applied to both old and new style display apps\n var displayApplicationsTemplate = BASE_MVC.wrapTemplate([\n '<% _.each( apps, function( app ){ %>',\n '
              ',\n '<%- app.label %> ',\n '',\n '<% _.each( app.links, function( link ){ %>',\n '\" href=\"<%- link.href %>\">',\n '<% print( _l( link.text ) ); %>',\n ' ',\n '<% }); %>',\n '',\n '
              ',\n '<% }); %>'\n ], 'apps' );\n\n return _.extend( {}, _super.prototype.templates, {\n warnings : warnings,\n details : detailsTemplate,\n noAccess : noAccessTemplate,\n summaries : summaryTemplates,\n detailMessages : detailMessageTemplates,\n displayApplications : displayApplicationsTemplate\n });\n}());\n\n\n// ============================================================================\n return {\n DatasetListItemView : DatasetListItemView\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/dataset/dataset-li.js\n ** module id = 32\n ** module chunks = 3\n **/","/* This class maps the form dom to an api compatible javascript dictionary. */\ndefine([ 'utils/utils' ], function( Utils ) {\n var Manager = Backbone.Model.extend({\n initialize: function( app ) {\n this.app = app;\n },\n\n /** Creates a checksum. */\n checksum: function() {\n var sum = '';\n var self = this;\n this.app.section.$el.find( '.section-row' ).each( function() {\n var id = $(this).attr( 'id' );\n var field = self.app.field_list[ id ];\n if ( field ) {\n sum += id + ':' + JSON.stringify( field.value && field.value() ) + ':' + field.collapsed + ';';\n }\n });\n return sum;\n },\n\n /** Convert dom into a dictionary of flat id/value pairs used e.g. on job submission. */\n create: function() {\n var self = this;\n\n // get raw dictionary from dom\n var dict = {};\n this._iterate( this.app.section.$el, dict );\n\n // add to result dictionary, label elements\n var result_dict = {};\n this.flat_dict = {};\n function add( flat_id, input_id, input_value ) {\n self.flat_dict[ flat_id ] = input_id;\n result_dict[ flat_id ] = input_value;\n self.app.element_list[ input_id ] && self.app.element_list[ input_id ].$el.attr( 'tour_id', flat_id );\n }\n // converter between raw dictionary and job dictionary\n function convert( identifier, head ) {\n for ( var index in head ) {\n var node = head[ index ];\n if ( node.input ) {\n var input = node.input;\n var flat_id = identifier;\n if ( identifier != '' ) {\n flat_id += '|';\n }\n flat_id += input.name;\n switch ( input.type ) {\n case 'repeat':\n var section_label = 'section-';\n var block_indices = [];\n var block_prefix = null;\n for ( var block_label in node ) {\n var pos = block_label.indexOf( section_label );\n if ( pos != -1 ) {\n pos += section_label.length;\n block_indices.push( parseInt( block_label.substr( pos ) ));\n if ( !block_prefix ) {\n block_prefix = block_label.substr( 0, pos );\n }\n }\n }\n block_indices.sort( function( a, b ) { return a - b; });\n var index = 0;\n for ( var i in block_indices ) {\n convert( flat_id + '_' + index++, node[ block_prefix + block_indices[ i ] ]);\n }\n break;\n case 'conditional':\n var value = self.app.field_list[ input.id ].value();\n add( flat_id + '|' + input.test_param.name, input.id, value );\n var selectedCase = matchCase( input, value );\n if ( selectedCase != -1 ) {\n convert( flat_id, head[ input.id + '-section-' + selectedCase ] );\n }\n break;\n case 'section':\n convert( !input.flat && flat_id || '', node );\n break;\n default:\n var field = self.app.field_list[ input.id ];\n if ( field && field.value ) {\n var value = field.value();\n if ( input.ignore === undefined || input.ignore != value ) {\n if ( field.collapsed && input.collapsible_value ) {\n value = input.collapsible_value;\n }\n add( flat_id, input.id, value );\n if ( input.payload ) {\n for ( var p_id in input.payload ) {\n add( p_id, input.id, input.payload[ p_id ] );\n }\n }\n }\n }\n }\n }\n }\n }\n convert( '', dict );\n return result_dict;\n },\n\n /** Matches flat ids to corresponding input element\n * @param{string} flat_id - Flat input id to be looked up.\n */\n match: function ( flat_id ) {\n return this.flat_dict && this.flat_dict[ flat_id ];\n },\n\n /** Match conditional values to selected cases\n */\n matchCase: function( input, value ) {\n return matchCase( input, value );\n },\n\n /** Matches a new tool model to the current input elements e.g. used to update dynamic options\n */\n matchModel: function( model, callback ) {\n var self = this;\n visitInputs( model.inputs, function( input, name ) {\n self.flat_dict[ name ] && callback ( input, self.flat_dict[ name ] );\n });\n },\n\n /** Matches identifier from api response to input elements e.g. used to display validation errors\n */\n matchResponse: function( response ) {\n var result = {};\n var self = this;\n function search ( id, head ) {\n if ( typeof head === 'string' ) {\n var input_id = self.flat_dict[ id ];\n input_id && ( result[ input_id ] = head );\n } else {\n for ( var i in head ) {\n var new_id = i;\n if ( id !== '' ) {\n var separator = '|';\n if ( head instanceof Array ) {\n separator = '_';\n }\n new_id = id + separator + new_id;\n }\n search ( new_id, head[ i ] );\n }\n }\n }\n search( '', response );\n return result;\n },\n\n /** Map dom tree to dictionary tree with input elements.\n */\n _iterate: function( parent, dict ) {\n var self = this;\n var children = $( parent ).children();\n children.each( function() {\n var child = this;\n var id = $( child ).attr( 'id' );\n if ( $( child ).hasClass( 'section-row' ) ) {\n var input = self.app.input_list[ id ];\n dict[ id ] = ( input && { input : input } ) || {};\n self._iterate( child, dict[ id ] );\n } else {\n self._iterate( child, dict );\n }\n });\n }\n });\n\n /** Match conditional values to selected cases\n * @param{dict} input - Definition of conditional input parameter\n * @param{dict} value - Current value\n */\n var matchCase = function( input, value ) {\n if ( input.test_param.type == 'boolean' ) {\n if ( value == 'true' ) {\n value = input.test_param.truevalue || 'true';\n } else {\n value = input.test_param.falsevalue || 'false';\n }\n }\n for ( var i in input.cases ) {\n if ( input.cases[ i ].value == value ) {\n return i;\n }\n }\n return -1;\n };\n\n /** Visits tool inputs\n * @param{dict} inputs - Nested dictionary of input elements\n * @param{dict} callback - Called with the mapped dictionary object and corresponding model node\n */\n var visitInputs = function( inputs, callback, prefix, context ) {\n context = $.extend( true, {}, context );\n _.each( inputs, function ( input ) {\n if ( input && input.type && input.name ) {\n context[ input.name ] = input;\n }\n });\n for ( var key in inputs ) {\n var node = inputs[ key ];\n node.name = node.name || key;\n var name = prefix ? prefix + '|' + node.name : node.name;\n switch ( node.type ) {\n case 'repeat':\n _.each( node.cache, function( cache, j ) {\n visitInputs( cache, callback, name + '_' + j, context );\n });\n break;\n case 'conditional':\n if ( node.test_param ) {\n callback( node.test_param, name + '|' + node.test_param.name, context );\n var selectedCase = matchCase( node, node.test_param.value );\n if ( selectedCase != -1 ) {\n visitInputs( node.cases[ selectedCase ].inputs, callback, name, context );\n } else {\n Galaxy.emit.debug( 'form-data::visitInputs() - Invalid case for ' + name + '.' );\n }\n } else {\n Galaxy.emit.debug( 'form-data::visitInputs() - Conditional test parameter missing for ' + name + '.' );\n }\n break;\n case 'section':\n visitInputs( node.inputs, callback, name, context )\n break;\n default:\n callback( node, name, context );\n }\n }\n };\n\n return {\n Manager : Manager,\n visitInputs : visitInputs\n }\n});\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/form/form-data.js\n ** module id = 33\n ** module chunks = 0 3\n **/","/**\n This class creates a form input element wrapper\n*/\ndefine([], function() {\n return Backbone.View.extend({\n initialize: function( app, options ) {\n this.app = app;\n this.app_options = app.options || {};\n this.field = options && options.field || new Backbone.View();\n this.model = options && options.model || new Backbone.Model({\n text_enable : this.app_options.text_enable || 'Enable',\n text_disable : this.app_options.text_disable || 'Disable',\n cls_enable : this.app_options.cls_enable || 'fa fa-caret-square-o-down',\n cls_disable : this.app_options.cls_disable || 'fa fa-caret-square-o-up'\n }).set( options );\n\n // set element and link components\n this.setElement( this._template() );\n this.$field = this.$( '.ui-form-field' );\n this.$info = this.$( '.ui-form-info' );\n this.$preview = this.$( '.ui-form-preview' );\n this.$collapsible = this.$( '.ui-form-collapsible' );\n this.$collapsible_text = this.$( '.ui-form-collapsible-text' );\n this.$collapsible_icon = this.$( '.ui-form-collapsible-icon' );\n this.$title = this.$( '.ui-form-title' );\n this.$title_text = this.$( '.ui-form-title-text' );\n this.$error_text = this.$( '.ui-form-error-text' );\n this.$error = this.$( '.ui-form-error' );\n this.$backdrop = this.$( '.ui-form-backdrop' );\n\n // add field element\n this.$field.prepend( this.field.$el );\n\n // decide wether to expand or collapse fields\n var collapsible_value = this.model.get( 'collapsible_value' );\n this.field.collapsed = collapsible_value !== undefined && JSON.stringify( this.model.get( 'value' ) ) == JSON.stringify( collapsible_value );\n this.listenTo( this.model, 'change', this.render, this );\n this.render();\n\n // add click handler\n var self = this;\n this.$collapsible.on( 'click', function() {\n self.field.collapsed = !self.field.collapsed;\n app.trigger && app.trigger( 'change' );\n self.render();\n });\n },\n\n /** Set backdrop for input element\n */\n backdrop: function() {\n this.model.set( 'backdrop', true );\n },\n\n /** Set error text\n */\n error: function( text ) {\n this.model.set( 'error_text', text );\n },\n\n /** Reset this view\n */\n reset: function() {\n this.model.set( 'error_text', null );\n },\n\n render: function() {\n // render help\n $( '.tooltip' ).hide();\n var help_text = this.model.get( 'help', '' );\n var help_argument = this.model.get( 'argument' );\n if ( help_argument && help_text.indexOf( '(' + help_argument + ')' ) == -1 ) {\n help_text += ' (' + help_argument + ')';\n }\n this.$info.html( help_text );\n // render visibility\n this.$el[ this.model.get( 'hidden' ) ? 'hide' : 'show' ]();\n // render preview view for collapsed fields\n this.$preview[ ( this.field.collapsed && this.model.get( 'collapsible_preview' ) || this.model.get( 'disabled' ) ) ? 'show' : 'hide' ]()\n .html( _.escape( this.model.get( 'text_value' ) ) );\n // render error messages\n var error_text = this.model.get( 'error_text' );\n this.$error[ error_text ? 'show' : 'hide' ]();\n this.$el[ error_text ? 'addClass' : 'removeClass' ]( 'ui-error' );\n this.$error_text.html( error_text );\n // render backdrop\n this.$backdrop[ this.model.get( 'backdrop' ) ? 'show' : 'hide' ]();\n // render input field\n this.field.collapsed || this.model.get( 'disabled' ) ? this.$field.hide() : this.$field.show();\n // render input field color and style\n this.field.model && this.field.model.set( { 'color': this.model.get( 'color' ), 'style': this.model.get( 'style' ) } );\n // render collapsible options\n if ( !this.model.get( 'disabled' ) && this.model.get( 'collapsible_value' ) !== undefined ) {\n var collapsible_state = this.field.collapsed ? 'enable' : 'disable';\n this.$title_text.hide();\n this.$collapsible.show();\n this.$collapsible_text.text( this.model.get( 'label' ) );\n this.$collapsible_icon.removeClass().addClass( 'icon' )\n .addClass( this.model.get( 'cls_' + collapsible_state ) )\n .attr( 'data-original-title', this.model.get( 'text_' + collapsible_state ) )\n .tooltip( { placement: 'bottom' } );\n } else {\n this.$title_text.show().text( this.model.get( 'label' ) );\n this.$collapsible.hide();\n }\n },\n\n _template: function() {\n return $( '
              ' ).addClass( 'ui-form-element' )\n .append( $( '
              ' ).addClass( 'ui-form-error ui-error' )\n .append( $( '' ).addClass( 'fa fa-arrow-down' ) )\n .append( $( '' ).addClass( 'ui-form-error-text' ) )\n )\n .append( $( '
              ' ).addClass( 'ui-form-title' )\n .append( $( '
              ' ).addClass( 'ui-form-collapsible' )\n .append( $( '' ).addClass( 'ui-form-collapsible-icon' ) )\n .append( $( '' ).addClass( 'ui-form-collapsible-text' ) )\n )\n .append( $( '' ).addClass( 'ui-form-title-text' ) )\n )\n .append( $( '
              ' ).addClass( 'ui-form-field' )\n .append( $( '' ).addClass( 'ui-form-info' ) )\n .append( $( '
              ' ).addClass( 'ui-form-backdrop' ) )\n )\n .append( $( '
              ' ).addClass( 'ui-form-preview' ) );\n }\n });\n});\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/form/form-input.js\n ** module id = 34\n ** module chunks = 0 3\n **/","/**\n This class creates input elements. New input parameter types should be added to the types dictionary.\n*/\ndefine(['utils/utils',\n 'mvc/ui/ui-misc',\n 'mvc/ui/ui-select-content',\n 'mvc/ui/ui-select-library',\n 'mvc/ui/ui-select-ftp',\n 'mvc/ui/ui-color-picker'],\n function( Utils, Ui, SelectContent, SelectLibrary, SelectFtp, ColorPicker ) {\n\n // create form view\n return Backbone.Model.extend({\n /** Available parameter types */\n types: {\n 'text' : '_fieldText',\n 'select' : '_fieldSelect',\n 'data_column' : '_fieldSelect',\n 'genomebuild' : '_fieldSelect',\n 'data' : '_fieldData',\n 'data_collection' : '_fieldData',\n 'integer' : '_fieldSlider',\n 'float' : '_fieldSlider',\n 'boolean' : '_fieldBoolean',\n 'drill_down' : '_fieldDrilldown',\n 'color' : '_fieldColor',\n 'hidden' : '_fieldHidden',\n 'hidden_data' : '_fieldHidden',\n 'baseurl' : '_fieldHidden',\n 'library_data' : '_fieldLibrary',\n 'ftpfile' : '_fieldFtp'\n },\n\n /** Returns an input field for a given field type */\n create: function( input_def ) {\n var fieldClass = this.types[ input_def.type ];\n var field = typeof( this[ fieldClass ] ) === 'function' ? this[ fieldClass ].call( this, input_def ) : null;\n if ( !field ) {\n field = input_def.options ? this._fieldSelect( input_def ) : this._fieldText( input_def );\n Galaxy.emit.debug('form-parameters::_addRow()', 'Auto matched field type (' + input_def.type + ').');\n }\n input_def.value === undefined && ( input_def.value = null );\n field.value( input_def.value );\n return field;\n },\n\n /** Data input field */\n _fieldData: function( input_def ) {\n return new SelectContent.View({\n id : 'field-' + input_def.id,\n extensions : input_def.extensions,\n optional : input_def.optional,\n multiple : input_def.multiple,\n type : input_def.type,\n flavor : input_def.flavor,\n data : input_def.options,\n onchange : input_def.onchange\n });\n },\n\n /** Select/Checkbox/Radio options field */\n _fieldSelect: function ( input_def ) {\n // show text field e.g. in workflow editor\n if( input_def.is_workflow ) {\n return this._fieldText( input_def );\n }\n\n // customize properties\n if ( input_def.type == 'data_column' ) {\n input_def.error_text = 'Missing columns in referenced dataset.'\n }\n\n // identify available options\n var data = input_def.data;\n if( !data ) {\n data = [];\n _.each( input_def.options, function( option ) {\n data.push( { label: option[ 0 ], value: option[ 1 ] } );\n });\n }\n\n // identify display type\n var SelectClass = Ui.Select;\n switch ( input_def.display ) {\n case 'checkboxes':\n SelectClass = Ui.Checkbox;\n break;\n case 'radio':\n SelectClass = Ui.Radio;\n break;\n case 'radiobutton':\n SelectClass = Ui.RadioButton;\n break;\n }\n\n // create select field\n return new SelectClass.View({\n id : 'field-' + input_def.id,\n data : data,\n error_text : input_def.error_text || 'No options available',\n multiple : input_def.multiple,\n optional : input_def.optional,\n onchange : input_def.onchange,\n searchable : input_def.flavor !== 'workflow'\n });\n },\n\n /** Drill down options field */\n _fieldDrilldown: function ( input_def ) {\n // show text field e.g. in workflow editor\n if( input_def.is_workflow ) {\n return this._fieldText( input_def );\n }\n\n // create drill down field\n return new Ui.Drilldown.View({\n id : 'field-' + input_def.id,\n data : input_def.options,\n display : input_def.display,\n optional : input_def.optional,\n onchange : input_def.onchange\n });\n },\n\n /** Text input field */\n _fieldText: function( input_def ) {\n // field replaces e.g. a select field\n if ( input_def.options && input_def.data ) {\n input_def.area = input_def.multiple;\n if ( Utils.isEmpty( input_def.value ) ) {\n input_def.value = null;\n } else {\n if ( $.isArray( input_def.value ) ) {\n var str_value = '';\n for ( var i in input_def.value ) {\n str_value += String( input_def.value[ i ] );\n if ( !input_def.multiple ) {\n break;\n }\n str_value += '\\n';\n }\n input_def.value = str_value;\n }\n }\n }\n // create input element\n return new Ui.Input({\n id : 'field-' + input_def.id,\n area : input_def.area,\n placeholder : input_def.placeholder,\n onchange : input_def.onchange\n });\n },\n\n /** Slider field */\n _fieldSlider: function( input_def ) {\n return new Ui.Slider.View({\n id : 'field-' + input_def.id,\n precise : input_def.type == 'float',\n is_workflow : input_def.is_workflow,\n min : input_def.min,\n max : input_def.max,\n onchange : input_def.onchange\n });\n },\n\n /** Hidden field */\n _fieldHidden: function( input_def ) {\n return new Ui.Hidden({\n id : 'field-' + input_def.id,\n info : input_def.info\n });\n },\n\n /** Boolean field */\n _fieldBoolean: function( input_def ) {\n return new Ui.RadioButton.View({\n id : 'field-' + input_def.id,\n data : [ { label : 'Yes', value : 'true' },\n { label : 'No', value : 'false' }],\n onchange : input_def.onchange\n });\n },\n\n /** Color picker field */\n _fieldColor: function( input_def ) {\n return new ColorPicker({\n id : 'field-' + input_def.id,\n onchange : input_def.onchange\n });\n },\n\n /** Library dataset field */\n _fieldLibrary: function( input_def ) {\n return new SelectLibrary.View({\n id : 'field-' + input_def.id,\n optional : input_def.optional,\n multiple : input_def.multiple,\n onchange : input_def.onchange\n });\n },\n\n /** FTP file field */\n _fieldFtp: function( input_def ) {\n return new SelectFtp.View({\n id : 'field-' + input_def.id,\n optional : input_def.optional,\n multiple : input_def.multiple,\n onchange : input_def.onchange\n });\n }\n });\n\n return {\n View: View\n };\n});\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/form/form-parameters.js\n ** module id = 35\n ** module chunks = 0 3\n **/","/** This class creates a ui component which enables the dynamic creation of portlets */\ndefine( [ 'utils/utils', 'mvc/ui/ui-portlet', 'mvc/ui/ui-misc' ],\nfunction( Utils, Portlet, Ui ) {\n var View = Backbone.View.extend({\n initialize: function( options ) {\n this.list = {};\n this.options = Utils.merge( options, {\n title : 'Repeat',\n empty_text : 'Not available.',\n max : null,\n min : null\n });\n this.button_new = new Ui.ButtonIcon({\n icon : 'fa-plus',\n title : 'Insert ' + this.options.title,\n tooltip : 'Add new ' + this.options.title + ' block',\n floating: 'clear',\n cls : 'ui-button-icon form-repeat-add',\n onclick : function() { options.onnew && options.onnew() }\n });\n this.setElement( $( '
              ' ).append( this.$list = $( '
              ' ) )\n .append( $( '
              ' ).append( this.button_new.$el ) ) );\n },\n\n /** Number of repeat blocks */\n size: function() {\n return _.size( this.list );\n },\n\n /** Add new repeat block */\n add: function( options ) {\n if ( !options.id || this.list[ options.id ] ) {\n Galaxy.emit.debug( 'form-repeat::add()', 'Duplicate or invalid repeat block id.' );\n return;\n }\n var button_delete = new Ui.ButtonIcon({\n icon : 'fa-trash-o',\n tooltip : 'Delete this repeat block',\n cls : 'ui-button-icon-plain form-repeat-delete',\n onclick : function() { options.ondel && options.ondel() }\n });\n var portlet = new Portlet.View({\n id : options.id,\n title : 'placeholder',\n cls : options.cls || 'ui-portlet-repeat',\n operations : { button_delete: button_delete }\n });\n portlet.append( options.$el );\n portlet.$el.addClass( 'section-row' ).hide();\n this.list[ options.id ] = portlet;\n this.$list.append( portlet.$el.fadeIn( 'fast' ) );\n this.options.max > 0 && this.size() >= this.options.max && this.button_new.disable();\n this._refresh();\n },\n\n /** Delete repeat block */\n del: function( id ) {\n if ( !this.list[ id ] ) {\n Galaxy.emit.debug( 'form-repeat::del()', 'Invalid repeat block id.' );\n return;\n }\n this.$list.find( '#' + id ).remove();\n delete this.list[ id ];\n this.button_new.enable();\n this._refresh();\n },\n\n /** Remove all */\n delAll: function() {\n for( var id in this.list ) {\n this.del( id );\n }\n },\n\n /** Hides add/del options */\n hideOptions: function() {\n this.button_new.$el.hide();\n _.each( this.list, function( portlet ) { portlet.hideOperation( 'button_delete' ) } );\n _.isEmpty( this.list ) && this.$el.append( $( '
              ' ).addClass( 'ui-form-info' ).html( this.options.empty_text ) );\n },\n\n /** Refresh view */\n _refresh: function() {\n var index = 0;\n for ( var id in this.list ) {\n var portlet = this.list[ id ];\n portlet.title( ++index + ': ' + this.options.title );\n portlet[ this.size() > this.options.min ? 'showOperation' : 'hideOperation' ]( 'button_delete' );\n }\n }\n });\n\n return {\n View : View\n }\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/form/form-repeat.js\n ** module id = 36\n ** module chunks = 0 3\n **/","/**\n This class creates a form section and populates it with input elements. It also handles repeat blocks and conditionals by recursively creating new sub sections.\n*/\ndefine([ 'utils/utils', 'mvc/ui/ui-misc', 'mvc/ui/ui-portlet', 'mvc/form/form-repeat', 'mvc/form/form-input', 'mvc/form/form-parameters' ],\nfunction( Utils, Ui, Portlet, Repeat, InputElement, Parameters ) {\n var View = Backbone.View.extend({\n initialize: function( app, options ) {\n this.app = app;\n this.inputs = options.inputs;\n this.parameters = new Parameters();\n this.setElement( $( '
              ' ) );\n this.render();\n },\n\n /** Render section view */\n render: function() {\n var self = this;\n this.$el.empty();\n _.each( this.inputs, function( input ) { self.add( input ) } );\n },\n\n /** Add a new input element */\n add: function( input ) {\n var input_def = jQuery.extend( true, {}, input );\n input_def.id = input.id = Utils.uid();\n this.app.input_list[ input_def.id ] = input_def;\n switch( input_def.type ) {\n case 'conditional':\n this._addConditional( input_def );\n break;\n case 'repeat':\n this._addRepeat( input_def );\n break;\n case 'section':\n this._addSection( input_def );\n break;\n default:\n this._addRow( input_def );\n }\n },\n\n /** Add a conditional block */\n _addConditional: function( input_def ) {\n var self = this;\n input_def.test_param.id = input_def.id;\n this.app.options.sustain_conditionals && ( input_def.test_param.disabled = true );\n var field = this._addRow( input_def.test_param );\n\n // set onchange event for test parameter\n field.model && field.model.set( 'onchange', function( value ) {\n var selectedCase = self.app.data.matchCase( input_def, value );\n for ( var i in input_def.cases ) {\n var case_def = input_def.cases[ i ];\n var section_row = self.$( '#' + input_def.id + '-section-' + i );\n var nonhidden = false;\n for ( var j in case_def.inputs ) {\n if ( !case_def.inputs[ j ].hidden ) {\n nonhidden = true;\n break;\n }\n }\n if ( i == selectedCase && nonhidden ) {\n section_row.fadeIn( 'fast' );\n } else {\n section_row.hide();\n }\n }\n self.app.trigger( 'change' );\n });\n\n // add conditional sub sections\n for ( var i in input_def.cases ) {\n var sub_section = new View( this.app, { inputs: input_def.cases[ i ].inputs } );\n this._append( sub_section.$el.addClass( 'ui-form-section' ), input_def.id + '-section-' + i );\n }\n\n // trigger refresh on conditional input field after all input elements have been created\n field.trigger( 'change' );\n },\n\n /** Add a repeat block */\n _addRepeat: function( input_def ) {\n var self = this;\n var block_index = 0;\n\n // create repeat block element\n var repeat = new Repeat.View({\n title : input_def.title || 'Repeat',\n min : input_def.min,\n max : input_def.max,\n onnew : function() { create( input_def.inputs ); self.app.trigger( 'change' ); }\n });\n\n // helper function to create new repeat blocks\n function create ( inputs ) {\n var sub_section_id = input_def.id + '-section-' + ( block_index++ );\n var sub_section = new View( self.app, { inputs: inputs } );\n repeat.add( { id : sub_section_id,\n $el : sub_section.$el,\n ondel : function() { repeat.del( sub_section_id ); self.app.trigger( 'change' ); } } );\n }\n\n //\n // add parsed/minimum number of repeat blocks\n //\n var n_cache = _.size( input_def.cache );\n for ( var i = 0; i < Math.max( Math.max( n_cache, input_def.min ), input_def.default || 0 ); i++ ) {\n create( i < n_cache ? input_def.cache[ i ] : input_def.inputs );\n }\n\n // hide options\n this.app.options.sustain_repeats && repeat.hideOptions();\n\n // create input field wrapper\n var input_element = new InputElement( this.app, {\n label : input_def.title || input_def.name,\n help : input_def.help,\n field : repeat\n });\n this._append( input_element.$el, input_def.id );\n },\n\n /** Add a customized section */\n _addSection: function( input_def ) {\n var portlet = new Portlet.View({\n title : input_def.title || input_def.name,\n cls : 'ui-portlet-section',\n collapsible : true,\n collapsible_button : true,\n collapsed : !input_def.expanded\n });\n portlet.append( new View( this.app, { inputs: input_def.inputs } ).$el );\n portlet.append( $( '
              ' ).addClass( 'ui-form-info' ).html( input_def.help ) );\n this.app.on( 'expand', function( input_id ) { ( portlet.$( '#' + input_id ).length > 0 ) && portlet.expand(); } );\n this._append( portlet.$el, input_def.id );\n },\n\n /** Add a single input field element */\n _addRow: function( input_def ) {\n var self = this;\n var id = input_def.id;\n input_def.onchange = function() { self.app.trigger( 'change', id ) };\n var field = this.parameters.create( input_def );\n this.app.field_list[ id ] = field;\n var input_element = new InputElement( this.app, {\n name : input_def.name,\n label : input_def.label || input_def.name,\n value : input_def.value,\n text_value : input_def.text_value,\n collapsible_value : input_def.collapsible_value,\n collapsible_preview : input_def.collapsible_preview,\n help : input_def.help,\n argument : input_def.argument,\n disabled : input_def.disabled,\n color : input_def.color,\n style : input_def.style,\n backdrop : input_def.backdrop,\n hidden : input_def.hidden,\n field : field\n });\n this.app.element_list[ id ] = input_element;\n this._append( input_element.$el, input_def.id );\n return field;\n },\n\n /** Append a new element to the form i.e. input element, repeat block, conditionals etc. */\n _append: function( $el, id ) {\n this.$el.append( $el.addClass( 'section-row' ).attr( 'id', id ) );\n }\n });\n\n return {\n View: View\n };\n});\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/form/form-section.js\n ** module id = 37\n ** module chunks = 0 3\n **/","/**\n This is the main class of the form plugin. It is referenced as 'app' in lower level modules.\n*/\ndefine( [ 'utils/utils', 'mvc/ui/ui-portlet', 'mvc/ui/ui-misc', 'mvc/form/form-section', 'mvc/form/form-data' ],\nfunction( Utils, Portlet, Ui, FormSection, FormData ) {\n return Backbone.View.extend({\n initialize: function( options ) {\n this.options = Utils.merge( options, {\n initial_errors : false,\n cls : 'ui-portlet-limited',\n icon : null,\n always_refresh : true\n });\n this.setElement( '
              ' );\n this.render();\n },\n\n /** Update available options */\n update: function( new_model ){\n var self = this;\n this.data.matchModel( new_model, function( node, input_id ) {\n var input = self.input_list[ input_id ];\n if ( input && input.options ) {\n if ( !_.isEqual( input.options, node.options ) ) {\n input.options = node.options;\n var field = self.field_list[ input_id ];\n if ( field.update ) {\n var new_options = [];\n if ( ( [ 'data', 'data_collection', 'drill_down' ] ).indexOf( input.type ) != -1 ) {\n new_options = input.options;\n } else {\n for ( var i in node.options ) {\n var opt = node.options[ i ];\n if ( opt.length > 2 ) {\n new_options.push( { label: opt[ 0 ], value: opt[ 1 ] } );\n }\n }\n }\n field.update( new_options );\n field.trigger( 'change' );\n Galaxy.emit.debug( 'form-view::update()', 'Updating options for ' + input_id );\n }\n }\n }\n });\n },\n\n /** Set form into wait mode */\n wait: function( active ) {\n for ( var i in this.input_list ) {\n var field = this.field_list[ i ];\n var input = this.input_list[ i ];\n if ( input.is_dynamic && field.wait && field.unwait ) {\n field[ active ? 'wait' : 'unwait' ]();\n }\n }\n },\n\n /** Highlight and scroll to input element (currently only used for error notifications) */\n highlight: function ( input_id, message, silent ) {\n var input_element = this.element_list[ input_id ];\n if ( input_element ) {\n input_element.error( message || 'Please verify this parameter.' );\n this.portlet.expand();\n this.trigger( 'expand', input_id );\n if ( !silent ) {\n var $panel = this.$el.parents().filter(function() {\n return [ 'auto', 'scroll' ].indexOf( $( this ).css( 'overflow' ) ) != -1;\n }).first();\n $panel.animate( { scrollTop : $panel.scrollTop() + input_element.$el.offset().top - 120 }, 500 );\n }\n }\n },\n\n /** Highlights errors */\n errors: function( options ) {\n this.trigger( 'reset' );\n if ( options && options.errors ) {\n var error_messages = this.data.matchResponse( options.errors );\n for ( var input_id in this.element_list ) {\n var input = this.element_list[ input_id ];\n if ( error_messages[ input_id ] ) {\n this.highlight( input_id, error_messages[ input_id ], true );\n }\n }\n }\n },\n\n /** Render tool form */\n render: function() {\n var self = this;\n this.off('change');\n this.off('reset');\n // contains the dom field elements as created by the parameter factory i.e. form-parameters\n this.field_list = {};\n // contains input definitions/dictionaries as provided by the parameters to_dict() function through the api\n this.input_list = {};\n // contains the dom elements of each input element i.e. form-input which wraps the actual input field\n this.element_list = {};\n // converts the form into a json data structure\n this.data = new FormData.Manager( this );\n this._renderForm();\n this.data.create();\n this.options.initial_errors && this.errors( this.options );\n // add listener which triggers on checksum change, and reset the form input wrappers\n var current_check = this.data.checksum();\n this.on('change', function( input_id ) {\n var input = self.input_list[ input_id ];\n if ( !input || input.refresh_on_change || self.options.always_refresh ) {\n var new_check = self.data.checksum();\n if ( new_check != current_check ) {\n current_check = new_check;\n self.options.onchange && self.options.onchange();\n }\n }\n });\n this.on('reset', function() {\n _.each( self.element_list, function( input_element ) { input_element.reset() } );\n });\n return this;\n },\n\n /** Renders/appends dom elements of the form */\n _renderForm: function() {\n $( '.tooltip' ).remove();\n this.message = new Ui.Message();\n this.section = new FormSection.View( this, { inputs: this.options.inputs } );\n this.portlet = new Portlet.View({\n icon : this.options.icon,\n title : this.options.title,\n cls : this.options.cls,\n operations : this.options.operations,\n buttons : this.options.buttons,\n collapsible : this.options.collapsible,\n collapsed : this.options.collapsed\n });\n this.portlet.append( this.message.$el );\n this.portlet.append( this.section.$el );\n this.$el.empty();\n this.options.inputs && this.$el.append( this.portlet.$el );\n this.options.message && this.message.update( { persistent: true, status: 'warning', message: this.options.message } );\n Galaxy.emit.debug( 'form-view::initialize()', 'Completed' );\n }\n });\n});\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/form/form-view.js\n ** module id = 38\n ** module chunks = 0 3\n **/","define([\n \"mvc/collection/collection-model\",\n \"mvc/history/history-content-model\",\n \"utils/localization\"\n], function( DC_MODEL, HISTORY_CONTENT, _l ){\n\n'use strict';\n\n/*==============================================================================\n\nModels for DatasetCollections contained within a history.\n\nTODO:\n these might be compactable to one class if some duplication with\n collection-model is used.\n\n==============================================================================*/\nvar hcontentMixin = HISTORY_CONTENT.HistoryContentMixin,\n ListDC = DC_MODEL.ListDatasetCollection,\n PairDC = DC_MODEL.PairDatasetCollection,\n ListPairedDC = DC_MODEL.ListPairedDatasetCollection,\n ListOfListsDC = DC_MODEL.ListOfListsDatasetCollection;\n\n//==============================================================================\n/** Override to post to contents route w/o id. */\nfunction buildHDCASave( _super ){\n return function _save( attributes, options ){\n if( this.isNew() ){\n options = options || {};\n options.url = this.urlRoot + this.get( 'history_id' ) + '/contents';\n attributes = attributes || {};\n attributes.type = 'dataset_collection';\n }\n return _super.call( this, attributes, options );\n };\n}\n\n\n//==============================================================================\n/** @class Backbone model for List Dataset Collection within a History.\n */\nvar HistoryListDatasetCollection = ListDC.extend( hcontentMixin ).extend(\n/** @lends HistoryListDatasetCollection.prototype */{\n\n defaults : _.extend( _.clone( ListDC.prototype.defaults ), {\n history_content_type: 'dataset_collection',\n collection_type : 'list',\n model_class : 'HistoryDatasetCollectionAssociation'\n }),\n\n /** Override to post to contents route w/o id. */\n save : buildHDCASave( ListDC.prototype.save ),\n\n /** String representation. */\n toString : function(){\n return 'History' + ListDC.prototype.toString.call( this );\n }\n});\n\n\n//==============================================================================\n/** @class Backbone model for Pair Dataset Collection within a History.\n * @constructs\n */\nvar HistoryPairDatasetCollection = PairDC.extend( hcontentMixin ).extend(\n/** @lends HistoryPairDatasetCollection.prototype */{\n\n defaults : _.extend( _.clone( PairDC.prototype.defaults ), {\n history_content_type: 'dataset_collection',\n collection_type : 'paired',\n model_class : 'HistoryDatasetCollectionAssociation'\n }),\n\n /** Override to post to contents route w/o id. */\n save : buildHDCASave( PairDC.prototype.save ),\n\n /** String representation. */\n toString : function(){\n return 'History' + PairDC.prototype.toString.call( this );\n }\n});\n\n\n//==============================================================================\n/** @class Backbone model for List of Pairs Dataset Collection within a History. */\nvar HistoryListPairedDatasetCollection = ListPairedDC.extend( hcontentMixin ).extend({\n\n defaults : _.extend( _.clone( ListPairedDC.prototype.defaults ), {\n history_content_type: 'dataset_collection',\n collection_type : 'list:paired',\n model_class : 'HistoryDatasetCollectionAssociation'\n }),\n\n /** Override to post to contents route w/o id. */\n save : buildHDCASave( ListPairedDC.prototype.save ),\n\n /** String representation. */\n toString : function(){\n return 'History' + ListPairedDC.prototype.toString.call( this );\n }\n});\n\n\n//==============================================================================\n/** @class Backbone model for List of Lists Dataset Collection within a History. */\nvar HistoryListOfListsDatasetCollection = ListOfListsDC.extend( hcontentMixin ).extend({\n\n defaults : _.extend( _.clone( ListOfListsDC.prototype.defaults ), {\n history_content_type: 'dataset_collection',\n collection_type : 'list:list',\n model_class : 'HistoryDatasetCollectionAssociation'\n }),\n\n /** Override to post to contents route w/o id. */\n save : buildHDCASave( ListOfListsDC.prototype.save ),\n\n /** String representation. */\n toString : function(){\n return ([ 'HistoryListOfListsDatasetCollection(', this.get( 'name' ), ')' ].join( '' ));\n }\n});\n\n\n//==============================================================================\n return {\n HistoryListDatasetCollection : HistoryListDatasetCollection,\n HistoryPairDatasetCollection : HistoryPairDatasetCollection,\n HistoryListPairedDatasetCollection : HistoryListPairedDatasetCollection,\n HistoryListOfListsDatasetCollection : HistoryListOfListsDatasetCollection\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/history/hdca-model.js\n ** module id = 39\n ** module chunks = 3\n **/","define([\n \"mvc/base/controlled-fetch-collection\",\n \"mvc/history/hda-model\",\n \"mvc/history/hdca-model\",\n \"mvc/history/history-preferences\",\n \"mvc/base-mvc\",\n \"utils/ajax-queue\"\n], function( CONTROLLED_FETCH_COLLECTION, HDA_MODEL, HDCA_MODEL, HISTORY_PREFS, BASE_MVC, AJAX_QUEUE ){\n'use strict';\n\n//==============================================================================\nvar _super = CONTROLLED_FETCH_COLLECTION.PaginatedCollection;\n/** @class Backbone collection for history content.\n * NOTE: history content seems like a dataset collection, but differs in that it is mixed:\n * each element can be either an HDA (dataset) or a DatasetCollection and co-exist on\n * the same level.\n * Dataset collections on the other hand are not mixed and (so far) can only contain either\n * HDAs or child dataset collections on one level.\n * This is why this does not inherit from any of the DatasetCollections (currently).\n */\nvar HistoryContents = _super.extend( BASE_MVC.LoggableMixin ).extend({\n _logNamespace : 'history',\n\n // ........................................................................ composite collection\n /** since history content is a mix, override model fn into a factory, creating based on history_content_type */\n model : function( attrs, options ) {\n if( attrs.history_content_type === \"dataset\" ) {\n return new HDA_MODEL.HistoryDatasetAssociation( attrs, options );\n\n } else if( attrs.history_content_type === \"dataset_collection\" ) {\n switch( attrs.collection_type ){\n case 'list':\n return new HDCA_MODEL.HistoryListDatasetCollection( attrs, options );\n case 'paired':\n return new HDCA_MODEL.HistoryPairDatasetCollection( attrs, options );\n case 'list:paired':\n return new HDCA_MODEL.HistoryListPairedDatasetCollection( attrs, options );\n case 'list:list':\n return new HDCA_MODEL.HistoryListOfListsDatasetCollection( attrs, options );\n }\n // This is a hack inside a hack:\n // Raise a plain object with validationError to fake a model.validationError\n // (since we don't have a model to use validate with)\n // (the outer hack being the mixed content/model function in this collection)\n var msg = 'Unknown collection_type: ' + attrs.collection_type;\n console.warn( msg, attrs );\n return { validationError : msg };\n }\n return { validationError : 'Unknown history_content_type: ' + attrs.history_content_type };\n },\n\n // ........................................................................ set up\n limitPerPage : 500,\n\n /** @type {Integer} how many contents per call to fetch when using progressivelyFetchDetails */\n limitPerProgressiveFetch : 500,\n\n /** @type {String} order used here and when fetching from server */\n order : 'hid',\n\n /** root api url */\n urlRoot : Galaxy.root + 'api/histories',\n\n /** complete api url */\n url : function(){\n return this.urlRoot + '/' + this.historyId + '/contents';\n },\n\n /** Set up */\n initialize : function( models, options ){\n options = options || {};\n _super.prototype.initialize.call( this, models, options );\n\n this.history = options.history || null;\n this.setHistoryId( options.historyId || null );\n /** @type {Boolean} does this collection contain and fetch deleted elements */\n this.includeDeleted = options.includeDeleted || this.includeDeleted;\n /** @type {Boolean} does this collection contain and fetch non-visible elements */\n this.includeHidden = options.includeHidden || this.includeHidden;\n\n // backbonejs uses collection.model.prototype.idAttribute to determine if a model is *already* in a collection\n // and either merged or replaced. In this case, our 'model' is a function so we need to add idAttribute\n // manually here - if we don't, contents will not merge but be replaced/swapped.\n this.model.prototype.idAttribute = 'type_id';\n },\n\n setHistoryId : function( newId ){\n this.historyId = newId;\n this._setUpWebStorage();\n },\n\n /** Set up client side storage. Currently PersistanStorage keyed under 'history:' */\n _setUpWebStorage : function( initialSettings ){\n // TODO: use initialSettings\n if( !this.historyId ){ return; }\n this.storage = new HISTORY_PREFS.HistoryPrefs({\n id: HISTORY_PREFS.HistoryPrefs.historyStorageKey( this.historyId )\n });\n this.trigger( 'new-storage', this.storage, this );\n\n this.on({\n 'include-deleted' : function( newVal ){\n this.storage.includeDeleted( newVal );\n },\n 'include-hidden' : function( newVal ){\n this.storage.includeHidden( newVal );\n }\n });\n\n this.includeDeleted = this.storage.includeDeleted() || false;\n this.includeHidden = this.storage.includeHidden() || false;\n return this;\n },\n\n // ........................................................................ common queries\n /** @type {Object} map of collection available sorting orders containing comparator fns */\n comparators : _.extend( _.clone( _super.prototype.comparators ), {\n 'name' : BASE_MVC.buildComparator( 'name', { ascending: true }),\n 'name-dsc' : BASE_MVC.buildComparator( 'name', { ascending: false }),\n 'hid' : BASE_MVC.buildComparator( 'hid', { ascending: false }),\n 'hid-asc' : BASE_MVC.buildComparator( 'hid', { ascending: true }),\n }),\n\n /** Get every model in this collection not in a 'ready' state (running). */\n running : function(){\n return this.filter( function( c ){ return !c.inReadyState(); });\n },\n\n /** return contents that are not ready and not deleted/hidden */\n runningAndActive : function(){\n return this.filter( function( c ){\n return ( !c.inReadyState() )\n && ( c.get( 'visible' ) )\n // TODO: deletedOrPurged?\n && ( !c.get( 'deleted' ) );\n });\n },\n\n /** Get the model with the given hid\n * @param {Int} hid the hid to search for\n * @returns {HistoryDatasetAssociation} the model with the given hid or undefined if not found\n */\n getByHid : function( hid ){\n // note: there *can* be more than one content with a given hid, this finds the first based on order\n return this.findWhere({ hid: hid });\n },\n\n /** return true if all contents have details */\n haveDetails : function(){\n return this.all( function( c ){ return c.hasDetails(); });\n },\n\n // ........................................................................ hidden / deleted\n /** return a new contents collection of only hidden items */\n hidden : function(){\n return this.filter( function( c ){ return c.hidden(); });\n },\n\n /** return a new contents collection of only hidden items */\n deleted : function(){\n return this.filter( function( c ){ return c.get( 'deleted' ); });\n },\n\n /** return a new contents collection of only hidden items */\n visibleAndUndeleted : function(){\n return this.filter( function( c ){\n return ( c.get( 'visible' ) )\n // TODO: deletedOrPurged?\n && ( !c.get( 'deleted' ) );\n });\n },\n\n /** create a setter in order to publish the change */\n setIncludeDeleted : function( setting, options ){\n if( _.isBoolean( setting ) && setting !== this.includeDeleted ){\n this.includeDeleted = setting;\n if( _.result( options, 'silent' ) ){ return; }\n this.trigger( 'include-deleted', setting, this );\n }\n },\n\n /** create a setter in order to publish the change */\n setIncludeHidden : function( setting, options ){\n if( _.isBoolean( setting ) && setting !== this.includeHidden ){\n this.includeHidden = setting;\n options = options || {};\n if( _.result( options, 'silent' ) ){ return; }\n this.trigger( 'include-hidden', setting, this );\n }\n },\n\n // ........................................................................ ajax\n // ............ controlled fetch collection\n /** override to get expanded ids from sessionStorage and pass to API as details */\n fetch : function( options ){\n options = options || {};\n if( this.historyId && !options.details ){\n var prefs = HISTORY_PREFS.HistoryPrefs.get( this.historyId ).toJSON();\n if( !_.isEmpty( prefs.expandedIds ) ){\n options.details = _.values( prefs.expandedIds ).join( ',' );\n }\n }\n return _super.prototype.fetch.call( this, options );\n },\n\n // ............. ControlledFetch stuff\n /** override to include the API versioning flag */\n _buildFetchData : function( options ){\n return _.extend( _super.prototype._buildFetchData.call( this, options ), {\n v : 'dev'\n });\n },\n\n /** Extend to include details and version */\n _fetchParams : _super.prototype._fetchParams.concat([\n // TODO: remove (the need for) both\n /** version */\n 'v',\n /** dataset ids to get full details of */\n 'details',\n ]),\n\n /** override to add deleted/hidden filters */\n _buildFetchFilters : function( options ){\n var superFilters = _super.prototype._buildFetchFilters.call( this, options ) || {};\n var filters = {};\n if( !this.includeDeleted ){\n filters.deleted = false;\n filters.purged = false;\n }\n if( !this.includeHidden ){\n filters.visible = true;\n }\n return _.defaults( superFilters, filters );\n },\n\n // ............ paginated collection\n getTotalItemCount : function(){\n return this.history.contentsShown();\n },\n\n // ............ history contents specific ajax\n /** override to filter requested contents to those updated after the Date 'since' */\n fetchUpdated : function( since, options ){\n if( since ){\n options = options || { filters: {} };\n options.remove = false;\n options.filters = {\n 'update_time-ge' : since.toISOString(),\n // workflows will produce hidden datasets (non-output datasets) that still\n // need to be updated in the collection or they'll update forever\n // we can remove the default visible filter by using an 'empty' value\n visible : ''\n };\n }\n return this.fetch( options );\n },\n\n /** fetch all the deleted==true contents of this collection */\n fetchDeleted : function( options ){\n options = options || {};\n var self = this;\n options.filters = _.extend( options.filters, {\n // all deleted, purged or not\n deleted : true,\n purged : undefined\n });\n options.remove = false;\n\n self.trigger( 'fetching-deleted', self );\n return self.fetch( options )\n .always( function(){ self.trigger( 'fetching-deleted-done', self ); });\n },\n\n /** fetch all the visible==false contents of this collection */\n fetchHidden : function( options ){\n options = options || {};\n var self = this;\n options.filters = _.extend( options.filters, {\n visible : false\n });\n options.remove = false;\n\n self.trigger( 'fetching-hidden', self );\n return self.fetch( options )\n .always( function(){ self.trigger( 'fetching-hidden-done', self ); });\n },\n\n /** fetch detailed model data for all contents in this collection */\n fetchAllDetails : function( options ){\n options = options || {};\n var detailsFlag = { details: 'all' };\n options.data = _.extend( options.data || {}, detailsFlag );\n return this.fetch( options );\n },\n\n /** specialty fetch method for retrieving the element_counts of all hdcas in the history */\n fetchCollectionCounts : function( options ){\n options = options || {};\n options.keys = [ 'type_id', 'element_count' ].join( ',' );\n options.filters = _.extend( options.filters || {}, {\n history_content_type: 'dataset_collection',\n });\n options.remove = false;\n return this.fetch( options );\n },\n\n // ............. quasi-batch ops\n // TODO: to batch\n /** helper that fetches using filterParams then calls save on each fetched using updateWhat as the save params */\n _filterAndUpdate : function( filterParams, updateWhat ){\n var self = this;\n var idAttribute = self.model.prototype.idAttribute;\n var updateArgs = [ updateWhat ];\n\n return self.fetch({ filters: filterParams, remove: false })\n .then( function( fetched ){\n // convert filtered json array to model array\n fetched = fetched.reduce( function( modelArray, currJson, i ){\n var model = self.get( currJson[ idAttribute ] );\n return model? modelArray.concat( model ) : modelArray;\n }, []);\n return self.ajaxQueue( 'save', updateArgs, fetched );\n });\n },\n\n /** using a queue, perform ajaxFn on each of the models in this collection */\n ajaxQueue : function( ajaxFn, args, collection ){\n collection = collection || this.models;\n return new AJAX_QUEUE.AjaxQueue( collection.slice().reverse().map( function( content, i ){\n var fn = _.isString( ajaxFn )? content[ ajaxFn ] : ajaxFn;\n return function(){ return fn.apply( content, args ); };\n })).deferred;\n },\n\n /** fetch contents' details in batches of limitPerCall - note: only get searchable details here */\n progressivelyFetchDetails : function( options ){\n options = options || {};\n var deferred = jQuery.Deferred();\n var self = this;\n var limit = options.limitPerCall || self.limitPerProgressiveFetch;\n // TODO: only fetch tags and annotations if specifically requested\n var searchAttributes = HDA_MODEL.HistoryDatasetAssociation.prototype.searchAttributes;\n var detailKeys = searchAttributes.join( ',' );\n\n function _recursivelyFetch( offset ){\n offset = offset || 0;\n var _options = _.extend( _.clone( options ), {\n view : 'summary',\n keys : detailKeys,\n limit : limit,\n offset : offset,\n reset : offset === 0,\n remove : false\n });\n\n _.defer( function(){\n self.fetch.call( self, _options )\n .fail( deferred.reject )\n .done( function( response ){\n deferred.notify( response, limit, offset );\n if( response.length !== limit ){\n self.allFetched = true;\n deferred.resolve( response, limit, offset );\n\n } else {\n _recursivelyFetch( offset + limit );\n }\n });\n });\n }\n _recursivelyFetch();\n return deferred;\n },\n\n /** does some bit of JSON represent something that can be copied into this contents collection */\n isCopyable : function( contentsJSON ){\n var copyableModelClasses = [\n 'HistoryDatasetAssociation',\n 'HistoryDatasetCollectionAssociation'\n ];\n return ( ( _.isObject( contentsJSON ) && contentsJSON.id )\n && ( _.contains( copyableModelClasses, contentsJSON.model_class ) ) );\n },\n\n /** copy an existing, accessible hda into this collection */\n copy : function( json ){\n // TODO: somehow showhorn all this into 'save'\n var id, type, contentType;\n if( _.isString( json ) ){\n id = json;\n contentType = 'hda';\n type = 'dataset';\n } else {\n id = json.id;\n contentType = ({\n 'HistoryDatasetAssociation' : 'hda',\n 'LibraryDatasetDatasetAssociation' : 'ldda',\n 'HistoryDatasetCollectionAssociation' : 'hdca'\n })[ json.model_class ] || 'hda';\n type = ( contentType === 'hdca'? 'dataset_collection' : 'dataset' );\n }\n var collection = this,\n xhr = jQuery.ajax( this.url(), {\n method: 'POST',\n contentType: 'application/json',\n data: JSON.stringify({\n content : id,\n source : contentType,\n type : type\n })\n })\n .done( function( response ){\n collection.add([ response ], { parse: true });\n })\n .fail( function( error, status, message ){\n collection.trigger( 'error', collection, xhr, {},\n 'Error copying contents', { type: type, id: id, source: contentType });\n });\n return xhr;\n },\n\n /** create a new HDCA in this collection */\n createHDCA : function( elementIdentifiers, collectionType, name, options ){\n // normally collection.create returns the new model, but we need the promise from the ajax, so we fake create\n //precondition: elementIdentifiers is an array of plain js objects\n // in the proper form to create the collectionType\n var hdca = this.model({\n history_content_type: 'dataset_collection',\n collection_type : collectionType,\n history_id : this.historyId,\n name : name,\n // should probably be able to just send in a bunch of json here and restruct per class\n // note: element_identifiers is now (incorrectly) an attribute\n element_identifiers : elementIdentifiers\n // do not create the model on the client until the ajax returns\n });\n return hdca.save( options );\n },\n\n // ........................................................................ searching\n /** return true if all contents have the searchable attributes */\n haveSearchDetails : function(){\n return this.allFetched && this.all( function( content ){\n // null (which is a valid returned annotation value)\n // will return false when using content.has( 'annotation' )\n //TODO: a bit hacky - formalize\n return _.has( content.attributes, 'annotation' );\n });\n },\n\n /** return a new collection of contents whose attributes contain the substring matchesWhat */\n matches : function( matchesWhat ){\n return this.filter( function( content ){\n return content.matches( matchesWhat );\n });\n },\n\n // ........................................................................ misc\n /** In this override, copy the historyId to the clone */\n clone : function(){\n var clone = Backbone.Collection.prototype.clone.call( this );\n clone.historyId = this.historyId;\n return clone;\n },\n\n /** String representation. */\n toString : function(){\n return ([ 'HistoryContents(', [ this.historyId, this.length ].join(), ')' ].join( '' ));\n }\n});\n\n\n//==============================================================================\n return {\n HistoryContents : HistoryContents\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/history/history-contents.js\n ** module id = 40\n ** module chunks = 3\n **/","define([\n \"mvc/base-mvc\"\n], function( BASE_MVC ){\n\n'use strict';\n\nvar logNamespace = 'history';\n\n// ============================================================================\n/** session storage for individual history preferences */\nvar HistoryPrefs = BASE_MVC.SessionStorageModel.extend(\n/** @lends HistoryPrefs.prototype */{\n //TODO:?? move to user prefs?\n defaults : {\n //TODO:?? expandedIds to array?\n expandedIds : {},\n show_deleted : false,\n show_hidden : false\n },\n\n /** add an hda id to the hash of expanded hdas */\n addExpanded : function( model ){\n//TODO: use type_id and not model\n var current = this.get( 'expandedIds' );\n current[ model.id ] = model.get( 'id' );\n this.save( 'expandedIds', current );\n },\n\n /** remove an hda id from the hash of expanded hdas */\n removeExpanded : function( model ){\n var current = this.get( 'expandedIds' );\n delete current[ model.id ];\n this.save( 'expandedIds', current );\n },\n\n isExpanded : function( contentId ){\n return _.result( this.get( 'expandedIds' ), contentId, false );\n },\n\n allExpanded : function(){\n return _.values( this.get( 'expandedIds' ) );\n },\n\n clearExpanded : function(){\n this.set( 'expandedIds', {} );\n },\n\n includeDeleted : function( val ){\n // moving the invocation here so other components don't need to know the key\n // TODO: change this key later\n if( !_.isUndefined( val ) ){ this.set( 'show_deleted', val ); }\n return this.get( 'show_deleted' );\n },\n\n includeHidden : function( val ){\n // TODO: change this key later\n if( !_.isUndefined( val ) ){ this.set( 'show_hidden', val ); }\n return this.get( 'show_hidden' );\n },\n\n toString : function(){\n return 'HistoryPrefs(' + this.id + ')';\n }\n\n}, {\n // ........................................................................ class vars\n // class lvl for access w/o instantiation\n storageKeyPrefix : 'history:',\n\n /** key string to store each histories settings under */\n historyStorageKey : function historyStorageKey( historyId ){\n if( !historyId ){\n throw new Error( 'HistoryPrefs.historyStorageKey needs valid id: ' + historyId );\n }\n // single point of change\n return ( HistoryPrefs.storageKeyPrefix + historyId );\n },\n\n /** return the existing storage for the history with the given id (or create one if it doesn't exist) */\n get : function get( historyId ){\n return new HistoryPrefs({ id: HistoryPrefs.historyStorageKey( historyId ) });\n },\n\n /** clear all history related items in sessionStorage */\n clearAll : function clearAll( historyId ){\n for( var key in sessionStorage ){\n if( key.indexOf( HistoryPrefs.storageKeyPrefix ) === 0 ){\n sessionStorage.removeItem( key );\n }\n }\n }\n});\n\n//==============================================================================\n return {\n HistoryPrefs: HistoryPrefs\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/history/history-preferences.js\n ** module id = 41\n ** module chunks = 3\n **/","define([\n 'mvc/base-mvc',\n 'utils/localization'\n], function( BASE_MVC, _l ){\n\n'use strict';\n\nvar logNamespace = 'list';\n//==============================================================================\n/** A view which, when first rendered, shows only summary data/attributes, but\n * can be expanded to show further details (and optionally fetch those\n * details from the server).\n */\nvar ExpandableView = Backbone.View.extend( BASE_MVC.LoggableMixin ).extend({\n _logNamespace : logNamespace,\n\n //TODO: Although the reasoning behind them is different, this shares a lot with HiddenUntilActivated above: combine them\n //PRECONDITION: model must have method hasDetails\n //PRECONDITION: subclasses must have templates.el and templates.details\n\n initialize : function( attributes ){\n /** are the details of this view expanded/shown or not? */\n this.expanded = attributes.expanded || false;\n this.log( '\\t expanded:', this.expanded );\n this.fxSpeed = attributes.fxSpeed !== undefined? attributes.fxSpeed : this.fxSpeed;\n },\n\n // ........................................................................ render main\n /** jq fx speed */\n fxSpeed : 'fast',\n\n /** Render this content, set up ui.\n * @param {Number or String} speed the speed of the render\n */\n render : function( speed ){\n var $newRender = this._buildNewRender();\n this._setUpBehaviors( $newRender );\n this._queueNewRender( $newRender, speed );\n return this;\n },\n\n /** Build a temp div containing the new children for the view's $el.\n * If the view is already expanded, build the details as well.\n */\n _buildNewRender : function(){\n // create a new render using a skeleton template, render title buttons, render body, and set up events, etc.\n var $newRender = $( this.templates.el( this.model.toJSON(), this ) );\n if( this.expanded ){\n this.$details( $newRender ).replaceWith( this._renderDetails().show() );\n }\n return $newRender;\n },\n\n /** Fade out the old el, swap in the new contents, then fade in.\n * @param {Number or String} speed jq speed to use for rendering effects\n * @fires rendered when rendered\n */\n _queueNewRender : function( $newRender, speed ) {\n speed = ( speed === undefined )?( this.fxSpeed ):( speed );\n var view = this;\n\n if( speed === 0 ){\n view._swapNewRender( $newRender );\n view.trigger( 'rendered', view );\n\n } else {\n $( view ).queue( 'fx', [\n function( next ){\n view.$el.fadeOut( speed, next );\n },\n function( next ){\n view._swapNewRender( $newRender );\n next();\n },\n function( next ){\n view.$el.fadeIn( speed, next );\n },\n function( next ){\n view.trigger( 'rendered', view );\n next();\n }\n ]);\n }\n },\n\n /** empty out the current el, move the $newRender's children in */\n _swapNewRender : function( $newRender ){\n return this.$el.empty()\n .attr( 'class', _.isFunction( this.className )? this.className(): this.className )\n .append( $newRender.children() );\n },\n\n /** set up js behaviors, event handlers for elements within the given container\n * @param {jQuery} $container jq object that contains the elements to process (defaults to this.$el)\n */\n _setUpBehaviors : function( $where ){\n $where = $where || this.$el;\n // set up canned behavior on children (bootstrap, popupmenus, editable_text, etc.)\n //make_popup_menus( $where );\n $where.find( '[title]' ).tooltip({ placement : 'bottom' });\n },\n\n // ......................................................................... details\n /** shortcut to details DOM (as jQ) */\n $details : function( $where ){\n $where = $where || this.$el;\n return $where.find( '> .details' );\n },\n\n /** build the DOM for the details and set up behaviors on it */\n _renderDetails : function(){\n var $newDetails = $( this.templates.details( this.model.toJSON(), this ) );\n this._setUpBehaviors( $newDetails );\n return $newDetails;\n },\n\n // ......................................................................... expansion/details\n /** Show or hide the details\n * @param {Boolean} expand if true, expand; if false, collapse\n */\n toggleExpanded : function( expand ){\n expand = ( expand === undefined )?( !this.expanded ):( expand );\n if( expand ){\n this.expand();\n } else {\n this.collapse();\n }\n return this;\n },\n\n /** Render and show the full, detailed body of this view including extra data and controls.\n * note: if the model does not have detailed data, fetch that data before showing the body\n * @fires expanded when a body has been expanded\n */\n expand : function(){\n var view = this;\n return view._fetchModelDetails().always( function(){\n view._expand();\n });\n },\n\n /** Check for model details and, if none, fetch them.\n * @returns {jQuery.promise} the model.fetch.xhr if details are being fetched, an empty promise if not\n */\n _fetchModelDetails : function(){\n if( !this.model.hasDetails() ){\n return this.model.fetch();\n }\n return jQuery.when();\n },\n\n /** Inner fn called when expand (public) has fetched the details */\n _expand : function(){\n var view = this,\n $newDetails = view._renderDetails();\n view.$details().replaceWith( $newDetails );\n // needs to be set after the above or the slide will not show\n view.expanded = true;\n view.$details().slideDown( view.fxSpeed, function(){\n view.trigger( 'expanded', view );\n });\n },\n\n /** Hide the body/details of an HDA.\n * @fires collapsed when a body has been collapsed\n */\n collapse : function(){\n this.debug( this + '(ExpandableView).collapse' );\n var view = this;\n view.expanded = false;\n this.$details().slideUp( view.fxSpeed, function(){\n view.trigger( 'collapsed', view );\n });\n }\n\n});\n\n\n//==============================================================================\n/** A view that is displayed in some larger list/grid/collection.\n * Inherits from Expandable, Selectable, Draggable.\n * The DOM contains warnings, a title bar, and a series of primary action controls.\n * Primary actions are meant to be easily accessible item functions (such as delete)\n * that are rendered in the title bar.\n *\n * Details are rendered when the user clicks the title bar or presses enter/space when\n * the title bar is in focus.\n *\n * Designed as a base class for history panel contents - but usable elsewhere (I hope).\n */\nvar ListItemView = ExpandableView.extend(\n BASE_MVC.mixin( BASE_MVC.SelectableViewMixin, BASE_MVC.DraggableViewMixin, {\n\n tagName : 'div',\n className : 'list-item',\n\n /** Set up the base class and all mixins */\n initialize : function( attributes ){\n ExpandableView.prototype.initialize.call( this, attributes );\n BASE_MVC.SelectableViewMixin.initialize.call( this, attributes );\n BASE_MVC.DraggableViewMixin.initialize.call( this, attributes );\n this._setUpListeners();\n },\n\n /** event listeners */\n _setUpListeners : function(){\n // hide the primary actions in the title bar when selectable and narrow\n this.on( 'selectable', function( isSelectable ){\n if( isSelectable ){\n this.$( '.primary-actions' ).hide();\n } else {\n this.$( '.primary-actions' ).show();\n }\n }, this );\n return this;\n },\n\n // ........................................................................ rendering\n /** In this override, call methods to build warnings, titlebar and primary actions */\n _buildNewRender : function(){\n var $newRender = ExpandableView.prototype._buildNewRender.call( this );\n $newRender.children( '.warnings' ).replaceWith( this._renderWarnings() );\n $newRender.children( '.title-bar' ).replaceWith( this._renderTitleBar() );\n $newRender.children( '.primary-actions' ).append( this._renderPrimaryActions() );\n $newRender.find( '> .title-bar .subtitle' ).replaceWith( this._renderSubtitle() );\n return $newRender;\n },\n\n /** In this override, render the selector controls and set up dragging before the swap */\n _swapNewRender : function( $newRender ){\n ExpandableView.prototype._swapNewRender.call( this, $newRender );\n if( this.selectable ){ this.showSelector( 0 ); }\n if( this.draggable ){ this.draggableOn(); }\n return this.$el;\n },\n\n /** Render any warnings the item may need to show (e.g. \"I'm deleted\") */\n _renderWarnings : function(){\n var view = this,\n $warnings = $( '
              ' ),\n json = view.model.toJSON();\n //TODO:! unordered (map)\n _.each( view.templates.warnings, function( templateFn ){\n $warnings.append( $( templateFn( json, view ) ) );\n });\n return $warnings;\n },\n\n /** Render the title bar (the main/exposed SUMMARY dom element) */\n _renderTitleBar : function(){\n return $( this.templates.titleBar( this.model.toJSON(), this ) );\n },\n\n /** Return an array of jQ objects containing common/easily-accessible item controls */\n _renderPrimaryActions : function(){\n // override this\n return [];\n },\n\n /** Render the title bar (the main/exposed SUMMARY dom element) */\n _renderSubtitle : function(){\n return $( this.templates.subtitle( this.model.toJSON(), this ) );\n },\n\n // ......................................................................... events\n /** event map */\n events : {\n // expand the body when the title is clicked or when in focus and space or enter is pressed\n 'click .title-bar' : '_clickTitleBar',\n 'keydown .title-bar' : '_keyDownTitleBar',\n 'click .selector' : 'toggleSelect'\n },\n\n /** expand when the title bar is clicked */\n _clickTitleBar : function( event ){\n event.stopPropagation();\n if( event.altKey ){\n this.toggleSelect( event );\n if( !this.selectable ){\n this.showSelector();\n }\n } else {\n this.toggleExpanded();\n }\n },\n\n /** expand when the title bar is in focus and enter or space is pressed */\n _keyDownTitleBar : function( event ){\n // bail (with propagation) if keydown and not space or enter\n var KEYCODE_SPACE = 32, KEYCODE_RETURN = 13;\n if( event && ( event.type === 'keydown' )\n &&( event.keyCode === KEYCODE_SPACE || event.keyCode === KEYCODE_RETURN ) ){\n this.toggleExpanded();\n event.stopPropagation();\n return false;\n }\n return true;\n },\n\n // ......................................................................... misc\n /** String representation */\n toString : function(){\n var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n return 'ListItemView(' + modelString + ')';\n }\n}));\n\n// ............................................................................ TEMPLATES\n/** underscore templates */\nListItemView.prototype.templates = (function(){\n\n var elTemplato = BASE_MVC.wrapTemplate([\n '
              ',\n // errors, messages, etc.\n '
              ',\n\n // multi-select checkbox\n '
              ',\n '',\n '
              ',\n // space for title bar buttons - gen. floated to the right\n '
              ',\n '
              ',\n\n // expandable area for more details\n '
              ',\n '
              '\n ]);\n\n var warnings = {};\n\n var titleBarTemplate = BASE_MVC.wrapTemplate([\n // adding a tabindex here allows focusing the title bar and the use of keydown to expand the dataset display\n '
              ',\n //TODO: prob. belongs in dataset-list-item\n '',\n '
              ',\n '<%- element.name %>',\n '
              ',\n '
              ',\n '
              '\n ], 'element' );\n\n var subtitleTemplate = BASE_MVC.wrapTemplate([\n // override this\n '
              '\n ]);\n\n var detailsTemplate = BASE_MVC.wrapTemplate([\n // override this\n '
              '\n ]);\n\n return {\n el : elTemplato,\n warnings : warnings,\n titleBar : titleBarTemplate,\n subtitle : subtitleTemplate,\n details : detailsTemplate\n };\n}());\n\n\n//==============================================================================\n/** A view that is displayed in some larger list/grid/collection.\n * *AND* can display some sub-list of it's own when expanded (e.g. dataset collections).\n * This list will 'foldout' when the item is expanded depending on this.foldoutStyle:\n * If 'foldout': will expand vertically to show the nested list\n * If 'drilldown': will overlay the parent list\n *\n * Inherits from ListItemView.\n *\n * _renderDetails does the work of creating this.details: a sub-view that shows the nested list\n */\nvar FoldoutListItemView = ListItemView.extend({\n\n /** If 'foldout': show the sub-panel inside the expanded item\n * If 'drilldown': only fire events and handle by pub-sub\n * (allow the panel containing this item to attach it, hide itself, etc.)\n */\n foldoutStyle : 'foldout',\n /** Panel view class to instantiate for the sub-panel */\n foldoutPanelClass : null,\n\n /** override to:\n * add attributes foldoutStyle and foldoutPanelClass for config poly\n * disrespect attributes.expanded if drilldown\n */\n initialize : function( attributes ){\n if( this.foldoutStyle === 'drilldown' ){ this.expanded = false; }\n this.foldoutStyle = attributes.foldoutStyle || this.foldoutStyle;\n this.foldoutPanelClass = attributes.foldoutPanelClass || this.foldoutPanelClass;\n\n ListItemView.prototype.initialize.call( this, attributes );\n this.foldout = this._createFoldoutPanel();\n },\n\n /** in this override, attach the foldout panel when rendering details */\n _renderDetails : function(){\n if( this.foldoutStyle === 'drilldown' ){ return $(); }\n var $newDetails = ListItemView.prototype._renderDetails.call( this );\n return this._attachFoldout( this.foldout, $newDetails );\n },\n\n /** In this override, handle collection expansion. */\n _createFoldoutPanel : function(){\n var model = this.model;\n var FoldoutClass = this._getFoldoutPanelClass( model ),\n options = this._getFoldoutPanelOptions( model ),\n foldout = new FoldoutClass( _.extend( options, {\n model : model\n }));\n return foldout;\n },\n\n /** Stub to return proper foldout panel class */\n _getFoldoutPanelClass : function(){\n // override\n return this.foldoutPanelClass;\n },\n\n /** Stub to return proper foldout panel options */\n _getFoldoutPanelOptions : function(){\n return {\n // propagate foldout style down\n foldoutStyle : this.foldoutStyle,\n fxSpeed : this.fxSpeed\n };\n },\n\n /** Render the foldout panel inside the view, hiding controls */\n _attachFoldout : function( foldout, $whereTo ){\n $whereTo = $whereTo || this.$( '> .details' );\n this.foldout = foldout.render( 0 );\n foldout.$( '> .controls' ).hide();\n return $whereTo.append( foldout.$el );\n },\n\n /** In this override, branch on foldoutStyle to show expanded */\n expand : function(){\n var view = this;\n return view._fetchModelDetails()\n .always(function(){\n if( view.foldoutStyle === 'foldout' ){\n view._expand();\n } else if( view.foldoutStyle === 'drilldown' ){\n view._expandByDrilldown();\n }\n });\n },\n\n /** For drilldown, set up close handler and fire expanded:drilldown\n * containing views can listen to this and handle other things\n * (like hiding themselves) by listening for expanded/collapsed:drilldown\n */\n _expandByDrilldown : function(){\n var view = this;\n // attachment and rendering done by listener\n view.listenTo( view.foldout, 'close', function(){\n view.trigger( 'collapsed:drilldown', view, view.foldout );\n });\n view.trigger( 'expanded:drilldown', view, view.foldout );\n }\n\n});\n\n// ............................................................................ TEMPLATES\n/** underscore templates */\nFoldoutListItemView.prototype.templates = (function(){\n\n var detailsTemplate = BASE_MVC.wrapTemplate([\n '
              ',\n // override with more info (that goes above the panel)\n '
              '\n ], 'collection' );\n\n return _.extend( {}, ListItemView.prototype.templates, {\n details : detailsTemplate\n });\n}());\n\n\n//==============================================================================\n return {\n ExpandableView : ExpandableView,\n ListItemView : ListItemView,\n FoldoutListItemView : FoldoutListItemView\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/list/list-item.js\n ** module id = 42\n ** module chunks = 3\n **/","/**\n This is the base class of the tool form plugin. This class is e.g. inherited by the regular and the workflow tool form.\n*/\ndefine(['utils/utils', 'utils/deferred', 'mvc/ui/ui-misc', 'mvc/form/form-view',\n 'mvc/citation/citation-model', 'mvc/citation/citation-view'],\n function(Utils, Deferred, Ui, FormBase, CitationModel, CitationView) {\n return FormBase.extend({\n initialize: function(options) {\n var self = this;\n FormBase.prototype.initialize.call(this, options);\n this.deferred = new Deferred();\n if (options.inputs) {\n this._buildForm(options);\n } else {\n this.deferred.execute(function(process) {\n self._buildModel(process, options, true);\n });\n }\n // listen to history panel\n if ( options.listen_to_history && parent.Galaxy && parent.Galaxy.currHistoryPanel ) {\n this.listenTo( parent.Galaxy.currHistoryPanel.collection, 'change', function() {\n this.refresh();\n });\n }\n // destroy dom elements\n this.$el.on( 'remove', function() { self.remove() } );\n },\n\n /** Listen to history panel changes and update the tool form */\n refresh: function() {\n var self = this;\n self.deferred.reset();\n this.deferred.execute( function (process){\n self._updateModel( process)\n });\n },\n\n /** Wait for deferred build processes before removal */\n remove: function() {\n var self = this;\n this.$el.hide();\n this.deferred.execute(function(){\n FormBase.prototype.remove.call(self);\n Galaxy.emit.debug('tool-form-base::remove()', 'Destroy view.');\n });\n },\n\n /** Build form */\n _buildForm: function(options) {\n var self = this;\n this.options = Utils.merge(options, this.options);\n this.options = Utils.merge({\n icon : options.icon,\n title : '' + options.name + ' ' + options.description + ' (Galaxy Version ' + options.version + ')',\n operations : !this.options.hide_operations && this._operations(),\n onchange : function() {\n self.refresh();\n }\n }, this.options);\n this.options.customize && this.options.customize( this.options );\n this.render();\n if ( !this.options.collapsible ) {\n this.$el.append( $( '
              ' ).addClass( 'ui-margin-top-large' ).append( this._footer() ) );\n }\n },\n\n /** Builds a new model through api call and recreates the entire form\n */\n _buildModel: function(process, options, hide_message) {\n var self = this;\n this.options.id = options.id;\n this.options.version = options.version;\n\n // build request url\n var build_url = '';\n var build_data = {};\n if ( options.job_id ) {\n build_url = Galaxy.root + 'api/jobs/' + options.job_id + '/build_for_rerun';\n } else {\n build_url = Galaxy.root + 'api/tools/' + options.id + '/build';\n if ( Galaxy.params && Galaxy.params.tool_id == options.id ) {\n build_data = $.extend( {}, Galaxy.params );\n options.version && ( build_data[ 'tool_version' ] = options.version );\n }\n }\n\n // get initial model\n Utils.get({\n url : build_url,\n data : build_data,\n success : function(new_model) {\n new_model = new_model.tool_model || new_model;\n if( !new_model.display ) {\n window.location = Galaxy.root;\n return;\n }\n self._buildForm(new_model);\n !hide_message && self.message.update({\n status : 'success',\n message : 'Now you are using \\'' + self.options.name + '\\' version ' + self.options.version + ', id \\'' + self.options.id + '\\'.',\n persistent : false\n });\n Galaxy.emit.debug('tool-form-base::initialize()', 'Initial tool model ready.', new_model);\n process.resolve();\n },\n error : function(response, status) {\n var error_message = ( response && response.err_msg ) || 'Uncaught error.';\n if ( status == 401 ) {\n window.location = Galaxy.root + 'user/login?' + $.param({ redirect : Galaxy.root + '?tool_id=' + self.options.id });\n } else if ( self.$el.is(':empty') ) {\n self.$el.prepend((new Ui.Message({\n message : error_message,\n status : 'danger',\n persistent : true,\n large : true\n })).$el);\n } else {\n Galaxy.modal && Galaxy.modal.show({\n title : 'Tool request failed',\n body : error_message,\n buttons : {\n 'Close' : function() {\n Galaxy.modal.hide();\n }\n }\n });\n }\n Galaxy.emit.debug('tool-form::initialize()', 'Initial tool model request failed.', response);\n process.reject();\n }\n });\n },\n\n /** Request a new model for an already created tool form and updates the form inputs\n */\n _updateModel: function(process) {\n // link this\n var self = this;\n var model_url = this.options.update_url || Galaxy.root + 'api/tools/' + this.options.id + '/build';\n var current_state = {\n tool_id : this.options.id,\n tool_version : this.options.version,\n inputs : $.extend(true, {}, self.data.create())\n }\n this.wait(true);\n\n // log tool state\n Galaxy.emit.debug('tool-form-base::_updateModel()', 'Sending current state.', current_state);\n\n // post job\n Utils.request({\n type : 'POST',\n url : model_url,\n data : current_state,\n success : function(new_model) {\n self.update(new_model['tool_model'] || new_model);\n self.options.update && self.options.update(new_model);\n self.wait(false);\n Galaxy.emit.debug('tool-form-base::_updateModel()', 'Received new model.', new_model);\n process.resolve();\n },\n error : function(response) {\n Galaxy.emit.debug('tool-form-base::_updateModel()', 'Refresh request failed.', response);\n process.reject();\n }\n });\n },\n\n /** Create tool operation menu\n */\n _operations: function() {\n var self = this;\n var options = this.options;\n\n // button for version selection\n var versions_button = new Ui.ButtonMenu({\n icon : 'fa-cubes',\n title : (!options.narrow && 'Versions') || null,\n tooltip : 'Select another tool version'\n });\n if (!options.sustain_version && options.versions && options.versions.length > 1) {\n for (var i in options.versions) {\n var version = options.versions[i];\n if (version != options.version) {\n versions_button.addMenu({\n title : 'Switch to ' + version,\n version : version,\n icon : 'fa-cube',\n onclick : function() {\n // here we update the tool version (some tools encode the version also in the id)\n var id = options.id.replace(options.version, this.version);\n var version = this.version;\n // queue model request\n self.deferred.reset();\n self.deferred.execute(function(process) {\n self._buildModel(process, {id: id, version: version})\n });\n }\n });\n }\n }\n } else {\n versions_button.$el.hide();\n }\n\n // button for options e.g. search, help\n var menu_button = new Ui.ButtonMenu({\n icon : 'fa-caret-down',\n title : (!options.narrow && 'Options') || null,\n tooltip : 'View available options'\n });\n if(options.biostar_url) {\n menu_button.addMenu({\n icon : 'fa-question-circle',\n title : 'Question?',\n tooltip : 'Ask a question about this tool (Biostar)',\n onclick : function() {\n window.open(options.biostar_url + '/p/new/post/');\n }\n });\n menu_button.addMenu({\n icon : 'fa-search',\n title : 'Search',\n tooltip : 'Search help for this tool (Biostar)',\n onclick : function() {\n window.open(options.biostar_url + '/local/search/page/?q=' + options.name);\n }\n });\n };\n menu_button.addMenu({\n icon : 'fa-share',\n title : 'Share',\n tooltip : 'Share this tool',\n onclick : function() {\n prompt('Copy to clipboard: Ctrl+C, Enter', window.location.origin + Galaxy.root + 'root?tool_id=' + options.id);\n }\n });\n\n // add admin operations\n if (Galaxy.user && Galaxy.user.get('is_admin')) {\n menu_button.addMenu({\n icon : 'fa-download',\n title : 'Download',\n tooltip : 'Download this tool',\n onclick : function() {\n window.location.href = Galaxy.root + 'api/tools/' + options.id + '/download';\n }\n });\n }\n\n // button for version selection\n if (options.requirements && options.requirements.length > 0) {\n menu_button.addMenu({\n icon : 'fa-info-circle',\n title : 'Requirements',\n tooltip : 'Display tool requirements',\n onclick : function() {\n if (!this.visible || self.portlet.collapsed ) {\n this.visible = true;\n self.portlet.expand();\n self.message.update({\n persistent : true,\n message : self._templateRequirements(options),\n status : 'info'\n });\n } else {\n this.visible = false;\n self.message.update({\n message : ''\n });\n }\n }\n });\n }\n\n // add toolshed url\n if (options.sharable_url) {\n menu_button.addMenu({\n icon : 'fa-external-link',\n title : 'See in Tool Shed',\n tooltip : 'Access the repository',\n onclick : function() {\n window.open(options.sharable_url);\n }\n });\n }\n\n return {\n menu : menu_button,\n versions : versions_button\n }\n },\n\n /** Create footer\n */\n _footer: function() {\n var options = this.options;\n var $el = $( '
              ' ).append( this._templateHelp( options ) );\n if ( options.citations ) {\n var $citations = $( '
              ' );\n var citations = new CitationModel.ToolCitationCollection();\n citations.tool_id = options.id;\n var citation_list_view = new CitationView.CitationListView({ el: $citations, collection: citations });\n citation_list_view.render();\n citations.fetch();\n $el.append( $citations );\n }\n return $el;\n },\n\n /** Templates\n */\n _templateHelp: function( options ) {\n var $tmpl = $( '
              ' ).addClass( 'ui-form-help' ).append( options.help );\n $tmpl.find( 'a' ).attr( 'target', '_blank' );\n return $tmpl;\n },\n\n _templateRequirements: function( options ) {\n var nreq = options.requirements.length;\n if ( nreq > 0 ) {\n var requirements_message = 'This tool requires ';\n _.each( options.requirements, function( req, i ) {\n requirements_message += req.name + ( req.version ? ' (Version ' + req.version + ')' : '' ) + ( i < nreq - 2 ? ', ' : ( i == nreq - 2 ? ' and ' : '' ) );\n });\n var requirements_link = $( '' ).attr( 'target', '_blank' ).attr( 'href', 'https://wiki.galaxyproject.org/Tools/Requirements' ).text( 'here' );\n return $( '' ).append( requirements_message + '. Click ' ).append( requirements_link ).append( ' for more information.' );\n }\n return 'No requirements found.';\n }\n });\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/tool/tool-form-base.js\n ** module id = 43\n ** module chunks = 0 3\n **/","/**\n * Model, view, and controller objects for Galaxy tools and tool panel.\n */\n\n define([\n \"libs/underscore\",\n \"viz/trackster/util\",\n \"mvc/dataset/data\",\n \"mvc/tool/tool-form\"\n\n], function(_, util, data, ToolForm) {\n 'use strict';\n\n/**\n * Mixin for tracking model visibility.\n */\nvar VisibilityMixin = {\n hidden: false,\n\n show: function() {\n this.set(\"hidden\", false);\n },\n\n hide: function() {\n this.set(\"hidden\", true);\n },\n\n toggle: function() {\n this.set(\"hidden\", !this.get(\"hidden\"));\n },\n\n is_visible: function() {\n return !this.attributes.hidden;\n }\n\n};\n\n/**\n * A tool parameter.\n */\nvar ToolParameter = Backbone.Model.extend({\n defaults: {\n name: null,\n label: null,\n type: null,\n value: null,\n html: null,\n num_samples: 5\n },\n\n initialize: function(options) {\n this.attributes.html = unescape(this.attributes.html);\n },\n\n copy: function() {\n return new ToolParameter(this.toJSON());\n },\n\n set_value: function(value) {\n this.set('value', value || '');\n }\n});\n\nvar ToolParameterCollection = Backbone.Collection.extend({\n model: ToolParameter\n});\n\n/**\n * A data tool parameter.\n */\nvar DataToolParameter = ToolParameter.extend({});\n\n/**\n * An integer tool parameter.\n */\nvar IntegerToolParameter = ToolParameter.extend({\n set_value: function(value) {\n this.set('value', parseInt(value, 10));\n },\n\n /**\n * Returns samples from a tool input.\n */\n get_samples: function() {\n return d3.scale.linear()\n .domain([this.get('min'), this.get('max')])\n .ticks(this.get('num_samples'));\n }\n});\n\nvar FloatToolParameter = IntegerToolParameter.extend({\n set_value: function(value) {\n this.set('value', parseFloat(value));\n }\n});\n\n/**\n * A select tool parameter.\n */\nvar SelectToolParameter = ToolParameter.extend({\n /**\n * Returns tool options.\n */\n get_samples: function() {\n return _.map(this.get('options'), function(option) {\n return option[0];\n });\n }\n});\n\n// Set up dictionary of parameter types.\nToolParameter.subModelTypes = {\n 'integer': IntegerToolParameter,\n 'float': FloatToolParameter,\n 'data': DataToolParameter,\n 'select': SelectToolParameter\n};\n\n/**\n * A Galaxy tool.\n */\nvar Tool = Backbone.Model.extend({\n // Default attributes.\n defaults: {\n id: null,\n name: null,\n description: null,\n target: null,\n inputs: [],\n outputs: []\n },\n\n urlRoot: Galaxy.root + 'api/tools',\n\n initialize: function(options) {\n\n // Set parameters.\n this.set('inputs', new ToolParameterCollection(_.map(options.inputs, function(p) {\n var p_class = ToolParameter.subModelTypes[p.type] || ToolParameter;\n return new p_class(p);\n })));\n },\n\n /**\n *\n */\n toJSON: function() {\n var rval = Backbone.Model.prototype.toJSON.call(this);\n\n // Convert inputs to JSON manually.\n rval.inputs = this.get('inputs').map(function(i) { return i.toJSON(); });\n return rval;\n },\n\n /**\n * Removes inputs of a particular type; this is useful because not all inputs can be handled by\n * client and server yet.\n */\n remove_inputs: function(types) {\n var tool = this,\n incompatible_inputs = tool.get('inputs').filter( function(input) {\n return ( types.indexOf( input.get('type') ) !== -1);\n });\n tool.get('inputs').remove(incompatible_inputs);\n },\n\n /**\n * Returns object copy, optionally including only inputs that can be sampled.\n */\n copy: function(only_samplable_inputs) {\n var copy = new Tool(this.toJSON());\n\n // Return only samplable inputs if flag is set.\n if (only_samplable_inputs) {\n var valid_inputs = new Backbone.Collection();\n copy.get('inputs').each(function(input) {\n if (input.get_samples()) {\n valid_inputs.push(input);\n }\n });\n copy.set('inputs', valid_inputs);\n }\n\n return copy;\n },\n\n apply_search_results: function(results) {\n ( _.indexOf(results, this.attributes.id) !== -1 ? this.show() : this.hide() );\n return this.is_visible();\n },\n\n /**\n * Set a tool input's value.\n */\n set_input_value: function(name, value) {\n this.get('inputs').find(function(input) {\n return input.get('name') === name;\n }).set('value', value);\n },\n\n /**\n * Set many input values at once.\n */\n set_input_values: function(inputs_dict) {\n var self = this;\n _.each(_.keys(inputs_dict), function(input_name) {\n self.set_input_value(input_name, inputs_dict[input_name]);\n });\n },\n\n /**\n * Run tool; returns a Deferred that resolves to the tool's output(s).\n */\n run: function() {\n return this._run();\n },\n\n /**\n * Rerun tool using regions and a target dataset.\n */\n rerun: function(target_dataset, regions) {\n return this._run({\n action: 'rerun',\n target_dataset_id: target_dataset.id,\n regions: regions\n });\n },\n\n /**\n * Returns input dict for tool's inputs.\n */\n get_inputs_dict: function() {\n var input_dict = {};\n this.get('inputs').each(function(input) {\n input_dict[input.get('name')] = input.get('value');\n });\n return input_dict;\n },\n\n /**\n * Run tool; returns a Deferred that resolves to the tool's output(s).\n * NOTE: this method is a helper method and should not be called directly.\n */\n _run: function(additional_params) {\n // Create payload.\n var payload = _.extend({\n tool_id: this.id,\n inputs: this.get_inputs_dict()\n }, additional_params);\n\n // Because job may require indexing datasets, use server-side\n // deferred to ensure that job is run. Also use deferred that\n // resolves to outputs from tool.\n var run_deferred = $.Deferred(),\n ss_deferred = new util.ServerStateDeferred({\n ajax_settings: {\n url: this.urlRoot,\n data: JSON.stringify(payload),\n dataType: \"json\",\n contentType: 'application/json',\n type: \"POST\"\n },\n interval: 2000,\n success_fn: function(response) {\n return response !== \"pending\";\n }\n });\n\n // Run job and resolve run_deferred to tool outputs.\n $.when(ss_deferred.go()).then(function(result) {\n run_deferred.resolve(new data.DatasetCollection(result));\n });\n return run_deferred;\n }\n});\n_.extend(Tool.prototype, VisibilityMixin);\n\n/**\n * Tool view.\n */\nvar ToolView = Backbone.View.extend({\n\n});\n\n/**\n * Wrap collection of tools for fast access/manipulation.\n */\nvar ToolCollection = Backbone.Collection.extend({\n model: Tool\n});\n\n/**\n * Label or section header in tool panel.\n */\nvar ToolSectionLabel = Backbone.Model.extend(VisibilityMixin);\n\n/**\n * Section of tool panel with elements (labels and tools).\n */\nvar ToolSection = Backbone.Model.extend({\n defaults: {\n elems: [],\n open: false\n },\n\n clear_search_results: function() {\n _.each(this.attributes.elems, function(elt) {\n elt.show();\n });\n\n this.show();\n this.set(\"open\", false);\n },\n\n apply_search_results: function(results) {\n var all_hidden = true,\n cur_label;\n _.each(this.attributes.elems, function(elt) {\n if (elt instanceof ToolSectionLabel) {\n cur_label = elt;\n cur_label.hide();\n }\n else if (elt instanceof Tool) {\n if (elt.apply_search_results(results)) {\n all_hidden = false;\n if (cur_label) {\n cur_label.show();\n }\n }\n }\n });\n\n if (all_hidden) {\n this.hide();\n }\n else {\n this.show();\n this.set(\"open\", true);\n }\n }\n});\n_.extend(ToolSection.prototype, VisibilityMixin);\n\n/**\n * Tool search that updates results when query is changed. Result value of null\n * indicates that query was not run; if not null, results are from search using\n * query.\n */\nvar ToolSearch = Backbone.Model.extend({\n defaults: {\n search_hint_string: \"search tools\",\n min_chars_for_search: 3,\n clear_btn_url: \"\",\n search_url: \"\",\n visible: true,\n query: \"\",\n results: null,\n // ESC (27) will clear the input field and tool search filters\n clear_key: 27\n },\n\n urlRoot: Galaxy.root + 'api/tools',\n\n initialize: function() {\n this.on(\"change:query\", this.do_search);\n },\n\n /**\n * Do the search and update the results.\n */\n do_search: function() {\n var query = this.attributes.query;\n\n // If query is too short, do not search.\n if (query.length < this.attributes.min_chars_for_search) {\n this.set(\"results\", null);\n return;\n }\n\n // Do search via AJAX.\n var q = query;\n // Stop previous ajax-request\n if (this.timer) {\n clearTimeout(this.timer);\n }\n // Start a new ajax-request in X ms\n $(\"#search-clear-btn\").hide();\n $(\"#search-spinner\").show();\n var self = this;\n this.timer = setTimeout(function () {\n // log the search to analytics if present\n if ( typeof ga !== 'undefined' ) {\n ga( 'send', 'pageview', Galaxy.root + '?q=' + q );\n }\n $.get( self.urlRoot, { q: q }, function (data) {\n self.set(\"results\", data);\n $(\"#search-spinner\").hide();\n $(\"#search-clear-btn\").show();\n }, \"json\" );\n }, 400 );\n },\n\n clear_search: function() {\n this.set(\"query\", \"\");\n this.set(\"results\", null);\n }\n\n});\n_.extend(ToolSearch.prototype, VisibilityMixin);\n\n/**\n * Tool Panel.\n */\nvar ToolPanel = Backbone.Model.extend({\n\n initialize: function(options) {\n this.attributes.tool_search = options.tool_search;\n this.attributes.tool_search.on(\"change:results\", this.apply_search_results, this);\n this.attributes.tools = options.tools;\n this.attributes.layout = new Backbone.Collection( this.parse(options.layout) );\n },\n\n /**\n * Parse tool panel dictionary and return collection of tool panel elements.\n */\n parse: function(response) {\n // Recursive function to parse tool panel elements.\n var self = this,\n // Helper to recursively parse tool panel.\n parse_elt = function(elt_dict) {\n var type = elt_dict.model_class;\n // There are many types of tools; for now, anything that ends in 'Tool'\n // is treated as a generic tool.\n if ( type.indexOf('Tool') === type.length - 4 ) {\n return self.attributes.tools.get(elt_dict.id);\n }\n else if (type === 'ToolSection') {\n // Parse elements.\n var elems = _.map(elt_dict.elems, parse_elt);\n elt_dict.elems = elems;\n return new ToolSection(elt_dict);\n }\n else if (type === 'ToolSectionLabel') {\n return new ToolSectionLabel(elt_dict);\n }\n };\n\n return _.map(response, parse_elt);\n },\n\n clear_search_results: function() {\n this.get('layout').each(function(panel_elt) {\n if (panel_elt instanceof ToolSection) {\n panel_elt.clear_search_results();\n }\n else {\n // Label or tool, so just show.\n panel_elt.show();\n }\n });\n },\n\n apply_search_results: function() {\n var results = this.get('tool_search').get('results');\n if (results === null) {\n this.clear_search_results();\n return;\n }\n\n var cur_label = null;\n this.get('layout').each(function(panel_elt) {\n if (panel_elt instanceof ToolSectionLabel) {\n cur_label = panel_elt;\n cur_label.hide();\n }\n else if (panel_elt instanceof Tool) {\n if (panel_elt.apply_search_results(results)) {\n if (cur_label) {\n cur_label.show();\n }\n }\n }\n else {\n // Starting new section, so clear current label.\n cur_label = null;\n panel_elt.apply_search_results(results);\n }\n });\n }\n});\n\n/**\n * View classes for Galaxy tools and tool panel.\n *\n * Views use the templates defined below for rendering. Views update as needed\n * based on (a) model/collection events and (b) user interactions; in this sense,\n * they are controllers are well and the HTML is the real view in the MVC architecture.\n */\n\n/**\n * Base view that handles visibility based on model's hidden attribute.\n */\nvar BaseView = Backbone.View.extend({\n initialize: function() {\n this.model.on(\"change:hidden\", this.update_visible, this);\n this.update_visible();\n },\n update_visible: function() {\n ( this.model.attributes.hidden ? this.$el.hide() : this.$el.show() );\n }\n});\n\n/**\n * Link to a tool.\n */\nvar ToolLinkView = BaseView.extend({\n tagName: 'div',\n\n render: function() {\n // create element\n var $link = $('
              ');\n $link.append(templates.tool_link(this.model.toJSON()));\n\n var formStyle = this.model.get( 'form_style', null );\n // open upload dialog for upload tool\n if (this.model.id === 'upload1') {\n $link.find('a').on('click', function(e) {\n e.preventDefault();\n Galaxy.upload.show();\n });\n }\n else if ( formStyle === 'regular' ) { // regular tools\n var self = this;\n $link.find('a').on('click', function(e) {\n e.preventDefault();\n var form = new ToolForm.View( { id : self.model.id, version : self.model.get('version') } );\n form.deferred.execute(function() {\n Galaxy.app.display( form );\n });\n });\n }\n\n // add element\n this.$el.append($link);\n return this;\n }\n});\n\n/**\n * Panel label/section header.\n */\nvar ToolSectionLabelView = BaseView.extend({\n tagName: 'div',\n className: 'toolPanelLabel',\n\n render: function() {\n this.$el.append( $(\"\").text(this.model.attributes.text) );\n return this;\n }\n});\n\n/**\n * Panel section.\n */\nvar ToolSectionView = BaseView.extend({\n tagName: 'div',\n className: 'toolSectionWrapper',\n\n initialize: function() {\n BaseView.prototype.initialize.call(this);\n this.model.on(\"change:open\", this.update_open, this);\n },\n\n render: function() {\n // Build using template.\n this.$el.append( templates.panel_section(this.model.toJSON()) );\n\n // Add tools to section.\n var section_body = this.$el.find(\".toolSectionBody\");\n _.each(this.model.attributes.elems, function(elt) {\n if (elt instanceof Tool) {\n var tool_view = new ToolLinkView({model: elt, className: \"toolTitle\"});\n tool_view.render();\n section_body.append(tool_view.$el);\n }\n else if (elt instanceof ToolSectionLabel) {\n var label_view = new ToolSectionLabelView({model: elt});\n label_view.render();\n section_body.append(label_view.$el);\n }\n else {\n // TODO: handle nested section bodies?\n }\n });\n return this;\n },\n\n events: {\n 'click .toolSectionTitle > a': 'toggle'\n },\n\n /**\n * Toggle visibility of tool section.\n */\n toggle: function() {\n this.model.set(\"open\", !this.model.attributes.open);\n },\n\n /**\n * Update whether section is open or close.\n */\n update_open: function() {\n (this.model.attributes.open ?\n this.$el.children(\".toolSectionBody\").slideDown(\"fast\") :\n this.$el.children(\".toolSectionBody\").slideUp(\"fast\")\n );\n }\n});\n\nvar ToolSearchView = Backbone.View.extend({\n tagName: 'div',\n id: 'tool-search',\n className: 'bar',\n\n events: {\n 'click': 'focus_and_select',\n 'keyup :input': 'query_changed',\n 'click #search-clear-btn': 'clear'\n },\n\n render: function() {\n this.$el.append( templates.tool_search(this.model.toJSON()) );\n if (!this.model.is_visible()) {\n this.$el.hide();\n }\n this.$el.find('[title]').tooltip();\n return this;\n },\n\n focus_and_select: function() {\n this.$el.find(\":input\").focus().select();\n },\n\n clear: function() {\n this.model.clear_search();\n this.$el.find(\":input\").val('');\n this.focus_and_select();\n return false;\n },\n\n query_changed: function( evData ) {\n // check for the 'clear key' (ESC) first\n if( ( this.model.attributes.clear_key ) &&\n ( this.model.attributes.clear_key === evData.which ) ){\n this.clear();\n return false;\n }\n this.model.set(\"query\", this.$el.find(\":input\").val());\n }\n});\n\n/**\n * Tool panel view. Events triggered include:\n * tool_link_click(click event, tool_model)\n */\nvar ToolPanelView = Backbone.View.extend({\n tagName: 'div',\n className: 'toolMenu',\n\n /**\n * Set up view.\n */\n initialize: function() {\n this.model.get('tool_search').on(\"change:results\", this.handle_search_results, this);\n },\n\n render: function() {\n var self = this;\n\n // Render search.\n var search_view = new ToolSearchView( { model: this.model.get('tool_search') } );\n search_view.render();\n self.$el.append(search_view.$el);\n\n // Render panel.\n this.model.get('layout').each(function(panel_elt) {\n if (panel_elt instanceof ToolSection) {\n var section_title_view = new ToolSectionView({model: panel_elt});\n section_title_view.render();\n self.$el.append(section_title_view.$el);\n }\n else if (panel_elt instanceof Tool) {\n var tool_view = new ToolLinkView({model: panel_elt, className: \"toolTitleNoSection\"});\n tool_view.render();\n self.$el.append(tool_view.$el);\n }\n else if (panel_elt instanceof ToolSectionLabel) {\n var label_view = new ToolSectionLabelView({model: panel_elt});\n label_view.render();\n self.$el.append(label_view.$el);\n }\n });\n\n // Setup tool link click eventing.\n self.$el.find(\"a.tool-link\").click(function(e) {\n // Tool id is always the first class.\n var\n tool_id = $(this).attr('class').split(/\\s+/)[0],\n tool = self.model.get('tools').get(tool_id);\n\n self.trigger(\"tool_link_click\", e, tool);\n });\n\n return this;\n },\n\n handle_search_results: function() {\n var results = this.model.get('tool_search').get('results');\n if (results && results.length === 0) {\n $(\"#search-no-results\").show();\n }\n else {\n $(\"#search-no-results\").hide();\n }\n }\n});\n\n/**\n * View for working with a tool: setting parameters and inputs and executing the tool.\n */\nvar ToolFormView = Backbone.View.extend({\n className: 'toolForm',\n\n render: function() {\n this.$el.children().remove();\n this.$el.append( templates.tool_form(this.model.toJSON()) );\n }\n});\n\n/**\n * Integrated tool menu + tool execution.\n */\nvar IntegratedToolMenuAndView = Backbone.View.extend({\n className: 'toolMenuAndView',\n\n initialize: function() {\n this.tool_panel_view = new ToolPanelView({collection: this.collection});\n this.tool_form_view = new ToolFormView();\n },\n\n render: function() {\n // Render and append tool panel.\n this.tool_panel_view.render();\n this.tool_panel_view.$el.css(\"float\", \"left\");\n this.$el.append(this.tool_panel_view.$el);\n\n // Append tool form view.\n this.tool_form_view.$el.hide();\n this.$el.append(this.tool_form_view.$el);\n\n // On tool link click, show tool.\n var self = this;\n this.tool_panel_view.on(\"tool_link_click\", function(e, tool) {\n // Prevents click from activating link:\n e.preventDefault();\n // Show tool that was clicked on:\n self.show_tool(tool);\n });\n },\n\n /**\n * Fetch and display tool.\n */\n show_tool: function(tool) {\n var self = this;\n tool.fetch().done( function() {\n self.tool_form_view.model = tool;\n self.tool_form_view.render();\n self.tool_form_view.$el.show();\n $('#left').width(\"650px\");\n });\n }\n});\n\n// TODO: move into relevant views\nvar templates = {\n // the search bar at the top of the tool panel\n tool_search : _.template([\n '\" autocomplete=\"off\" type=\"text\" />',\n ' ',\n //TODO: replace with icon\n '',\n ].join('')),\n\n // the category level container in the tool panel (e.g. 'Get Data', 'Text Manipulation')\n panel_section : _.template([\n '
              \">',\n '<%- name %>',\n '
              ',\n '
              \" class=\"toolSectionBody\" style=\"display: none;\">',\n '
              ',\n '
              '\n ].join('')),\n\n // a single tool's link in the tool panel; will load the tool form in the center panel\n tool_link : _.template([\n '',\n '<% _.each( labels, function( label ){ %>',\n '\">',\n '<%- label %>',\n '',\n '<% }); %>',\n '',\n ' tool-link\" href=\"<%= link %>\" target=\"<%- target %>\" minsizehint=\"<%- min_width %>\">',\n '<%- name %>',\n '',\n ' <%- description %>'\n ].join('')),\n\n // the tool form for entering tool parameters, viewing help and executing the tool\n // loaded when a tool link is clicked in the tool panel\n tool_form : _.template([\n '
              <%- tool.name %> (version <%- tool.version %>)
              ',\n '
              ',\n '<% _.each( tool.inputs, function( input ){ %>',\n '
              ',\n '',\n '
              ',\n '<%= input.html %>',\n '
              ',\n '
              ',\n '<%- input.help %>',\n '
              ',\n '
              ',\n '
              ',\n '<% }); %>',\n '
              ',\n '
              ',\n '',\n '
              ',\n '
              ',\n '
              <% tool.help %>
              ',\n '
              ',\n // TODO: we need scoping here because 'help' is the dom for the help menu in the masthead\n // which implies a leaky variable that I can't find\n ].join(''), { variable: 'tool' }),\n};\n\n\n// Exports\nreturn {\n ToolParameter: ToolParameter,\n IntegerToolParameter: IntegerToolParameter,\n SelectToolParameter: SelectToolParameter,\n Tool: Tool,\n ToolCollection: ToolCollection,\n ToolSearch: ToolSearch,\n ToolPanel: ToolPanel,\n ToolPanelView: ToolPanelView,\n ToolFormView: ToolFormView\n};\n\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/tool/tools.js\n ** module id = 44\n ** module chunks = 0 3\n **/","/** Renders the color picker used e.g. in the tool form **/\ndefine(['utils/utils'], function( Utils ) {\n return Backbone.View.extend({\n colors: {\n standard: ['c00000','ff0000','ffc000','ffff00','92d050','00b050','00b0f0','0070c0','002060','7030a0'],\n base : ['ffffff','000000','eeece1','1f497d','4f81bd','c0504d','9bbb59','8064a2','4bacc6','f79646'],\n theme :[['f2f2f2','7f7f7f','ddd9c3','c6d9f0','dbe5f1','f2dcdb','ebf1dd','e5e0ec','dbeef3','fdeada'],\n ['d8d8d8','595959','c4bd97','8db3e2','b8cce4','e5b9b7','d7e3bc','ccc1d9','b7dde8','fbd5b5'],\n ['bfbfbf','3f3f3f','938953','548dd4','95b3d7','d99694','c3d69b','b2a2c7','92cddc','fac08f'],\n ['a5a5a5','262626','494429','17365d','366092','953734','76923c','5f497a','31859b','e36c09'],\n ['7f7f7e','0c0c0c','1d1b10','0f243e','244061','632423','4f6128','3f3151','205867','974806']]\n },\n\n initialize : function( options ) {\n this.options = Utils.merge( options, {} );\n this.setElement( this._template() );\n this.$panel = this.$( '.ui-color-picker-panel' );\n this.$view = this.$( '.ui-color-picker-view' );\n this.$value = this.$( '.ui-color-picker-value' );\n this.$header = this.$( '.ui-color-picker-header' );\n this._build();\n this.visible = false;\n this.value( this.options.value );\n this.$boxes = this.$( '.ui-color-picker-box' );\n var self = this;\n this.$boxes.on( 'click', function() {\n self.value( $( this ).css( 'background-color' ) );\n self.$header.trigger( 'click' );\n } );\n this.$header.on( 'click', function() {\n self.visible = !self.visible;\n if ( self.visible ) {\n self.$view.fadeIn( 'fast' );\n } else {\n self.$view.fadeOut( 'fast' );\n }\n } );\n },\n\n /** Get/set value */\n value : function ( new_val ) {\n if ( new_val !== undefined && new_val !== null ) {\n this.$value.css( 'background-color', new_val );\n this.$( '.ui-color-picker-box' ).empty();\n this.$( this._getValue() ).html( this._templateCheck() );\n this.options.onchange && this.options.onchange( new_val );\n }\n return this._getValue();\n },\n\n /** Get value from dom */\n _getValue: function() {\n var rgb = this.$value.css( 'background-color' );\n rgb = rgb.match(/^rgb\\((\\d+),\\s*(\\d+),\\s*(\\d+)\\)$/);\n if ( rgb ) {\n function hex( x ) {\n return ( '0' + parseInt( x ).toString( 16 ) ).slice( -2 );\n }\n return '#' + hex( rgb[ 1] ) + hex( rgb[ 2 ] ) + hex( rgb[ 3 ] );\n } else {\n return null;\n }\n },\n\n /** Build color panel */\n _build: function() {\n var $content = this._content({\n label : 'Theme Colors',\n colors : this.colors.base,\n padding : 10\n });\n for ( var i in this.colors.theme ) {\n var line_def = {};\n if ( i == 0 ) {\n line_def[ 'bottom' ] = true;\n } else {\n if ( i != this.colors.theme.length - 1 ) {\n line_def[ 'top' ] = true;\n line_def[ 'bottom' ] = true;\n } else {\n line_def[ 'top' ] = true;\n line_def[ 'padding' ] = 5;\n }\n }\n line_def[ 'colors' ] = this.colors.theme[ i ];\n this._content( line_def );\n }\n this._content({\n label : 'Standard Colors',\n colors : this.colors.standard,\n padding : 5\n });\n },\n\n /** Create content */\n _content: function( options ) {\n var label = options.label;\n var colors = options.colors;\n var padding = options.padding;\n var top = options.top;\n var bottom = options.bottom;\n var $content = $( this._templateContent() );\n var $label = $content.find( '.label' );\n if ( options.label ) {\n $label.html( options.label );\n } else {\n $label.hide();\n }\n var $line = $content.find( '.line' );\n this.$panel.append( $content );\n for ( var i in colors ) {\n var $box = $( this._templateBox( colors[ i ] ) );\n if ( top ) {\n $box.css( 'border-top', 'none' );\n $box.css( 'border-top-left-radius', '0px' );\n $box.css( 'border-top-right-radius', '0px' );\n }\n if ( bottom ) {\n $box.css( 'border-bottom', 'none' );\n $box.css( 'border-bottom-left-radius', '0px' );\n $box.css( 'border-bottom-right-radius', '0px' );\n }\n $line.append( $box );\n }\n if (padding) {\n $line.css( 'padding-bottom', padding );\n }\n return $content;\n },\n\n /** Check icon */\n _templateCheck: function() {\n return '
              ';\n },\n\n /** Content template */\n _templateContent: function() {\n return '
              ' +\n '
              ' +\n '
              ' +\n '
              ';\n },\n\n /** Box template */\n _templateBox: function( color ) {\n return '
              ';\n },\n\n /** Main template */\n _template: function() {\n return '
              ' +\n '
              ' +\n '
              ' +\n '
              Select a color
              ' +\n '
              ' +\n '
              ' +\n '
              ' +\n '
              '\n '
              ';\n }\n });\n});\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/ui/ui-color-picker.js\n ** module id = 46\n ** module chunks = 0 3\n **/","/** This class creates/wraps a drill down element. */\ndefine([ 'utils/utils', 'mvc/ui/ui-options' ], function( Utils, Options ) {\n\nvar View = Options.BaseIcons.extend({\n initialize: function( options ) {\n options.type = options.display || 'checkbox';\n options.multiple = ( options.type == 'checkbox' );\n Options.BaseIcons.prototype.initialize.call( this, options );\n },\n\n /** Set states for selected values */\n _setValue: function ( new_value ) {\n Options.BaseIcons.prototype._setValue.call( this, new_value );\n if ( new_value !== undefined && new_value !== null && this.header_index ) {\n var self = this;\n var values = $.isArray( new_value ) ? new_value : [ new_value ];\n _.each( values, function( v ) {\n var list = self.header_index[ v ];\n _.each( list, function( element ) {\n self._setState( element, true );\n });\n });\n }\n },\n\n /** Expand/collapse a sub group */\n _setState: function ( header_id, is_expanded ) {\n var $button = this.$( '.button-' + header_id );\n var $subgroup = this.$( '.subgroup-' + header_id );\n $button.data( 'is_expanded', is_expanded );\n if ( is_expanded ) {\n $subgroup.show();\n $button.removeClass( 'fa-plus-square' ).addClass( 'fa-minus-square' );\n } else {\n $subgroup.hide();\n $button.removeClass( 'fa-minus-square' ).addClass( 'fa-plus-square' );\n }\n },\n\n /** Template to create options tree */\n _templateOptions: function() {\n var self = this;\n this.header_index = {};\n\n // attach event handler\n function attach( $el, header_id ) {\n var $button = $el.find( '.button-' + header_id );\n $button.on( 'click', function() {\n self._setState( header_id, !$button.data( 'is_expanded' ) );\n });\n }\n\n // recursive function which iterates through options\n function iterate ( $tmpl, options, header ) {\n header = header || [];\n for ( i in options ) {\n var level = options[ i ];\n var has_options = level.options && level.options.length > 0;\n var new_header = header.slice( 0 );\n self.header_index[ level.value ] = new_header.slice( 0 );\n var $group = $( '
              ' );\n if ( has_options ) {\n var header_id = Utils.uid();\n var $button = $( '' ).addClass( 'button-' + header_id ).addClass( 'ui-drilldown-button fa fa-plus-square' );\n var $subgroup = $( '
              ' ).addClass( 'subgroup-' + header_id ).addClass( 'ui-drilldown-subgroup' );\n $group.append( $( '
              ' )\n .append( $button )\n .append( self._templateOption( { label: level.name, value: level.value } ) ) );\n new_header.push( header_id );\n iterate ( $subgroup, level.options, new_header );\n $group.append( $subgroup );\n attach( $group, header_id );\n } else {\n $group.append( self._templateOption( { label: level.name, value: level.value } ) );\n }\n $tmpl.append( $group );\n }\n }\n\n // iterate through options and create dom\n var $tmpl = $( '
              ' );\n iterate( $tmpl, this.model.get( 'data' ) );\n return $tmpl;\n },\n\n /** Template for drill down view */\n _template: function() {\n return $( '
              ' ).addClass( 'ui-options-list drilldown-container' ).attr( 'id', this.model.id );\n }\n});\n\nreturn {\n View: View\n}\n\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/ui/ui-drilldown.js\n ** module id = 47\n ** module chunks = 0 3\n **/","define([ 'utils/utils', 'mvc/ui/ui-misc', 'mvc/ui/ui-select-default' ], function( Utils, Ui, Select ) {\n\n/** Batch mode variations */\nvar Batch = { DISABLED: 'disabled', ENABLED: 'enabled', LINKED: 'linked' };\n\n/** List of available content selectors options */\nvar Configurations = {\n data: [\n { src: 'hda', icon: 'fa-file-o', tooltip: 'Single dataset', multiple: false, batch: Batch.DISABLED },\n { src: 'hda', icon: 'fa-files-o', tooltip: 'Multiple datasets', multiple: true, batch: Batch.LINKED },\n { src: 'hdca', icon: 'fa-folder-o', tooltip: 'Dataset collection', multiple: false, batch: Batch.LINKED } ],\n data_multiple: [\n { src: 'hda', icon: 'fa-files-o', tooltip: 'Multiple datasets', multiple: true, batch: Batch.DISABLED },\n { src: 'hdca', icon: 'fa-folder-o', tooltip: 'Dataset collection', multiple: false, batch: Batch.DISABLED } ],\n data_collection: [\n { src: 'hdca', icon: 'fa-folder-o', tooltip: 'Dataset collection', multiple: false, batch: Batch.DISABLED } ],\n workflow_data: [\n { src: 'hda', icon: 'fa-file-o', tooltip: 'Single dataset', multiple: false, batch: Batch.DISABLED } ],\n workflow_data_multiple: [\n { src: 'hda', icon: 'fa-files-o', tooltip: 'Multiple datasets', multiple: true, batch: Batch.DISABLED } ],\n workflow_data_collection: [\n { src: 'hdca', icon: 'fa-folder-o', tooltip: 'Dataset collection', multiple: false, batch: Batch.DISABLED } ],\n module_data: [\n { src: 'hda', icon: 'fa-file-o', tooltip: 'Single dataset', multiple: false, batch: Batch.DISABLED },\n { src: 'hda', icon: 'fa-files-o', tooltip: 'Multiple datasets', multiple: true, batch: Batch.ENABLED } ],\n module_data_collection: [\n { src: 'hdca', icon: 'fa-folder-o', tooltip: 'Dataset collection', multiple: false, batch: Batch.DISABLED },\n { src: 'hdca', icon: 'fa-folder', tooltip: 'Multiple collections', multiple: true, batch: Batch.ENABLED } ]\n};\n\n/** View for hda and hdca content selector ui elements */\nvar View = Backbone.View.extend({\n initialize : function( options ) {\n var self = this;\n this.model = options && options.model || new Backbone.Model({\n src_labels : { 'hda' : 'dataset', 'hdca': 'dataset collection' },\n pagelimit : 100\n }).set( options );\n this.setElement( $( '
              ' ).addClass( 'ui-select-content' ) );\n this.button_product = new Ui.RadioButton.View( {\n value : 'false',\n data : [ { icon: 'fa fa-chain', value: 'false',\n tooltip: 'Linked inputs will be run in matched order with other datasets e.g. use this for matching forward and reverse reads.' },\n { icon: 'fa fa-chain-broken', value: 'true',\n tooltip: 'Unlinked dataset inputs will be run against *all* other inputs.' } ] } );\n var $batch_div = $( '
              ' ).addClass( 'ui-form-info' )\n .append( $( '' ).addClass( 'fa fa-sitemap' ) )\n .append( $( '' ).html( 'This is a batch mode input field. Separate jobs will be triggered for each dataset selection.' ) );\n this.$batch = {\n linked : $batch_div.clone(),\n enabled : $batch_div.clone().append( $( '
              ' )\n .append( $( '
              ' ).addClass( 'ui-form-title' ).html( 'Batch options:' ) )\n .append( this.button_product.$el ) )\n .append( $( '
              ' ).css( 'clear', 'both' ) )\n };\n\n // track current history elements\n this.history = {};\n\n // add listeners\n this.listenTo( this.model, 'change:data', this._changeData, this );\n this.listenTo( this.model, 'change:wait', this._changeWait, this );\n this.listenTo( this.model, 'change:current', this._changeCurrent, this );\n this.listenTo( this.model, 'change:value', this._changeValue, this );\n this.listenTo( this.model, 'change:type change:optional change:multiple change:extensions', this._changeType, this );\n this.render();\n\n // add change event\n this.on( 'change', function() { options.onchange && options.onchange( self.value() ) } );\n },\n\n render: function() {\n this._changeType();\n this._changeValue();\n this._changeWait();\n },\n\n /** Indicate that select fields are being updated */\n wait: function() {\n this.model.set( 'wait', true );\n },\n\n /** Indicate that the options update has been completed */\n unwait: function() {\n this.model.set( 'wait', false );\n },\n\n /** Update data representing selectable options */\n update: function( options ) {\n this.model.set( 'data', options );\n },\n\n /** Return the currently selected dataset values */\n value: function ( new_value ) {\n new_value !== undefined && this.model.set( 'value', new_value );\n var current = this.model.get( 'current' );\n if ( this.config[ current ] ) {\n var id_list = this.fields[ current ].value();\n if (id_list !== null) {\n id_list = $.isArray( id_list ) ? id_list : [ id_list ];\n if ( id_list.length > 0 ) {\n var result = this._batch( { values: [] } );\n for ( var i in id_list ) {\n var details = this.history[ id_list[ i ] + '_' + this.config[ current ].src ];\n if ( details ) {\n result.values.push( details );\n } else {\n Galaxy.emit.debug( 'ui-select-content::value()', 'Requested details not found for \\'' + id_list[ i ] + '\\'.' );\n return null;\n }\n }\n result.values.sort( function( a, b ) { return a.hid - b.hid } );\n return result;\n }\n }\n } else {\n Galaxy.emit.debug( 'ui-select-content::value()', 'Invalid value/source \\'' + new_value + '\\'.' );\n }\n return null;\n },\n\n /** Change of current select field */\n _changeCurrent: function() {\n var self = this;\n _.each( this.fields, function( field, i ) {\n if ( self.model.get( 'current' ) == i ) {\n field.$el.show();\n _.each( self.$batch, function( $batchfield, batchmode ) {\n $batchfield[ self.config[ i ].batch == batchmode ? 'show' : 'hide' ]();\n });\n self.button_type.value( i );\n } else {\n field.$el.hide();\n }\n });\n },\n\n /** Change of type */\n _changeType: function() {\n var self = this;\n\n // identify selector type identifier i.e. [ flavor ]_[ type ]_[ multiple ]\n var config_id = ( this.model.get( 'flavor' ) ? this.model.get( 'flavor' ) + '_' : '' ) +\n String( this.model.get( 'type' ) ) + ( this.model.get( 'multiple' ) ? '_multiple' : '' );\n if ( Configurations[ config_id ] ) {\n this.config = Configurations[ config_id ];\n } else {\n this.config = Configurations[ 'data' ];\n Galaxy.emit.debug( 'ui-select-content::_changeType()', 'Invalid configuration/type id \\'' + config_id + '\\'.' );\n }\n\n // prepare extension component of error message\n var data = self.model.get( 'data' );\n var extensions = Utils.textify( this.model.get( 'extensions' ) );\n var src_labels = this.model.get( 'src_labels' );\n\n // build views\n this.fields = [];\n this.button_data = [];\n _.each( this.config, function( c, i ) {\n self.button_data.push({\n value : i,\n icon : c.icon,\n tooltip : c.tooltip\n });\n self.fields.push(\n new Select.View({\n optional : self.model.get( 'optional' ),\n multiple : c.multiple,\n searchable : !c.multiple || ( data && data[ c.src ] && data[ c.src ].length > self.model.get( 'pagelimit' ) ),\n selectall : false,\n error_text : 'No ' + ( extensions ? extensions + ' ' : '' ) + ( src_labels[ c.src ] || 'content' ) + ' available.',\n onchange : function() {\n self.trigger( 'change' );\n }\n })\n );\n });\n this.button_type = new Ui.RadioButton.View({\n value : this.model.get( 'current' ),\n data : this.button_data,\n onchange: function( value ) {\n self.model.set( 'current', value );\n self.trigger( 'change' );\n }\n });\n\n // append views\n this.$el.empty();\n var button_width = 0;\n if ( this.fields.length > 1 ) {\n this.$el.append( this.button_type.$el );\n button_width = Math.max( 0, this.fields.length * 36 ) + 'px';\n }\n _.each( this.fields, function( field ) {\n self.$el.append( field.$el.css( { 'margin-left': button_width } ) );\n });\n _.each( this.$batch, function( $batchfield, batchmode ) {\n self.$el.append( $batchfield.css( { 'margin-left': button_width } ) );\n });\n this.model.set( 'current', 0 );\n this._changeCurrent();\n this._changeData();\n },\n\n /** Change of wait flag */\n _changeWait: function() {\n var self = this;\n _.each( this.fields, function( field ) { field[ self.model.get( 'wait' ) ? 'wait' : 'unwait' ]() } );\n },\n\n /** Change of available options */\n _changeData: function() {\n var options = this.model.get( 'data' );\n var self = this;\n var select_options = {};\n _.each( options, function( items, src ) {\n select_options[ src ] = [];\n _.each( items, function( item ) {\n select_options[ src ].push({\n hid : item.hid,\n keep : item.keep,\n label: item.hid + ': ' + item.name,\n value: item.id\n });\n self.history[ item.id + '_' + src ] = item;\n });\n });\n _.each( this.config, function( c, i ) {\n select_options[ c.src ] && self.fields[ i ].add( select_options[ c.src ], function( a, b ) { return b.hid - a.hid } );\n });\n },\n\n /** Change of incoming value */\n _changeValue: function () {\n var new_value = this.model.get( 'value' );\n if ( new_value && new_value.values && new_value.values.length > 0 ) {\n // create list with content ids\n var list = [];\n _.each( new_value.values, function( value ) {\n list.push( value.id );\n });\n // sniff first suitable field type from config list\n var src = new_value.values[ 0 ].src;\n var multiple = new_value.values.length > 1;\n for( var i = 0; i < this.config.length; i++ ) {\n var field = this.fields[ i ];\n var c = this.config[ i ];\n if ( c.src == src && [ multiple, true ].indexOf( c.multiple ) !== -1 ) {\n this.model.set( 'current', i );\n field.value( list );\n break;\n }\n }\n } else {\n _.each( this.fields, function( field ) {\n field.value( null );\n });\n }\n },\n\n /** Assists in identifying the batch mode */\n _batch: function( result ) {\n result[ 'batch' ] = false;\n var current = this.model.get( 'current' );\n var config = this.config[ current ];\n if ( config.src == 'hdca' && !config.multiple ) {\n var hdca = this.history[ this.fields[ current ].value() + '_hdca' ];\n if ( hdca && hdca.map_over_type ) {\n result[ 'batch' ] = true;\n }\n }\n if ( config.batch == Batch.LINKED || config.batch == Batch.ENABLED ) {\n result[ 'batch' ] = true;\n if ( config.batch == Batch.ENABLED && this.button_product.value() === 'true' ) {\n result[ 'product' ] = true;\n }\n }\n return result;\n }\n});\n\nreturn {\n View: View\n}\n\n});\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/ui/ui-select-content.js\n ** module id = 48\n ** module chunks = 0 3\n **/","// dependencies\ndefine(['utils/utils', 'mvc/ui/ui-list'],\n function(Utils, List) {\n\n/**\n * FTP file selector\n */\nvar View = Backbone.View.extend({\n // initialize\n initialize : function(options) {\n // link this\n var self = this;\n\n // create ui-list view to keep track of selected ftp files\n this.ftpfile_list = new List.View({\n name : 'file',\n optional : options.optional,\n multiple : options.multiple,\n onchange : function() {\n options.onchange && options.onchange(self.value());\n }\n });\n\n // create elements\n this.setElement(this.ftpfile_list.$el);\n\n // initial fetch of ftps\n Utils.get({\n url : Galaxy.root + 'api/remote_files',\n success : function(response) {\n var data = [];\n for (var i in response) {\n data.push({\n value : response[i]['path'],\n label : response[i]['path']\n });\n }\n self.ftpfile_list.update(data);\n }\n });\n },\n\n /** Return/Set currently selected ftp datasets */\n value: function(val) {\n return this.ftpfile_list.value(val);\n }\n});\n\nreturn {\n View: View\n}\n\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/ui/ui-select-ftp.js\n ** module id = 49\n ** module chunks = 0 3\n **/","// dependencies\ndefine(['utils/utils', 'mvc/ui/ui-misc', 'mvc/ui/ui-table', 'mvc/ui/ui-list'],\n function(Utils, Ui, Table, List) {\n\n// collection of libraries\nvar Libraries = Backbone.Collection.extend({\n url: Galaxy.root + 'api/libraries?deleted=false'\n});\n\n// collection of dataset\nvar LibraryDatasets = Backbone.Collection.extend({\n initialize: function() {\n var self = this;\n this.config = new Backbone.Model({ library_id: null });\n this.config.on('change', function() {\n self.fetch({ reset: true });\n });\n },\n url: function() {\n return Galaxy.root + 'api/libraries/' + this.config.get('library_id') + '/contents';\n }\n});\n\n// hda/hdca content selector ui element\nvar View = Backbone.View.extend({\n // initialize\n initialize : function(options) {\n // link this\n var self = this;\n\n // collections\n this.libraries = new Libraries();\n this.datasets = new LibraryDatasets();\n\n // link app and options\n this.options = options;\n\n // select field for the library\n // TODO: Remove this once the library API supports searching for library datasets\n this.library_select = new Ui.Select.View({\n onchange : function(value) {\n self.datasets.config.set('library_id', value);\n }\n });\n\n // create ui-list view to keep track of selected data libraries\n this.dataset_list = new List.View({\n name : 'dataset',\n optional : options.optional,\n multiple : options.multiple,\n onchange : function() {\n self.trigger('change');\n }\n });\n\n // add reset handler for fetched libraries\n this.libraries.on('reset', function() {\n var data = [];\n self.libraries.each(function(model) {\n data.push({\n value : model.id,\n label : model.get('name')\n });\n });\n self.library_select.update(data);\n });\n\n // add reset handler for fetched library datasets\n this.datasets.on('reset', function() {\n var data = [];\n var library_current = self.library_select.text();\n if (library_current !== null) {\n self.datasets.each(function(model) {\n if (model.get('type') === 'file') {\n data.push({\n value : model.id,\n label : model.get('name')\n });\n }\n });\n }\n self.dataset_list.update(data);\n });\n\n // add change event. fires on trigger\n this.on('change', function() {\n options.onchange && options.onchange(self.value());\n });\n\n // create elements\n this.setElement(this._template());\n this.$('.library-select').append(this.library_select.$el);\n this.$el.append(this.dataset_list.$el);\n\n // initial fetch of libraries\n this.libraries.fetch({\n reset: true,\n success: function() {\n self.library_select.trigger('change');\n if (self.options.value !== undefined) {\n self.value(self.options.value);\n }\n }\n });\n },\n\n /** Return/Set currently selected library datasets */\n value: function(val) {\n return this.dataset_list.value(val);\n },\n\n /** Template */\n _template: function() {\n return '
              ' +\n '
              ' +\n 'Select Library' +\n '' +\n '
              ' +\n '
              ';\n }\n});\n\nreturn {\n View: View\n}\n\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/ui/ui-select-library.js\n ** module id = 50\n ** module chunks = 0 3\n **/","define([ 'utils/utils' ], function( Utils ) {\nvar View = Backbone.View.extend({\n initialize : function( options ) {\n var self = this;\n this.options = Utils.merge( options, {\n id : Utils.uid(),\n min : null,\n max : null,\n step : null,\n precise : false,\n split : 10000\n } );\n\n // create new element\n this.setElement( this._template( this.options ) );\n\n // determine wether to use the slider\n this.useslider = this.options.max !== null && this.options.min !== null && this.options.max > this.options.min;\n\n // set default step size\n if ( this.options.step === null ) {\n this.options.step = 1.0;\n if ( this.options.precise && this.useslider ) {\n this.options.step = ( this.options.max - this.options.min ) / this.options.split;\n }\n }\n\n // create slider if min and max are defined properly\n if ( this.useslider ) {\n this.$slider = this.$( '#slider' );\n this.$slider.slider( this.options );\n this.$slider.on( 'slide', function ( event, ui ) {\n self.value( ui.value );\n });\n } else {\n this.$( '.ui-form-slider-text' ).css( 'width', '100%' );\n }\n\n // link text input field\n this.$text = this.$( '#text' );\n\n // set initial value\n this.options.value !== undefined && ( this.value( this.options.value ) );\n\n // add text field event\n var pressed = [];\n this.$text.on( 'change', function () {\n self.value( $( this ).val() );\n });\n this.$text.on( 'keyup', function( e ) {\n pressed[e.which] = false;\n self.options.onchange && self.options.onchange( $( this ).val() );\n });\n this.$text.on( 'keydown', function ( e ) {\n var v = e.which;\n pressed[ v ] = true;\n if ( self.options.is_workflow && pressed[ 16 ] && v == 52 ) {\n self.value( '$' )\n event.preventDefault();\n } else if (!( v == 8 || v == 9 || v == 13 || v == 37 || v == 39 || ( v >= 48 && v <= 57 && !pressed[ 16 ] ) || ( v >= 96 && v <= 105 )\n || ( ( v == 190 || v == 110 ) && $( this ).val().indexOf( '.' ) == -1 && self.options.precise )\n || ( ( v == 189 || v == 109 ) && $( this ).val().indexOf( '-' ) == -1 )\n || self._isParameter( $( this ).val() )\n || pressed[ 91 ] || pressed[ 17 ] ) ) {\n event.preventDefault();\n }\n });\n },\n\n /** Set and Return the current value\n */\n value : function ( new_val ) {\n if ( new_val !== undefined ) {\n if ( new_val !== null && new_val !== '' && !this._isParameter( new_val ) ) {\n isNaN( new_val ) && ( new_val = 0 );\n this.options.max !== null && ( new_val = Math.min( new_val, this.options.max ) );\n this.options.min !== null && ( new_val = Math.max( new_val, this.options.min ) );\n }\n this.$slider && this.$slider.slider( 'value', new_val );\n this.$text.val( new_val );\n this.options.onchange && this.options.onchange( new_val );\n }\n return this.$text.val();\n },\n\n /** Return true if the field contains a workflow parameter i.e. $('name')\n */\n _isParameter: function( value ) {\n return this.options.is_workflow && String( value ).substring( 0, 1 ) === '$';\n },\n\n /** Slider template\n */\n _template: function( options ) {\n return '
              ' +\n '' +\n '
              ' +\n '
              ';\n }\n});\n\nreturn {\n View : View\n};\n});\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/ui/ui-slider.js\n ** module id = 51\n ** module chunks = 0 3\n **/","// dependencies\ndefine(['utils/utils'], function(Utils) {\n\n/**\n * This class creates a ui table element.\n */\nvar View = Backbone.View.extend({\n // current row\n row: null,\n \n // count rows\n row_count: 0,\n \n // defaults options\n optionsDefault: {\n content : 'No content available.',\n onchange : null,\n ondblclick : null,\n onconfirm : null,\n cls : 'ui-table',\n cls_tr : ''\n },\n \n // events\n events : {\n 'click' : '_onclick',\n 'dblclick' : '_ondblclick'\n },\n \n // initialize\n initialize : function(options) {\n // configure options\n this.options = Utils.merge(options, this.optionsDefault);\n \n // create new element\n var $el = $(this._template(this.options));\n \n // link sub-elements\n this.$thead = $el.find('thead');\n this.$tbody = $el.find('tbody');\n this.$tmessage = $el.find('tmessage');\n \n // set element\n this.setElement($el);\n \n // initialize row\n this.row = this._row();\n },\n \n // add header cell\n addHeader: function($el) {\n var wrapper = $('
              ' +\n '' +\n '' +\n '
              ' +\n '' + options.content + '' +\n '
              ';\n }\n});\n\nreturn {\n View: View\n}\n\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/ui/ui-table.js\n ** module id = 52\n ** module chunks = 0 3\n **/","define( [], function() {\n var Model = Backbone.Model.extend({\n defaults: {\n extension : 'auto',\n genome : '?',\n url_paste : '',\n status : 'init',\n info : null,\n file_name : '',\n file_mode : '',\n file_size : 0,\n file_type : null,\n file_path : '',\n file_data : null,\n percentage : 0,\n space_to_tab : false,\n to_posix_lines : true,\n enabled : true\n },\n reset: function( attr ) {\n this.clear().set( this.defaults ).set( attr );\n }\n });\n var Collection = Backbone.Collection.extend( { model: Model } );\n return { Model: Model, Collection : Collection };\n});\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/upload/upload-model.js\n ** module id = 53\n ** module chunks = 3\n **/","/**\n * This class defines a queue to ensure that multiple deferred callbacks are executed sequentially.\n */\ndefine(['utils/utils'], function( Utils ) {\nreturn Backbone.Model.extend({\n initialize: function(){\n this.active = {};\n this.last = null;\n },\n\n /** Adds a callback to the queue. Upon execution a deferred object is parsed to the callback i.e. callback( deferred ).\n * If the callback does not take any arguments, the deferred is resolved instantly.\n */\n execute: function( callback ) {\n var self = this;\n var id = Utils.uid();\n var has_deferred = callback.length > 0;\n\n // register process\n this.active[ id ] = true;\n\n // deferred process\n var process = $.Deferred();\n process.promise().always(function() {\n delete self.active[ id ];\n has_deferred && Galaxy.emit.debug( 'deferred::execute()', this.state().charAt(0).toUpperCase() + this.state().slice(1) + ' ' + id );\n });\n\n // deferred queue\n $.when( this.last ).always(function() {\n if ( self.active[ id ] ) {\n has_deferred && Galaxy.emit.debug( 'deferred::execute()', 'Running ' + id );\n callback( process );\n !has_deferred && process.resolve();\n } else {\n process.reject();\n }\n });\n this.last = process.promise();\n },\n\n /** Resets the promise queue. All currently queued but unexecuted callbacks/promises will be rejected.\n */\n reset: function() {\n Galaxy.emit.debug('deferred::execute()', 'Reset');\n for ( var i in this.active ) {\n this.active[ i ] = false;\n }\n },\n\n /** Returns true if all processes are done.\n */\n ready: function() {\n return $.isEmptyObject( this.active );\n }\n});\n\n});\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/utils/deferred.js\n ** module id = 55\n ** module chunks = 0 3\n **/","define([\n \"mvc/base-mvc\",\n \"utils/localization\",\n \"ui/editable-text\",\n], function( baseMVC, _l ){\n// =============================================================================\n/** A view on any model that has a 'annotation' attribute\n */\nvar AnnotationEditor = Backbone.View\n .extend( baseMVC.LoggableMixin )\n .extend( baseMVC.HiddenUntilActivatedViewMixin ).extend({\n\n tagName : 'div',\n className : 'annotation-display',\n\n /** Set up listeners, parse options */\n initialize : function( options ){\n options = options || {};\n this.tooltipConfig = options.tooltipConfig || { placement: 'bottom' };\n //console.debug( this, options );\n // only listen to the model only for changes to annotations\n this.listenTo( this.model, 'change:annotation', function(){\n this.render();\n });\n this.hiddenUntilActivated( options.$activator, options );\n },\n\n /** Build the DOM elements, call select to on the created input, and set up behaviors */\n render : function(){\n var view = this;\n this.$el.html( this._template() );\n\n //TODO: handle empties better\n this.$annotation().make_text_editable({\n use_textarea: true,\n on_finish: function( newAnnotation ){\n view.$annotation().text( newAnnotation );\n view.model.save({ annotation: newAnnotation }, { silent: true })\n .fail( function(){\n view.$annotation().text( view.model.previous( 'annotation' ) );\n });\n }\n });\n return this;\n },\n\n /** @returns {String} the html text used to build the view's DOM */\n _template : function(){\n var annotation = this.model.get( 'annotation' );\n return [\n //TODO: make prompt optional\n '',\n // set up initial tags by adding as CSV to input vals (necc. to init select2)\n '
              ',\n _.escape( annotation ),\n '
              '\n ].join( '' );\n },\n\n /** @returns {jQuery} the main element for this view */\n $annotation : function(){\n return this.$el.find( '.annotation' );\n },\n\n /** shut down event listeners and remove this view's DOM */\n remove : function(){\n this.$annotation.off();\n this.stopListening( this.model );\n Backbone.View.prototype.remove.call( this );\n },\n\n /** string rep */\n toString : function(){ return [ 'AnnotationEditor(', this.model + '', ')' ].join(''); }\n});\n// =============================================================================\nreturn {\n AnnotationEditor : AnnotationEditor\n};\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/annotation.js\n ** module id = 66\n ** module chunks = 3\n **/","define([\n 'libs/underscore',\n 'libs/backbone',\n 'mvc/base-mvc',\n], function( _, Backbone, BASE_MVC ){\n'use strict';\n\n//=============================================================================\n/**\n * A Collection that can be limited/offset/re-ordered/filtered.\n * @type {Backbone.Collection}\n */\nvar ControlledFetchCollection = Backbone.Collection.extend({\n\n /** call setOrder on initialization to build the comparator based on options */\n initialize : function( models, options ){\n Backbone.Collection.prototype.initialize.call( this, models, options );\n this.setOrder( options.order || this.order, { silent: true });\n },\n\n /** set up to track order changes and re-sort when changed */\n _setUpListeners : function(){\n return this.on({\n 'changed-order' : this.sort\n });\n },\n\n /** override to provide order and offsets based on instance vars, set limit if passed,\n * and set allFetched/fire 'all-fetched' when xhr returns\n */\n fetch : function( options ){\n options = this._buildFetchOptions( options );\n // console.log( 'fetch options:', options );\n return Backbone.Collection.prototype.fetch.call( this, options );\n },\n\n /** build ajax data/parameters from options */\n _buildFetchOptions : function( options ){\n // note: we normally want options passed in to override the defaults built here\n // so most of these fns will generate defaults\n options = _.clone( options ) || {};\n var self = this;\n\n // jquery ajax option; allows multiple q/qv for filters (instead of 'q[]')\n options.traditional = true;\n\n // options.data\n // we keep limit, offset, etc. in options *as well as move it into data* because:\n // - it makes fetch calling convenient to add it to a single options map (instead of as mult. args)\n // - it allows the std. event handlers (for fetch, etc.) to have access\n // to the pagination options too\n // (i.e. this.on( 'sync', function( options ){ if( options.limit ){ ... } }))\n // however, when we send to xhr/jquery we copy them to data also so that they become API query params\n options.data = options.data || self._buildFetchData( options );\n // console.log( 'data:', options.data );\n\n // options.data.filters --> options.data.q, options.data.qv\n var filters = this._buildFetchFilters( options );\n // console.log( 'filters:', filters );\n if( !_.isEmpty( filters ) ){\n _.extend( options.data, this._fetchFiltersToAjaxData( filters ) );\n }\n // console.log( 'data:', options.data );\n return options;\n },\n\n /** Build the dictionary to send to fetch's XHR as data */\n _buildFetchData : function( options ){\n var defaults = {};\n if( this.order ){ defaults.order = this.order; }\n return _.defaults( _.pick( options, this._fetchParams ), defaults );\n },\n\n /** These attribute keys are valid params to fetch/API-index */\n _fetchParams : [\n /** model dependent string to control the order of models returned */\n 'order',\n /** limit the number of models returned from a fetch */\n 'limit',\n /** skip this number of models when fetching */\n 'offset',\n /** what series of attributes to return (model dependent) */\n 'view',\n /** individual keys to return for the models (see api/histories.index) */\n 'keys'\n ],\n\n /** add any needed filters here based on collection state */\n _buildFetchFilters : function( options ){\n // override\n return _.clone( options.filters || {} );\n },\n\n /** Convert dictionary filters to qqv style arrays */\n _fetchFiltersToAjaxData : function( filters ){\n // return as a map so ajax.data can extend from it\n var filterMap = {\n q : [],\n qv : []\n };\n _.each( filters, function( v, k ){\n // don't send if filter value is empty\n if( v === undefined || v === '' ){ return; }\n // json to python\n if( v === true ){ v = 'True'; }\n if( v === false ){ v = 'False'; }\n if( v === null ){ v = 'None'; }\n // map to k/v arrays (q/qv)\n filterMap.q.push( k );\n filterMap.qv.push( v );\n });\n return filterMap;\n },\n\n /** override to reset allFetched flag to false */\n reset : function( models, options ){\n this.allFetched = false;\n return Backbone.Collection.prototype.reset.call( this, models, options );\n },\n\n // ........................................................................ order\n order : null,\n\n /** @type {Object} map of collection available sorting orders containing comparator fns */\n comparators : {\n 'update_time' : BASE_MVC.buildComparator( 'update_time', { ascending: false }),\n 'update_time-asc' : BASE_MVC.buildComparator( 'update_time', { ascending: true }),\n 'create_time' : BASE_MVC.buildComparator( 'create_time', { ascending: false }),\n 'create_time-asc' : BASE_MVC.buildComparator( 'create_time', { ascending: true }),\n },\n\n /** set the order and comparator for this collection then sort with the new order\n * @event 'changed-order' passed the new order and the collection\n */\n setOrder : function( order, options ){\n options = options || {};\n var collection = this;\n var comparator = collection.comparators[ order ];\n if( _.isUndefined( comparator ) ){ throw new Error( 'unknown order: ' + order ); }\n // if( _.isUndefined( comparator ) ){ return; }\n if( comparator === collection.comparator ){ return; }\n\n var oldOrder = collection.order;\n collection.order = order;\n collection.comparator = comparator;\n\n if( !options.silent ){\n collection.trigger( 'changed-order', options );\n }\n return collection;\n },\n\n});\n\n\n//=============================================================================\n/**\n *\n */\nvar PaginatedCollection = ControlledFetchCollection.extend({\n\n /** @type {Number} limit used for each page's fetch */\n limitPerPage : 500,\n\n initialize : function( models, options ){\n ControlledFetchCollection.prototype.initialize.call( this, models, options );\n this.currentPage = options.currentPage || 0;\n },\n\n getTotalItemCount : function(){\n return this.length;\n },\n\n shouldPaginate : function(){\n return this.getTotalItemCount() >= this.limitPerPage;\n },\n\n getLastPage : function(){\n return Math.floor( this.getTotalItemCount() / this.limitPerPage );\n },\n\n getPageCount : function(){\n return this.getLastPage() + 1;\n },\n\n getPageLimitOffset : function( pageNum ){\n pageNum = this.constrainPageNum( pageNum );\n return {\n limit : this.limitPerPage,\n offset: pageNum * this.limitPerPage\n };\n },\n\n constrainPageNum : function( pageNum ){\n return Math.max( 0, Math.min( pageNum, this.getLastPage() ));\n },\n\n /** fetch the next page of data */\n fetchPage : function( pageNum, options ){\n var self = this;\n pageNum = self.constrainPageNum( pageNum );\n self.currentPage = pageNum;\n options = _.defaults( options || {}, self.getPageLimitOffset( pageNum ) );\n\n self.trigger( 'fetching-more' );\n return self.fetch( options )\n .always( function(){\n self.trigger( 'fetching-more-done' );\n });\n },\n\n fetchCurrentPage : function( options ){\n return this.fetchPage( this.currentPage, options );\n },\n\n fetchPrevPage : function( options ){\n return this.fetchPage( this.currentPage - 1, options );\n },\n\n fetchNextPage : function( options ){\n return this.fetchPage( this.currentPage + 1, options );\n },\n});\n\n\n//=============================================================================\n/**\n * A Collection that will load more elements without reseting.\n */\nvar InfinitelyScrollingCollection = ControlledFetchCollection.extend({\n\n /** @type {Number} limit used for the first fetch (or a reset) */\n limitOnFirstFetch : null,\n /** @type {Number} limit used for each subsequent fetch */\n limitPerFetch : 100,\n\n initialize : function( models, options ){\n ControlledFetchCollection.prototype.initialize.call( this, models, options );\n /** @type {Integer} number of contents to return from the first fetch */\n this.limitOnFirstFetch = options.limitOnFirstFetch || this.limitOnFirstFetch;\n /** @type {Integer} limit for every fetch after the first */\n this.limitPerFetch = options.limitPerFetch || this.limitPerFetch;\n /** @type {Boolean} are all contents fetched? */\n this.allFetched = false;\n /** @type {Integer} what was the offset of the last content returned */\n this.lastFetched = options.lastFetched || 0;\n },\n\n /** build ajax data/parameters from options */\n _buildFetchOptions : function( options ){\n // options (options for backbone.fetch and jquery.ajax generally)\n // backbone option; false here to make fetching an addititive process\n options.remove = options.remove || false;\n return ControlledFetchCollection.prototype._buildFetchOptions.call( this, options );\n },\n\n /** fetch the first 'page' of data */\n fetchFirst : function( options ){\n // console.log( 'ControlledFetchCollection.fetchFirst:', options );\n options = options? _.clone( options ) : {};\n this.allFetched = false;\n this.lastFetched = 0;\n return this.fetchMore( _.defaults( options, {\n reset : true,\n limit : this.limitOnFirstFetch,\n }));\n },\n\n /** fetch the next page of data */\n fetchMore : function( options ){\n // console.log( 'ControlledFetchCollection.fetchMore:', options );\n options = _.clone( options || {} );\n var collection = this;\n\n // console.log( 'fetchMore, options.reset:', options.reset );\n if( ( !options.reset && collection.allFetched ) ){\n return jQuery.when();\n }\n\n // TODO: this fails in the edge case where\n // the first fetch offset === limit (limit 4, offset 4, collection.length 4)\n options.offset = options.reset? 0 : ( options.offset || collection.lastFetched );\n var limit = options.limit = options.limit || collection.limitPerFetch || null;\n // console.log( 'fetchMore, limit:', limit, 'offset:', options.offset );\n\n collection.trigger( 'fetching-more' );\n return collection.fetch( options )\n .always( function(){\n collection.trigger( 'fetching-more-done' );\n })\n // maintain allFetched flag and trigger if all were fetched this time\n .done( function _postFetchMore( fetchedData ){\n var numFetched = _.isArray( fetchedData )? fetchedData.length : 0;\n collection.lastFetched += numFetched;\n // console.log( 'fetchMore, lastFetched:', collection.lastFetched );\n // anything less than a full page means we got all there is to get\n if( !limit || numFetched < limit ){\n collection.allFetched = true;\n collection.trigger( 'all-fetched', this );\n }\n }\n );\n },\n\n /** fetch all the collection */\n fetchAll : function( options ){\n // whitelist options to prevent allowing limit/offset/filters\n // (use vanilla fetch instead)\n options = options || {};\n var self = this;\n options = _.pick( options, 'silent' );\n options.filters = {};\n return self.fetch( options ).done( function( fetchData ){\n self.allFetched = true;\n self.trigger( 'all-fetched', self );\n });\n },\n});\n\n\n//==============================================================================\n return {\n ControlledFetchCollection : ControlledFetchCollection,\n PaginatedCollection : PaginatedCollection,\n InfinitelyScrollingCollection : InfinitelyScrollingCollection,\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/base/controlled-fetch-collection.js\n ** module id = 67\n ** module chunks = 3\n **/","define([\n \"mvc/list/list-view\",\n \"mvc/collection/collection-model\",\n \"mvc/collection/collection-li\",\n \"mvc/base-mvc\",\n \"utils/localization\"\n], function( LIST_VIEW, DC_MODEL, DC_LI, BASE_MVC, _l ){\n\n'use strict';\n\nvar logNamespace = 'collections';\n/* =============================================================================\nTODO:\n\n============================================================================= */\n/** @class non-editable, read-only View/Controller for a dataset collection.\n */\nvar _super = LIST_VIEW.ModelListPanel;\nvar CollectionView = _super.extend(\n/** @lends CollectionView.prototype */{\n //MODEL is either a DatasetCollection (or subclass) or a DatasetCollectionElement (list of pairs)\n _logNamespace : logNamespace,\n\n className : _super.prototype.className + ' dataset-collection-panel',\n\n /** sub view class used for datasets */\n DatasetDCEViewClass : DC_LI.DatasetDCEListItemView,\n /** sub view class used for nested collections */\n NestedDCDCEViewClass: DC_LI.NestedDCDCEListItemView,\n /** key of attribute in model to assign to this.collection */\n modelCollectionKey : 'elements',\n\n // ......................................................................... SET UP\n /** Set up the view, set up storage, bind listeners to HistoryContents events\n * @param {Object} attributes optional settings for the panel\n */\n initialize : function( attributes ){\n _super.prototype.initialize.call( this, attributes );\n this.linkTarget = attributes.linkTarget || '_blank';\n\n this.hasUser = attributes.hasUser;\n /** A stack of panels that currently cover or hide this panel */\n this.panelStack = [];\n /** The text of the link to go back to the panel containing this one */\n this.parentName = attributes.parentName;\n /** foldout or drilldown */\n this.foldoutStyle = attributes.foldoutStyle || 'foldout';\n },\n\n _queueNewRender : function( $newRender, speed ) {\n speed = ( speed === undefined )?( this.fxSpeed ):( speed );\n var panel = this;\n panel.log( '_queueNewRender:', $newRender, speed );\n\n // TODO: jquery@1.12 doesn't change display when the elem has display: flex\n // this causes display: block for those elems after the use of show/hide animations\n // animations are removed from this view for now until fixed\n panel._swapNewRender( $newRender );\n panel.trigger( 'rendered', panel );\n },\n\n // ------------------------------------------------------------------------ sub-views\n /** In this override, use model.getVisibleContents */\n _filterCollection : function(){\n //TODO: should *not* be model.getVisibleContents - visibility is not model related\n return this.model.getVisibleContents();\n },\n\n /** override to return proper view class based on element_type */\n _getItemViewClass : function( model ){\n //this.debug( this + '._getItemViewClass:', model );\n //TODO: subclasses use DCEViewClass - but are currently unused - decide\n switch( model.get( 'element_type' ) ){\n case 'hda':\n return this.DatasetDCEViewClass;\n case 'dataset_collection':\n return this.NestedDCDCEViewClass;\n }\n throw new TypeError( 'Unknown element type:', model.get( 'element_type' ) );\n },\n\n /** override to add link target and anon */\n _getItemViewOptions : function( model ){\n var options = _super.prototype._getItemViewOptions.call( this, model );\n return _.extend( options, {\n linkTarget : this.linkTarget,\n hasUser : this.hasUser,\n //TODO: could move to only nested: list:paired\n foldoutStyle : this.foldoutStyle\n });\n },\n\n // ------------------------------------------------------------------------ collection sub-views\n /** In this override, add/remove expanded/collapsed model ids to/from web storage */\n _setUpItemViewListeners : function( view ){\n var panel = this;\n _super.prototype._setUpItemViewListeners.call( panel, view );\n\n // use pub-sub to: handle drilldown expansion and collapse\n panel.listenTo( view, {\n 'expanded:drilldown': function( v, drilldown ){\n this._expandDrilldownPanel( drilldown );\n },\n 'collapsed:drilldown': function( v, drilldown ){\n this._collapseDrilldownPanel( drilldown );\n }\n });\n return this;\n },\n\n /** Handle drill down by hiding this panels list and controls and showing the sub-panel */\n _expandDrilldownPanel : function( drilldown ){\n this.panelStack.push( drilldown );\n // hide this panel's controls and list, set the name for back navigation, and attach to the $el\n this.$( '> .controls' ).add( this.$list() ).hide();\n drilldown.parentName = this.model.get( 'name' );\n this.$el.append( drilldown.render().$el );\n },\n\n /** Handle drilldown close by freeing the panel and re-rendering this panel */\n _collapseDrilldownPanel : function( drilldown ){\n this.panelStack.pop();\n this.render();\n },\n\n // ------------------------------------------------------------------------ panel events\n /** event map */\n events : {\n 'click .navigation .back' : 'close'\n },\n\n /** close/remove this collection panel */\n close : function( event ){\n this.remove();\n this.trigger( 'close' );\n },\n\n // ........................................................................ misc\n /** string rep */\n toString : function(){\n return 'CollectionView(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';\n }\n});\n\n\n//------------------------------------------------------------------------------ TEMPLATES\nCollectionView.prototype.templates = (function(){\n\n var controlsTemplate = BASE_MVC.wrapTemplate([\n '
              ',\n '',\n\n '
              ',\n '
              <%- collection.name || collection.element_identifier %>
              ',\n '
              ',\n '<% if( collection.collection_type === \"list\" ){ %>',\n _l( 'a list of datasets' ),\n '<% } else if( collection.collection_type === \"paired\" ){ %>',\n _l( 'a pair of datasets' ),\n '<% } else if( collection.collection_type === \"list:paired\" ){ %>',\n _l( 'a list of paired datasets' ),\n '<% } else if( collection.collection_type === \"list:list\" ){ %>',\n _l( 'a list of dataset lists' ),\n '<% } %>',\n '
              ',\n '
              ',\n '
              '\n ], 'collection' );\n\n return _.extend( _.clone( _super.prototype.templates ), {\n controls : controlsTemplate\n });\n}());\n\n\n\n// =============================================================================\n/** @class non-editable, read-only View/Controller for a dataset collection. */\nvar ListCollectionView = CollectionView.extend(\n/** @lends ListCollectionView.prototype */{\n\n //TODO: not strictly needed - due to switch in CollectionView._getContentClass\n /** sub view class used for datasets */\n DatasetDCEViewClass : DC_LI.DatasetDCEListItemView,\n\n // ........................................................................ misc\n /** string rep */\n toString : function(){\n return 'ListCollectionView(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';\n }\n});\n\n\n// =============================================================================\n/** @class non-editable, read-only View/Controller for a dataset collection. */\nvar PairCollectionView = ListCollectionView.extend(\n/** @lends PairCollectionView.prototype */{\n\n // ........................................................................ misc\n /** string rep */\n toString : function(){\n return 'PairCollectionView(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';\n }\n});\n\n\n// =============================================================================\n/** @class non-editable, read-only View/Controller for a dataset collection. */\nvar ListOfPairsCollectionView = CollectionView.extend(\n/** @lends ListOfPairsCollectionView.prototype */{\n\n //TODO: not strictly needed - due to switch in CollectionView._getContentClass\n /** sub view class used for nested collections */\n NestedDCDCEViewClass : DC_LI.NestedDCDCEListItemView.extend({\n foldoutPanelClass : PairCollectionView\n }),\n\n // ........................................................................ misc\n /** string rep */\n toString : function(){\n return 'ListOfPairsCollectionView(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';\n }\n});\n\n\n// =============================================================================\n/** @class non-editable, read-only View/Controller for a list of lists dataset collection. */\nvar ListOfListsCollectionView = CollectionView.extend({\n\n /** sub view class used for nested collections */\n NestedDCDCEViewClass : DC_LI.NestedDCDCEListItemView.extend({\n foldoutPanelClass : PairCollectionView\n }),\n\n /** string rep */\n toString : function(){\n return 'ListOfListsCollectionView(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';\n }\n});\n\n\n//==============================================================================\n return {\n CollectionView : CollectionView,\n ListCollectionView : ListCollectionView,\n PairCollectionView : PairCollectionView,\n ListOfPairsCollectionView : ListOfPairsCollectionView,\n ListOfListsCollectionView : ListOfListsCollectionView\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/collection/collection-view.js\n ** module id = 68\n ** module chunks = 3\n **/","define([\n \"mvc/dataset/states\",\n \"mvc/dataset/dataset-li\",\n \"mvc/tag\",\n \"mvc/annotation\",\n \"ui/fa-icon-button\",\n \"mvc/base-mvc\",\n \"utils/localization\"\n], function( STATES, DATASET_LI, TAGS, ANNOTATIONS, faIconButton, BASE_MVC, _l ){\n\n'use strict';\n//==============================================================================\nvar _super = DATASET_LI.DatasetListItemView;\n/** @class Editing view for DatasetAssociation.\n */\nvar DatasetListItemEdit = _super.extend(\n/** @lends DatasetListItemEdit.prototype */{\n\n /** set up: options */\n initialize : function( attributes ){\n _super.prototype.initialize.call( this, attributes );\n this.hasUser = attributes.hasUser;\n\n /** allow user purge of dataset files? */\n this.purgeAllowed = attributes.purgeAllowed || false;\n\n //TODO: move to HiddenUntilActivatedViewMixin\n /** should the tags editor be shown or hidden initially? */\n this.tagsEditorShown = attributes.tagsEditorShown || false;\n /** should the tags editor be shown or hidden initially? */\n this.annotationEditorShown = attributes.annotationEditorShown || false;\n },\n\n // ......................................................................... titlebar actions\n /** In this override, add the other two primary actions: edit and delete */\n _renderPrimaryActions : function(){\n var actions = _super.prototype._renderPrimaryActions.call( this );\n if( this.model.get( 'state' ) === STATES.NOT_VIEWABLE ){\n return actions;\n }\n // render the display, edit attr and delete icon-buttons\n return _super.prototype._renderPrimaryActions.call( this ).concat([\n this._renderEditButton(),\n this._renderDeleteButton()\n ]);\n },\n\n //TODO: move titleButtons into state renderers, remove state checks in the buttons\n\n /** Render icon-button to edit the attributes (format, permissions, etc.) this dataset. */\n _renderEditButton : function(){\n // don't show edit while uploading, in-accessible\n // DO show if in error (ala previous history panel)\n if( ( this.model.get( 'state' ) === STATES.DISCARDED )\n || ( !this.model.get( 'accessible' ) ) ){\n return null;\n }\n\n var purged = this.model.get( 'purged' ),\n deleted = this.model.get( 'deleted' ),\n editBtnData = {\n title : _l( 'Edit attributes' ),\n href : this.model.urls.edit,\n target : this.linkTarget,\n faIcon : 'fa-pencil',\n classes : 'edit-btn'\n };\n\n // disable if purged or deleted and explain why in the tooltip\n if( deleted || purged ){\n editBtnData.disabled = true;\n if( purged ){\n editBtnData.title = _l( 'Cannot edit attributes of datasets removed from disk' );\n } else if( deleted ){\n editBtnData.title = _l( 'Undelete dataset to edit attributes' );\n }\n\n // disable if still uploading or new\n } else if( _.contains( [ STATES.UPLOAD, STATES.NEW ], this.model.get( 'state' ) ) ){\n editBtnData.disabled = true;\n editBtnData.title = _l( 'This dataset is not yet editable' );\n }\n return faIconButton( editBtnData );\n },\n\n /** Render icon-button to delete this hda. */\n _renderDeleteButton : function(){\n // don't show delete if...\n if( ( !this.model.get( 'accessible' ) ) ){\n return null;\n }\n\n var self = this,\n deletedAlready = this.model.isDeletedOrPurged();\n return faIconButton({\n title : !deletedAlready? _l( 'Delete' ) : _l( 'Dataset is already deleted' ),\n disabled : deletedAlready,\n faIcon : 'fa-times',\n classes : 'delete-btn',\n onclick : function() {\n // ...bler... tooltips being left behind in DOM (hover out never called on deletion)\n self.$el.find( '.icon-btn.delete-btn' ).trigger( 'mouseout' );\n self.model[ 'delete' ]();\n }\n });\n },\n\n // ......................................................................... details\n /** In this override, add tags and annotations controls, make the ? dbkey a link to editing page */\n _renderDetails : function(){\n //TODO: generalize to be allow different details for each state\n var $details = _super.prototype._renderDetails.call( this ),\n state = this.model.get( 'state' );\n\n if( !this.model.isDeletedOrPurged() && _.contains([ STATES.OK, STATES.FAILED_METADATA ], state ) ){\n this._renderTags( $details );\n this._renderAnnotation( $details );\n this._makeDbkeyEditLink( $details );\n }\n\n this._setUpBehaviors( $details );\n return $details;\n },\n\n /** Add less commonly used actions in the details section based on state */\n _renderSecondaryActions : function(){\n var actions = _super.prototype._renderSecondaryActions.call( this );\n switch( this.model.get( 'state' ) ){\n case STATES.UPLOAD:\n case STATES.NOT_VIEWABLE:\n return actions;\n case STATES.ERROR:\n // error button comes first\n actions.unshift( this._renderErrButton() );\n return actions.concat([ this._renderRerunButton() ]);\n case STATES.OK:\n case STATES.FAILED_METADATA:\n return actions.concat([ this._renderRerunButton(), this._renderVisualizationsButton() ]);\n }\n return actions.concat([ this._renderRerunButton() ]);\n },\n\n /** Render icon-button to report an error on this dataset to the galaxy admin. */\n _renderErrButton : function(){\n return faIconButton({\n title : _l( 'View or report this error' ),\n href : this.model.urls.report_error,\n classes : 'report-error-btn',\n target : this.linkTarget,\n faIcon : 'fa-bug'\n });\n },\n\n /** Render icon-button to re-run the job that created this dataset. */\n _renderRerunButton : function(){\n var creating_job = this.model.get( 'creating_job' );\n if( this.model.get( 'rerunnable' ) ){\n return faIconButton({\n title : _l( 'Run this job again' ),\n href : this.model.urls.rerun,\n classes : 'rerun-btn',\n target : this.linkTarget,\n faIcon : 'fa-refresh',\n onclick : function( ev ) {\n ev.preventDefault();\n // create webpack split point in order to load the tool form async\n // TODO: split not working (tool loads fine)\n require([ 'mvc/tool/tool-form' ], function( ToolForm ){\n var form = new ToolForm.View({ 'job_id' : creating_job });\n form.deferred.execute( function(){\n Galaxy.app.display( form );\n });\n });\n }\n });\n }\n },\n\n /** Render an icon-button or popupmenu of links based on the applicable visualizations */\n _renderVisualizationsButton : function(){\n //TODO: someday - lazyload visualizations\n var visualizations = this.model.get( 'visualizations' );\n if( ( this.model.isDeletedOrPurged() )\n || ( !this.hasUser )\n || ( !this.model.hasData() )\n || ( _.isEmpty( visualizations ) ) ){\n return null;\n }\n if( !_.isObject( visualizations[0] ) ){\n this.warn( 'Visualizations have been switched off' );\n return null;\n }\n\n var $visualizations = $( this.templates.visualizations( visualizations, this ) );\n //HACK: need to re-write those directed at galaxy_main with linkTarget\n $visualizations.find( '[target=\"galaxy_main\"]').attr( 'target', this.linkTarget );\n // use addBack here to include the root $visualizations elem (for the case of 1 visualization)\n this._addScratchBookFn( $visualizations.find( '.visualization-link' ).addBack( '.visualization-link' ) );\n return $visualizations;\n },\n\n /** add scratchbook functionality to visualization links */\n _addScratchBookFn : function( $links ){\n var li = this;\n $links.click( function( ev ){\n if( Galaxy.frame && Galaxy.frame.active ){\n Galaxy.frame.add({\n title : 'Visualization',\n url : $( this ).attr( 'href' )\n });\n ev.preventDefault();\n ev.stopPropagation();\n }\n });\n },\n\n //TODO: if possible move these to readonly view - but display the owner's tags/annotation (no edit)\n /** Render the tags list/control */\n _renderTags : function( $where ){\n if( !this.hasUser ){ return; }\n var view = this;\n this.tagsEditor = new TAGS.TagsEditor({\n model : this.model,\n el : $where.find( '.tags-display' ),\n onshowFirstTime : function(){ this.render(); },\n // persist state on the hda view (and not the editor) since these are currently re-created each time\n onshow : function(){ view.tagsEditorShown = true; },\n onhide : function(){ view.tagsEditorShown = false; },\n $activator : faIconButton({\n title : _l( 'Edit dataset tags' ),\n classes : 'tag-btn',\n faIcon : 'fa-tags'\n }).appendTo( $where.find( '.actions .right' ) )\n });\n if( this.tagsEditorShown ){ this.tagsEditor.toggle( true ); }\n },\n\n /** Render the annotation display/control */\n _renderAnnotation : function( $where ){\n if( !this.hasUser ){ return; }\n var view = this;\n this.annotationEditor = new ANNOTATIONS.AnnotationEditor({\n model : this.model,\n el : $where.find( '.annotation-display' ),\n onshowFirstTime : function(){ this.render(); },\n // persist state on the hda view (and not the editor) since these are currently re-created each time\n onshow : function(){ view.annotationEditorShown = true; },\n onhide : function(){ view.annotationEditorShown = false; },\n $activator : faIconButton({\n title : _l( 'Edit dataset annotation' ),\n classes : 'annotate-btn',\n faIcon : 'fa-comment'\n }).appendTo( $where.find( '.actions .right' ) )\n });\n if( this.annotationEditorShown ){ this.annotationEditor.toggle( true ); }\n },\n\n /** If the format/dbkey/genome_build isn't set, make the display a link to the edit page */\n _makeDbkeyEditLink : function( $details ){\n // make the dbkey a link to editing\n if( this.model.get( 'metadata_dbkey' ) === '?'\n && !this.model.isDeletedOrPurged() ){\n var editableDbkey = $( '?' )\n .attr( 'href', this.model.urls.edit )\n .attr( 'target', this.linkTarget );\n $details.find( '.dbkey .value' ).replaceWith( editableDbkey );\n }\n },\n\n // ......................................................................... events\n /** event map */\n events : _.extend( _.clone( _super.prototype.events ), {\n 'click .undelete-link' : '_clickUndeleteLink',\n 'click .purge-link' : '_clickPurgeLink',\n\n 'click .edit-btn' : function( ev ){ this.trigger( 'edit', this, ev ); },\n 'click .delete-btn' : function( ev ){ this.trigger( 'delete', this, ev ); },\n 'click .rerun-btn' : function( ev ){ this.trigger( 'rerun', this, ev ); },\n 'click .report-err-btn' : function( ev ){ this.trigger( 'report-err', this, ev ); },\n 'click .visualization-btn' : function( ev ){ this.trigger( 'visualize', this, ev ); },\n 'click .dbkey a' : function( ev ){ this.trigger( 'edit', this, ev ); }\n }),\n\n /** listener for item undelete (in the messages section) */\n _clickUndeleteLink : function( ev ){\n this.model.undelete();\n return false;\n },\n\n /** listener for item purge (in the messages section) */\n _clickPurgeLink : function( ev ){\n if( confirm( _l( 'This will permanently remove the data in your dataset. Are you sure?' ) ) ){\n this.model.purge();\n }\n return false;\n },\n\n // ......................................................................... misc\n /** string rep */\n toString : function(){\n var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n return 'HDAEditView(' + modelString + ')';\n }\n});\n\n\n// ............................................................................ TEMPLATES\n/** underscore templates */\nDatasetListItemEdit.prototype.templates = (function(){\n\n var warnings = _.extend( {}, _super.prototype.templates.warnings, {\n failed_metadata : BASE_MVC.wrapTemplate([\n // in this override, provide a link to the edit page\n '<% if( dataset.state === \"failed_metadata\" ){ %>',\n '',\n '<% } %>'\n ], 'dataset' ),\n\n deleted : BASE_MVC.wrapTemplate([\n // in this override, provide links to undelete or purge the dataset\n '<% if( dataset.deleted && !dataset.purged ){ %>',\n // deleted not purged\n '
              ',\n _l( 'This dataset has been deleted' ),\n '
              ', _l( 'Undelete it' ), '',\n '<% if( view.purgeAllowed ){ %>',\n '
              ',\n _l( 'Permanently remove it from disk' ),\n '',\n '<% } %>',\n '
              ',\n '<% } %>'\n ], 'dataset' )\n });\n\n var visualizationsTemplate = BASE_MVC.wrapTemplate([\n '<% if( visualizations.length === 1 ){ %>',\n '\"',\n ' target=\"<%- visualizations[0].target %>\" title=\"', _l( 'Visualize in' ),\n ' <%- visualizations[0].html %>\">',\n '',\n '',\n\n '<% } else { %>',\n '
              ',\n '',\n '',\n '',\n '',\n '
              ',\n '<% } %>'\n ], 'visualizations' );\n\n return _.extend( {}, _super.prototype.templates, {\n warnings : warnings,\n visualizations : visualizationsTemplate\n });\n}());\n\n\n//==============================================================================\n return {\n DatasetListItemEdit : DatasetListItemEdit\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/dataset/dataset-li-edit.js\n ** module id = 69\n ** module chunks = 3\n **/","define([\n \"mvc/dataset/states\",\n \"mvc/base-mvc\",\n \"utils/localization\"\n], function( STATES, BASE_MVC, _l ){\n'use strict';\n\nvar logNamespace = 'dataset';\n//==============================================================================\nvar searchableMixin = BASE_MVC.SearchableModelMixin;\n/** @class base model for any DatasetAssociation (HDAs, LDDAs, DatasetCollectionDAs).\n * No knowledge of what type (HDA/LDDA/DCDA) should be needed here.\n * The DA's are made searchable (by attribute) by mixing in SearchableModelMixin.\n */\nvar DatasetAssociation = Backbone.Model\n .extend( BASE_MVC.LoggableMixin )\n .extend( BASE_MVC.mixin( searchableMixin, /** @lends DatasetAssociation.prototype */{\n _logNamespace : logNamespace,\n\n /** default attributes for a model */\n defaults : {\n state : STATES.NEW,\n deleted : false,\n purged : false,\n name : '(unnamed dataset)',\n accessible : true,\n // sniffed datatype (sam, tabular, bed, etc.)\n data_type : '',\n file_ext : '',\n file_size : 0,\n\n // array of associated file types (eg. [ 'bam_index', ... ])\n meta_files : [],\n\n misc_blurb : '',\n misc_info : '',\n\n tags : []\n // do NOT default on annotation, as this default is valid and will be passed on 'save'\n // which is incorrect behavior when the model is only partially fetched (annos are not passed in summary data)\n //annotation : ''\n },\n\n /** instance vars and listeners */\n initialize : function( attributes, options ){\n this.debug( this + '(Dataset).initialize', attributes, options );\n\n //!! this state is not in trans.app.model.Dataset.states - set it here -\n if( !this.get( 'accessible' ) ){\n this.set( 'state', STATES.NOT_VIEWABLE );\n }\n\n /** Datasets rely/use some web controllers - have the model generate those URLs on startup */\n this.urls = this._generateUrls();\n\n this._setUpListeners();\n },\n\n /** returns misc. web urls for rendering things like re-run, display, etc. */\n _generateUrls : function(){\n var id = this.get( 'id' );\n if( !id ){ return {}; }\n var urls = {\n 'purge' : 'datasets/' + id + '/purge_async',\n 'display' : 'datasets/' + id + '/display/?preview=True',\n 'edit' : 'datasets/' + id + '/edit',\n 'download' : 'datasets/' + id + '/display?to_ext=' + this.get( 'file_ext' ),\n 'report_error' : 'dataset/errors?id=' + id,\n 'rerun' : 'tool_runner/rerun?id=' + id,\n 'show_params' : 'datasets/' + id + '/show_params',\n 'visualization' : 'visualization',\n 'meta_download' : 'dataset/get_metadata_file?hda_id=' + id + '&metadata_name='\n };\n _.each( urls, function( value, key ){\n urls[ key ] = Galaxy.root + value;\n });\n this.urls = urls;\n return urls;\n },\n\n /** set up any event listeners\n * event: state:ready fired when this DA moves into/is already in a ready state\n */\n _setUpListeners : function(){\n // if the state has changed and the new state is a ready state, fire an event\n this.on( 'change:state', function( currModel, newState ){\n this.log( this + ' has changed state:', currModel, newState );\n if( this.inReadyState() ){\n this.trigger( 'state:ready', currModel, newState, this.previous( 'state' ) );\n }\n });\n // the download url (currently) relies on having a correct file extension\n this.on( 'change:id change:file_ext', function( currModel ){\n this._generateUrls();\n });\n },\n\n // ........................................................................ common queries\n /** override to add urls */\n toJSON : function(){\n var json = Backbone.Model.prototype.toJSON.call( this );\n //console.warn( 'returning json?' );\n //return json;\n return _.extend( json, {\n urls : this.urls\n });\n },\n\n /** Is this dataset deleted or purged? */\n isDeletedOrPurged : function(){\n return ( this.get( 'deleted' ) || this.get( 'purged' ) );\n },\n\n /** Is this dataset in a 'ready' state; where 'Ready' states are states where no\n * processing (for the ds) is left to do on the server.\n */\n inReadyState : function(){\n var ready = _.contains( STATES.READY_STATES, this.get( 'state' ) );\n return ( this.isDeletedOrPurged() || ready );\n },\n\n /** Does this model already contain detailed data (as opposed to just summary level data)? */\n hasDetails : function(){\n // if it's inaccessible assume it has everything it needs\n if( !this.get( 'accessible' ) ){ return true; }\n return this.has( 'annotation' );\n },\n\n /** Convenience function to match dataset.has_data. */\n hasData : function(){\n return ( this.get( 'file_size' ) > 0 );\n },\n\n // ........................................................................ ajax\n fetch : function( options ){\n var dataset = this;\n return Backbone.Model.prototype.fetch.call( this, options )\n .always( function(){\n dataset._generateUrls();\n });\n },\n\n /** override to use actual Dates objects for create/update times */\n parse : function( response, options ){\n var parsed = Backbone.Model.prototype.parse.call( this, response, options );\n if( parsed.create_time ){\n parsed.create_time = new Date( parsed.create_time );\n }\n if( parsed.update_time ){\n parsed.update_time = new Date( parsed.update_time );\n }\n return parsed;\n },\n\n /** override to wait by default */\n save : function( attrs, options ){\n options = options || {};\n options.wait = _.isUndefined( options.wait ) ? true : options.wait;\n return Backbone.Model.prototype.save.call( this, attrs, options );\n },\n\n //NOTE: subclasses of DA's will need to implement url and urlRoot in order to have these work properly\n /** save this dataset, _Mark_ing it as deleted (just a flag) */\n 'delete' : function( options ){\n if( this.get( 'deleted' ) ){ return jQuery.when(); }\n return this.save( { deleted: true }, options );\n },\n /** save this dataset, _Mark_ing it as undeleted */\n undelete : function( options ){\n if( !this.get( 'deleted' ) || this.get( 'purged' ) ){ return jQuery.when(); }\n return this.save( { deleted: false }, options );\n },\n\n /** remove the file behind this dataset from the filesystem (if permitted) */\n purge : function _purge( options ){\n //TODO: use, override model.destroy, HDA.delete({ purge: true })\n if( this.get( 'purged' ) ){ return jQuery.when(); }\n options = options || {};\n options.url = this.urls.purge;\n\n //TODO: ideally this would be a DELETE call to the api\n // using purge async for now\n var hda = this,\n xhr = jQuery.ajax( options );\n xhr.done( function( message, status, responseObj ){\n hda.set({ deleted: true, purged: true });\n });\n xhr.fail( function( xhr, status, message ){\n // Exception messages are hidden within error page including: '...not allowed in this Galaxy instance.'\n // unbury and re-add to xhr\n var error = _l( \"Unable to purge dataset\" );\n var messageBuriedInUnfortunatelyFormattedError = ( 'Removal of datasets by users '\n + 'is not allowed in this Galaxy instance' );\n if( xhr.responseJSON && xhr.responseJSON.error ){\n error = xhr.responseJSON.error;\n } else if( xhr.responseText.indexOf( messageBuriedInUnfortunatelyFormattedError ) !== -1 ){\n error = messageBuriedInUnfortunatelyFormattedError;\n }\n xhr.responseText = error;\n hda.trigger( 'error', hda, xhr, options, _l( error ), { error: error } );\n });\n return xhr;\n },\n\n // ........................................................................ searching\n /** what attributes of an HDA will be used in a text search */\n searchAttributes : [\n 'name', 'file_ext', 'genome_build', 'misc_blurb', 'misc_info', 'annotation', 'tags'\n ],\n\n /** our attr keys don't often match the labels we display to the user - so, when using\n * attribute specifiers ('name=\"bler\"') in a term, allow passing in aliases for the\n * following attr keys.\n */\n searchAliases : {\n title : 'name',\n format : 'file_ext',\n database : 'genome_build',\n blurb : 'misc_blurb',\n description : 'misc_blurb',\n info : 'misc_info',\n tag : 'tags'\n },\n\n // ........................................................................ misc\n /** String representation */\n toString : function(){\n var nameAndId = this.get( 'id' ) || '';\n if( this.get( 'name' ) ){\n nameAndId = '\"' + this.get( 'name' ) + '\",' + nameAndId;\n }\n return 'Dataset(' + nameAndId + ')';\n }\n}));\n\n\n//==============================================================================\n/** @class Backbone collection for dataset associations.\n */\nvar DatasetAssociationCollection = Backbone.Collection.extend( BASE_MVC.LoggableMixin ).extend(\n/** @lends HistoryContents.prototype */{\n _logNamespace : logNamespace,\n\n model : DatasetAssociation,\n\n /** root api url */\n urlRoot : Galaxy.root + 'api/datasets',\n\n /** url fn */\n url : function(){\n return this.urlRoot;\n },\n\n // ........................................................................ common queries\n /** Get the ids of every item in this collection\n * @returns array of encoded ids\n */\n ids : function(){\n return this.map( function( item ){ return item.get('id'); });\n },\n\n /** Get contents that are not ready\n * @returns array of content models\n */\n notReady : function(){\n return this.filter( function( content ){\n return !content.inReadyState();\n });\n },\n\n /** return true if any datasets don't have details */\n haveDetails : function(){\n return this.all( function( dataset ){ return dataset.hasDetails(); });\n },\n\n // ........................................................................ ajax\n /** using a queue, perform ajaxFn on each of the models in this collection */\n ajaxQueue : function( ajaxFn, options ){\n var deferred = jQuery.Deferred(),\n startingLength = this.length,\n responses = [];\n\n if( !startingLength ){\n deferred.resolve([]);\n return deferred;\n }\n\n // use reverse order (stylistic choice)\n var ajaxFns = this.chain().reverse().map( function( dataset, i ){\n return function(){\n var xhr = ajaxFn.call( dataset, options );\n // if successful, notify using the deferred to allow tracking progress\n xhr.done( function( response ){\n deferred.notify({ curr: i, total: startingLength, response: response, model: dataset });\n });\n // (regardless of previous error or success) if not last ajax call, shift and call the next\n // if last fn, resolve deferred\n xhr.always( function( response ){\n responses.push( response );\n if( ajaxFns.length ){\n ajaxFns.shift()();\n } else {\n deferred.resolve( responses );\n }\n });\n };\n }).value();\n // start the queue\n ajaxFns.shift()();\n\n return deferred;\n },\n\n // ........................................................................ sorting/filtering\n /** return a new collection of datasets whose attributes contain the substring matchesWhat */\n matches : function( matchesWhat ){\n return this.filter( function( dataset ){\n return dataset.matches( matchesWhat );\n });\n },\n\n /** String representation. */\n toString : function(){\n return ([ 'DatasetAssociationCollection(', this.length, ')' ].join( '' ));\n }\n});\n\n\n//==============================================================================\n return {\n DatasetAssociation : DatasetAssociation,\n DatasetAssociationCollection : DatasetAssociationCollection\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/dataset/dataset-model.js\n ** module id = 70\n ** module chunks = 3\n **/","define([\n \"mvc/dataset/dataset-li\",\n \"mvc/base-mvc\",\n \"utils/localization\"\n], function( DATASET_LI, BASE_MVC, _l ){\n\n'use strict';\n\n//==============================================================================\nvar _super = DATASET_LI.DatasetListItemView;\n/** @class Read only view for HistoryDatasetAssociation.\n * Since there are no controls on the HDAView to hide the dataset,\n * the primary thing this class does (currently) is override templates\n * to render the HID.\n */\nvar HDAListItemView = _super.extend(\n/** @lends HDAListItemView.prototype */{\n\n className : _super.prototype.className + \" history-content\",\n\n initialize : function( attributes, options ){\n _super.prototype.initialize.call( this, attributes, options );\n },\n\n // ......................................................................... misc\n /** String representation */\n toString : function(){\n var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n return 'HDAListItemView(' + modelString + ')';\n }\n});\n\n// ............................................................................ TEMPLATES\n/** underscore templates */\nHDAListItemView.prototype.templates = (function(){\n\n var titleBarTemplate = BASE_MVC.wrapTemplate([\n // adding the hid display to the title\n '
              ',\n '',\n '
              ',\n //TODO: remove whitespace and use margin-right\n '<%- dataset.hid %> ',\n '<%- dataset.name %>',\n '
              ',\n '
              '\n ], 'dataset' );\n\n var warnings = _.extend( {}, _super.prototype.templates.warnings, {\n hidden : BASE_MVC.wrapTemplate([\n // add a warning when hidden\n '<% if( !dataset.visible ){ %>',\n '
              ',\n _l( 'This dataset has been hidden' ),\n '
              ',\n '<% } %>'\n ], 'dataset' )\n });\n\n return _.extend( {}, _super.prototype.templates, {\n titleBar : titleBarTemplate,\n warnings : warnings\n });\n}());\n\n\n\n//==============================================================================\n return {\n HDAListItemView : HDAListItemView\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/history/hda-li.js\n ** module id = 71\n ** module chunks = 3\n **/","define([\n \"mvc/dataset/dataset-model\",\n \"mvc/history/history-content-model\",\n \"mvc/base-mvc\",\n \"utils/localization\"\n], function( DATASET, HISTORY_CONTENT, BASE_MVC, _l ){\n'use strict';\n\n//==============================================================================\nvar _super = DATASET.DatasetAssociation,\n hcontentMixin = HISTORY_CONTENT.HistoryContentMixin;\n/** @class (HDA) model for a Galaxy dataset contained in and related to a history.\n */\nvar HistoryDatasetAssociation = _super.extend( BASE_MVC.mixin( hcontentMixin,\n/** @lends HistoryDatasetAssociation.prototype */{\n\n /** default attributes for a model */\n defaults : _.extend( {}, _super.prototype.defaults, hcontentMixin.defaults, {\n history_content_type: 'dataset',\n model_class : 'HistoryDatasetAssociation'\n }),\n}));\n\n//==============================================================================\n return {\n HistoryDatasetAssociation : HistoryDatasetAssociation\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/history/hda-model.js\n ** module id = 72\n ** module chunks = 3\n **/","define([\n \"mvc/dataset/states\",\n \"mvc/collection/collection-li\",\n \"mvc/collection/collection-view\",\n \"mvc/base-mvc\",\n \"utils/localization\"\n], function( STATES, DC_LI, DC_VIEW, BASE_MVC, _l ){\n\n'use strict';\n\n//==============================================================================\nvar _super = DC_LI.DCListItemView;\n/** @class Read only view for HistoryDatasetCollectionAssociation (a dataset collection inside a history).\n */\nvar HDCAListItemView = _super.extend(\n/** @lends HDCAListItemView.prototype */{\n\n className : _super.prototype.className + \" history-content\",\n\n /** event listeners */\n _setUpListeners : function(){\n _super.prototype._setUpListeners.call( this );\n\n this.listenTo( this.model, {\n 'change:populated change:visible' : function( model, options ){ this.render(); },\n });\n },\n\n /** Override to provide the proper collections panels as the foldout */\n _getFoldoutPanelClass : function(){\n switch( this.model.get( 'collection_type' ) ){\n case 'list':\n return DC_VIEW.ListCollectionView;\n case 'paired':\n return DC_VIEW.PairCollectionView;\n case 'list:paired':\n return DC_VIEW.ListOfPairsCollectionView;\n case 'list:list':\n return DC_VIEW.ListOfListsCollectionView;\n }\n throw new TypeError( 'Uknown collection_type: ' + this.model.get( 'collection_type' ) );\n },\n\n /** In this override, add the state as a class for use with state-based CSS */\n _swapNewRender : function( $newRender ){\n _super.prototype._swapNewRender.call( this, $newRender );\n //TODO: model currently has no state\n var state = !this.model.get( 'populated' ) ? STATES.RUNNING : STATES.OK;\n //if( this.model.has( 'state' ) ){\n this.$el.addClass( 'state-' + state );\n //}\n return this.$el;\n },\n\n // ......................................................................... misc\n /** String representation */\n toString : function(){\n var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n return 'HDCAListItemView(' + modelString + ')';\n }\n});\n\n/** underscore templates */\nHDCAListItemView.prototype.templates = (function(){\n\n var warnings = _.extend( {}, _super.prototype.templates.warnings, {\n hidden : BASE_MVC.wrapTemplate([\n // add a warning when hidden\n '<% if( !collection.visible ){ %>',\n '
              ',\n _l( 'This collection has been hidden' ),\n '
              ',\n '<% } %>'\n ], 'collection' )\n });\n\n// could steal this from hda-base (or use mixed content)\n var titleBarTemplate = BASE_MVC.wrapTemplate([\n // adding the hid display to the title\n '
              ',\n '',\n '
              ',\n //TODO: remove whitespace and use margin-right\n '<%- collection.hid %> ',\n '<%- collection.name %>',\n '
              ',\n '
              ',\n '
              '\n ], 'collection' );\n\n return _.extend( {}, _super.prototype.templates, {\n warnings : warnings,\n titleBar : titleBarTemplate\n });\n}());\n\n\n//==============================================================================\n return {\n HDCAListItemView : HDCAListItemView\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/history/hdca-li.js\n ** module id = 73\n ** module chunks = 3\n **/","define([\n \"mvc/dataset/states\",\n \"mvc/base-mvc\",\n \"utils/localization\"\n], function( STATES, BASE_MVC, _l ){\n'use strict';\n\n//==============================================================================\n/** @class Mixin for HistoryContents content (HDAs, HDCAs).\n */\nvar HistoryContentMixin = {\n\n /** default attributes for a model */\n defaults : {\n /** parent (containing) history */\n history_id : null,\n /** some content_type (HistoryContents can contain mixed model classes) */\n history_content_type: null,\n /** indicating when/what order the content was generated in the context of the history */\n hid : null,\n /** whether the user wants the content shown (visible) */\n visible : true\n },\n\n // ........................................................................ mixed content element\n // In order to be part of a MIXED bbone collection, we can't rely on the id\n // (which may collide btwn models of different classes)\n // Instead, use type_id which prefixes the history_content_type so the bbone collection can differentiate\n idAttribute : 'type_id',\n\n // ........................................................................ common queries\n /** the more common alias of visible */\n hidden : function(){\n return !this.get( 'visible' );\n },\n\n//TODO: remove\n /** based on includeDeleted, includeHidden (gen. from the container control),\n * would this ds show in the list of ds's?\n * @param {Boolean} includeDeleted are we showing deleted hdas?\n * @param {Boolean} includeHidden are we showing hidden hdas?\n */\n isVisible : function( includeDeleted, includeHidden ){\n var isVisible = true;\n if( ( !includeDeleted )\n && ( this.get( 'deleted' ) || this.get( 'purged' ) ) ){\n isVisible = false;\n }\n if( ( !includeHidden )\n && ( !this.get( 'visible' ) ) ){\n isVisible = false;\n }\n return isVisible;\n },\n\n // ........................................................................ ajax\n //TODO?: these are probably better done on the leaf classes\n /** history content goes through the 'api/histories' API */\n urlRoot: Galaxy.root + 'api/histories/',\n\n /** full url spec. for this content */\n url : function(){\n var url = this.urlRoot + this.get( 'history_id' ) + '/contents/'\n + this.get('history_content_type') + 's/' + this.get( 'id' );\n return url;\n },\n\n /** save this content as not visible */\n hide : function( options ){\n if( !this.get( 'visible' ) ){ return jQuery.when(); }\n return this.save( { visible: false }, options );\n },\n /** save this content as visible */\n unhide : function( options ){\n if( this.get( 'visible' ) ){ return jQuery.when(); }\n return this.save( { visible: true }, options );\n },\n\n // ........................................................................ misc\n toString : function(){\n return ([ this.get( 'type_id' ), this.get( 'hid' ), this.get( 'name' ) ].join(':'));\n }\n};\n\n\n//==============================================================================\n return {\n HistoryContentMixin : HistoryContentMixin\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/history/history-content-model.js\n ** module id = 74\n ** module chunks = 3\n **/","\ndefine([\n \"mvc/history/history-contents\",\n \"mvc/history/history-preferences\",\n \"mvc/base/controlled-fetch-collection\",\n \"utils/utils\",\n \"mvc/base-mvc\",\n \"utils/localization\"\n], function( HISTORY_CONTENTS, HISTORY_PREFS, CONTROLLED_FETCH_COLLECTION, UTILS, BASE_MVC, _l ){\n'use strict';\n\n//==============================================================================\n/** @class Model for a Galaxy history resource - both a record of user\n * tool use and a collection of the datasets those tools produced.\n * @name History\n * @augments Backbone.Model\n */\nvar History = Backbone.Model\n .extend( BASE_MVC.LoggableMixin )\n .extend( BASE_MVC.mixin( BASE_MVC.SearchableModelMixin, /** @lends History.prototype */{\n _logNamespace : 'history',\n\n /** ms between fetches when checking running jobs/datasets for updates */\n UPDATE_DELAY : 4000,\n\n // values from api (may need more)\n defaults : {\n model_class : 'History',\n id : null,\n name : 'Unnamed History',\n state : 'new',\n\n deleted : false,\n contents_active : {},\n contents_states : {},\n },\n\n urlRoot: Galaxy.root + 'api/histories',\n\n contentsClass : HISTORY_CONTENTS.HistoryContents,\n\n /** What model fields to search with */\n searchAttributes : [\n 'name', 'annotation', 'tags'\n ],\n\n /** Adding title and singular tag */\n searchAliases : {\n title : 'name',\n tag : 'tags'\n },\n\n // ........................................................................ set up/tear down\n /** Set up the model\n * @param {Object} historyJSON model data for this History\n * @param {Object} options any extra settings including logger\n */\n initialize : function( historyJSON, options ){\n options = options || {};\n this.logger = options.logger || null;\n this.log( this + \".initialize:\", historyJSON, options );\n\n /** HistoryContents collection of the HDAs contained in this history. */\n this.contents = new this.contentsClass( [], {\n history : this,\n historyId : this.get( 'id' ),\n order : options.order,\n });\n\n this._setUpListeners();\n this._setUpCollectionListeners();\n\n /** cached timeout id for the dataset updater */\n this.updateTimeoutId = null;\n },\n\n /** set up any event listeners for this history including those to the contained HDAs\n * events: error:contents if an error occurred with the contents collection\n */\n _setUpListeners : function(){\n // if the model's id changes ('current' or null -> an actual id), update the contents history_id\n return this.on({\n 'error' : function( model, xhr, options, msg, details ){\n this.clearUpdateTimeout();\n },\n 'change:id' : function( model, newId ){\n if( this.contents ){\n this.contents.historyId = newId;\n }\n },\n });\n },\n\n /** event handlers for the contents submodels */\n _setUpCollectionListeners : function(){\n if( !this.contents ){ return this; }\n // bubble up errors\n return this.listenTo( this.contents, {\n 'error' : function(){\n this.trigger.apply( this, jQuery.makeArray( arguments ) );\n },\n });\n },\n\n // ........................................................................ derived attributes\n /** */\n contentsShown : function(){\n var contentsActive = this.get( 'contents_active' );\n var shown = contentsActive.active || 0;\n shown += this.contents.includeDeleted? contentsActive.deleted : 0;\n shown += this.contents.includeHidden? contentsActive.hidden : 0;\n return shown;\n },\n\n /** convert size in bytes to a more human readable version */\n nice_size : function(){\n var size = this.get( 'size' );\n return size? UTILS.bytesToString( size, true, 2 ) : _l( '(empty)' );\n },\n\n /** override to add nice_size */\n toJSON : function(){\n return _.extend( Backbone.Model.prototype.toJSON.call( this ), {\n nice_size : this.nice_size()\n });\n },\n\n /** override to allow getting nice_size */\n get : function( key ){\n if( key === 'nice_size' ){\n return this.nice_size();\n }\n return Backbone.Model.prototype.get.apply( this, arguments );\n },\n\n // ........................................................................ common queries\n /** T/F is this history owned by the current user (Galaxy.user)\n * Note: that this will return false for an anon user even if the history is theirs.\n */\n ownedByCurrUser : function(){\n // no currUser\n if( !Galaxy || !Galaxy.user ){\n return false;\n }\n // user is anon or history isn't owned\n if( Galaxy.user.isAnonymous() || Galaxy.user.id !== this.get( 'user_id' ) ){\n return false;\n }\n return true;\n },\n\n /** Return the number of running jobs assoc with this history (note: unknown === 0) */\n numOfUnfinishedJobs : function(){\n var unfinishedJobIds = this.get( 'non_ready_jobs' );\n return unfinishedJobIds? unfinishedJobIds.length : 0;\n },\n\n /** Return the number of running hda/hdcas in this history (note: unknown === 0) */\n numOfUnfinishedShownContents : function(){\n return this.contents.runningAndActive().length || 0;\n },\n\n // ........................................................................ updates\n _fetchContentRelatedAttributes : function(){\n var contentRelatedAttrs = [ 'size', 'non_ready_jobs', 'contents_active', 'hid_counter' ];\n return this.fetch({ data : $.param({ keys : contentRelatedAttrs.join( ',' ) }) });\n },\n\n /** check for any changes since the last time we updated (or fetch all if ) */\n refresh : function( options ){\n // console.log( this + '.refresh' );\n options = options || {};\n var self = this;\n\n // note if there was no previous update time, all summary contents will be fetched\n var lastUpdateTime = self.lastUpdateTime;\n // if we don't flip this, then a fully-fetched list will not be re-checked via fetch\n this.contents.allFetched = false;\n var fetchFn = self.contents.currentPage !== 0\n ? function(){ return self.contents.fetchPage( 0 ); }\n : function(){ return self.contents.fetchUpdated( lastUpdateTime ); };\n // note: if there was no previous update time, all summary contents will be fetched\n return fetchFn()\n .done( function( response, status, xhr ){\n var serverResponseDatetime;\n try {\n serverResponseDatetime = new Date( xhr.getResponseHeader( 'Date' ) );\n } catch( err ){}\n self.lastUpdateTime = serverResponseDatetime || new Date();\n self.checkForUpdates( options );\n });\n },\n\n /** continuously fetch updated contents every UPDATE_DELAY ms if this history's datasets or jobs are unfinished */\n checkForUpdates : function( options ){\n // console.log( this + '.checkForUpdates' );\n options = options || {};\n var delay = this.UPDATE_DELAY;\n var self = this;\n if( !self.id ){ return; }\n\n function _delayThenUpdate(){\n // prevent buildup of updater timeouts by clearing previous if any, then set new and cache id\n self.clearUpdateTimeout();\n self.updateTimeoutId = setTimeout( function(){\n self.refresh( options );\n }, delay );\n }\n\n // if there are still datasets in the non-ready state, recurse into this function with the new time\n var nonReadyContentCount = this.numOfUnfinishedShownContents();\n // console.log( 'nonReadyContentCount:', nonReadyContentCount );\n if( nonReadyContentCount > 0 ){\n _delayThenUpdate();\n\n } else {\n // no datasets are running, but currently runnning jobs may still produce new datasets\n // see if the history has any running jobs and continue to update if so\n // (also update the size for the user in either case)\n self._fetchContentRelatedAttributes()\n .done( function( historyData ){\n // console.log( 'non_ready_jobs:', historyData.non_ready_jobs );\n if( self.numOfUnfinishedJobs() > 0 ){\n _delayThenUpdate();\n\n } else {\n // otherwise, let listeners know that all updates have stopped\n self.trigger( 'ready' );\n }\n });\n }\n },\n\n /** clear the timeout and the cached timeout id */\n clearUpdateTimeout : function(){\n if( this.updateTimeoutId ){\n clearTimeout( this.updateTimeoutId );\n this.updateTimeoutId = null;\n }\n },\n\n // ........................................................................ ajax\n /** override to use actual Dates objects for create/update times */\n parse : function( response, options ){\n var parsed = Backbone.Model.prototype.parse.call( this, response, options );\n if( parsed.create_time ){\n parsed.create_time = new Date( parsed.create_time );\n }\n if( parsed.update_time ){\n parsed.update_time = new Date( parsed.update_time );\n }\n return parsed;\n },\n\n /** fetch this histories data (using options) then it's contents (using contentsOptions) */\n fetchWithContents : function( options, contentsOptions ){\n options = options || {};\n var self = this;\n\n // console.log( this + '.fetchWithContents' );\n // TODO: push down to a base class\n options.view = 'dev-detailed';\n\n // fetch history then use history data to fetch (paginated) contents\n return this.fetch( options ).then( function getContents( history ){\n self.contents.history = self;\n self.contents.setHistoryId( history.id );\n return self.fetchContents( contentsOptions );\n });\n },\n\n /** fetch this histories contents, adjusting options based on the stored history preferences */\n fetchContents : function( options ){\n options = options || {};\n var self = this;\n\n // we're updating, reset the update time\n self.lastUpdateTime = new Date();\n return self.contents.fetchCurrentPage( options );\n },\n\n /** save this history, _Mark_ing it as deleted (just a flag) */\n _delete : function( options ){\n if( this.get( 'deleted' ) ){ return jQuery.when(); }\n return this.save( { deleted: true }, options );\n },\n /** purge this history, _Mark_ing it as purged and removing all dataset data from the server */\n purge : function( options ){\n if( this.get( 'purged' ) ){ return jQuery.when(); }\n return this.save( { deleted: true, purged: true }, options );\n },\n /** save this history, _Mark_ing it as undeleted */\n undelete : function( options ){\n if( !this.get( 'deleted' ) ){ return jQuery.when(); }\n return this.save( { deleted: false }, options );\n },\n\n /** Make a copy of this history on the server\n * @param {Boolean} current if true, set the copy as the new current history (default: true)\n * @param {String} name name of new history (default: none - server sets to: Copy of )\n * @fires copied passed this history and the response JSON from the copy\n * @returns {xhr}\n */\n copy : function( current, name, allDatasets ){\n current = ( current !== undefined )?( current ):( true );\n if( !this.id ){\n throw new Error( 'You must set the history ID before copying it.' );\n }\n\n var postData = { history_id : this.id };\n if( current ){\n postData.current = true;\n }\n if( name ){\n postData.name = name;\n }\n if( !allDatasets ){\n postData.all_datasets = false;\n }\n postData.view = 'dev-detailed';\n\n var history = this;\n var copy = jQuery.post( this.urlRoot, postData );\n // if current - queue to setAsCurrent before firing 'copied'\n if( current ){\n return copy.then( function( response ){\n var newHistory = new History( response );\n return newHistory.setAsCurrent()\n .done( function(){\n history.trigger( 'copied', history, response );\n });\n });\n }\n return copy.done( function( response ){\n history.trigger( 'copied', history, response );\n });\n },\n\n setAsCurrent : function(){\n var history = this,\n xhr = jQuery.getJSON( Galaxy.root + 'history/set_as_current?id=' + this.id );\n\n xhr.done( function(){\n history.trigger( 'set-as-current', history );\n });\n return xhr;\n },\n\n // ........................................................................ misc\n toString : function(){\n return 'History(' + this.get( 'id' ) + ',' + this.get( 'name' ) + ')';\n }\n}));\n\n\n//==============================================================================\nvar _collectionSuper = CONTROLLED_FETCH_COLLECTION.InfinitelyScrollingCollection;\n/** @class A collection of histories (per user)\n * that maintains the current history as the first in the collection.\n * New or copied histories become the current history.\n */\nvar HistoryCollection = _collectionSuper.extend( BASE_MVC.LoggableMixin ).extend({\n _logNamespace : 'history',\n\n model : History,\n /** @type {String} initial order used by collection */\n order : 'update_time',\n /** @type {Number} limit used for the first fetch (or a reset) */\n limitOnFirstFetch : 10,\n /** @type {Number} limit used for each subsequent fetch */\n limitPerFetch : 10,\n\n initialize : function( models, options ){\n options = options || {};\n this.log( 'HistoryCollection.initialize', models, options );\n _collectionSuper.prototype.initialize.call( this, models, options );\n\n /** @type {boolean} should deleted histories be included */\n this.includeDeleted = options.includeDeleted || false;\n\n /** @type {String} encoded id of the history that's current */\n this.currentHistoryId = options.currentHistoryId;\n\n this.setUpListeners();\n // note: models are sent to reset *after* this fn ends; up to this point\n // the collection *is empty*\n },\n\n urlRoot : Galaxy.root + 'api/histories',\n url : function(){ return this.urlRoot; },\n\n /** set up reflexive event handlers */\n setUpListeners : function setUpListeners(){\n return this.on({\n // when a history is deleted, remove it from the collection (if optionally set to do so)\n 'change:deleted' : function( history ){\n // TODO: this becomes complicated when more filters are used\n this.debug( 'change:deleted', this.includeDeleted, history.get( 'deleted' ) );\n if( !this.includeDeleted && history.get( 'deleted' ) ){\n this.remove( history );\n }\n },\n // listen for a history copy, setting it to current\n 'copied' : function( original, newData ){\n this.setCurrent( new History( newData, [] ) );\n },\n // when a history is made current, track the id in the collection\n 'set-as-current' : function( history ){\n var oldCurrentId = this.currentHistoryId;\n this.trigger( 'no-longer-current', oldCurrentId );\n this.currentHistoryId = history.id;\n }\n });\n },\n\n /** override to change view */\n _buildFetchData : function( options ){\n return _.extend( _collectionSuper.prototype._buildFetchData.call( this, options ), {\n view : 'dev-detailed'\n });\n },\n\n /** override to filter out deleted and purged */\n _buildFetchFilters : function( options ){\n var superFilters = _collectionSuper.prototype._buildFetchFilters.call( this, options ) || {};\n var filters = {};\n if( !this.includeDeleted ){\n filters.deleted = false;\n filters.purged = false;\n } else {\n // force API to return both deleted and non\n //TODO: when the API is updated, remove this\n filters.deleted = null;\n }\n return _.defaults( superFilters, filters );\n },\n\n /** override to fetch current as well (as it may be outside the first 10, etc.) */\n fetchFirst : function( options ){\n var self = this;\n // TODO: batch?\n var xhr = $.when();\n if( this.currentHistoryId ){\n xhr = _collectionSuper.prototype.fetchFirst.call( self, {\n silent: true,\n limit : 1,\n filters: {\n // without these a deleted current history will return [] here and block the other xhr\n 'purged' : '',\n 'deleted' : '',\n 'encoded_id-in' : this.currentHistoryId,\n }\n });\n }\n return xhr.then( function(){\n options = options || {};\n options.offset = 0;\n return self.fetchMore( options );\n });\n },\n\n /** @type {Object} map of collection available sorting orders containing comparator fns */\n comparators : _.extend( _.clone( _collectionSuper.prototype.comparators ), {\n 'name' : BASE_MVC.buildComparator( 'name', { ascending: true }),\n 'name-dsc' : BASE_MVC.buildComparator( 'name', { ascending: false }),\n 'size' : BASE_MVC.buildComparator( 'size', { ascending: false }),\n 'size-asc' : BASE_MVC.buildComparator( 'size', { ascending: true }),\n }),\n\n /** override to always have the current history first */\n sort : function( options ){\n options = options || {};\n var silent = options.silent;\n var currentHistory = this.remove( this.get( this.currentHistoryId ) );\n _collectionSuper.prototype.sort.call( this, _.defaults({ silent: true }, options ) );\n this.unshift( currentHistory, { silent: true });\n if( !silent ){\n this.trigger( 'sort', this, options );\n }\n return this;\n },\n\n /** create a new history and by default set it to be the current history */\n create : function create( data, hdas, historyOptions, xhrOptions ){\n //TODO: .create is actually a collection function that's overridden here\n var collection = this,\n xhr = jQuery.getJSON( Galaxy.root + 'history/create_new_current' );\n return xhr.done( function( newData ){\n collection.setCurrent( new History( newData, [], historyOptions || {} ) );\n });\n },\n\n /** set the current history to the given history, placing it first in the collection.\n * Pass standard bbone options for use in unshift.\n * @triggers new-current passed history and this collection\n */\n setCurrent : function( history, options ){\n options = options || {};\n // new histories go in the front\n this.unshift( history, options );\n this.currentHistoryId = history.get( 'id' );\n if( !options.silent ){\n this.trigger( 'new-current', history, this );\n }\n return this;\n },\n\n toString: function toString(){\n return 'HistoryCollection(' + this.length + ',current:' + this.currentHistoryId + ')';\n }\n});\n\n\n//==============================================================================\nreturn {\n History : History,\n HistoryCollection : HistoryCollection\n};});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/history/history-model.js\n ** module id = 75\n ** module chunks = 3\n **/","define([\n \"mvc/list/list-item\",\n \"ui/loading-indicator\",\n \"mvc/base-mvc\",\n \"utils/localization\",\n \"ui/search-input\"\n], function( LIST_ITEM, LoadingIndicator, BASE_MVC, _l ){\n\n'use strict';\n\nvar logNamespace = 'list';\n/* ============================================================================\nTODO:\n\n============================================================================ */\n/** @class View for a list/collection of models and the sub-views of those models.\n * Sub-views must (at least have the interface if not) inherit from ListItemView.\n * (For a list panel that also includes some 'container' model (History->HistoryContents)\n * use ModelWithListPanel)\n *\n * Allows for:\n * searching collection/sub-views\n * selecting/multi-selecting sub-views\n *\n * Currently used:\n * for dataset/dataset-choice\n * as superclass of ModelListPanel\n */\nvar ListPanel = Backbone.View.extend( BASE_MVC.LoggableMixin ).extend(/** @lends ListPanel.prototype */{\n _logNamespace : logNamespace,\n\n /** class to use for constructing the sub-views */\n viewClass : LIST_ITEM.ListItemView,\n /** class to used for constructing collection of sub-view models */\n collectionClass : Backbone.Collection,\n\n tagName : 'div',\n className : 'list-panel',\n\n /** (in ms) that jquery effects will use */\n fxSpeed : 'fast',\n\n /** string to display when the collection has no contents */\n emptyMsg : _l( 'This list is empty' ),\n /** displayed when no items match the search terms */\n noneFoundMsg : _l( 'No matching items found' ),\n /** string used for search placeholder */\n searchPlaceholder : _l( 'search' ),\n\n // ......................................................................... SET UP\n /** Set up the view, set up storage, bind listeners to HistoryContents events\n * @param {Object} attributes optional settings for the list\n */\n initialize : function( attributes, options ){\n attributes = attributes || {};\n // set the logger if requested\n if( attributes.logger ){\n this.logger = attributes.logger;\n }\n this.log( this + '.initialize:', attributes );\n\n // ---- instance vars\n /** how quickly should jquery fx run? */\n this.fxSpeed = _.has( attributes, 'fxSpeed' )?( attributes.fxSpeed ):( this.fxSpeed );\n\n /** filters for displaying subviews */\n this.filters = [];\n /** current search terms */\n this.searchFor = attributes.searchFor || '';\n\n /** loading indicator */\n // this.indicator = new LoadingIndicator( this.$el );\n\n /** currently showing selectors on items? */\n this.selecting = ( attributes.selecting !== undefined )? attributes.selecting : true;\n //this.selecting = false;\n\n /** cached selected item.model.ids to persist btwn renders */\n this.selected = attributes.selected || [];\n /** the last selected item.model.id */\n this.lastSelected = null;\n\n /** are sub-views draggable */\n this.dragItems = attributes.dragItems || false;\n\n /** list item view class (when passed models) */\n this.viewClass = attributes.viewClass || this.viewClass;\n\n /** list item views */\n this.views = [];\n /** list item models */\n this.collection = attributes.collection || this._createDefaultCollection();\n\n /** filter fns run over collection items to see if they should show in the list */\n this.filters = attributes.filters || [];\n\n /** override $scrollContainer fn via attributes - fn should return jq for elem to call scrollTo on */\n this.$scrollContainer = attributes.$scrollContainer || this.$scrollContainer;\n\n /** @type {String} generic title */\n this.title = attributes.title || '';\n /** @type {String} generic subtitle */\n this.subtitle = attributes.subtitle || '';\n\n this._setUpListeners();\n },\n\n // ------------------------------------------------------------------------ listeners\n /** create any event listeners for the list */\n _setUpListeners : function(){\n this.off();\n\n //TODO: move errorHandler down into list-view from history-view or\n // pass to global error handler (Galaxy)\n this.on({\n error: function( model, xhr, options, msg, details ){\n //this.errorHandler( model, xhr, options, msg, details );\n console.error( model, xhr, options, msg, details );\n },\n // show hide the loading indicator\n loading: function(){\n this._showLoadingIndicator( 'loading...', 40 );\n },\n 'loading-done': function(){\n this._hideLoadingIndicator( 40 );\n },\n });\n\n // throw the first render up as a diff namespace using once (for outside consumption)\n this.once( 'rendered', function(){\n this.trigger( 'rendered:initial', this );\n });\n\n this._setUpCollectionListeners();\n this._setUpViewListeners();\n return this;\n },\n\n /** create and return a collection for when none is initially passed */\n _createDefaultCollection : function(){\n // override\n return new this.collectionClass([]);\n },\n\n /** listening for collection events */\n _setUpCollectionListeners : function(){\n this.log( this + '._setUpCollectionListeners', this.collection );\n this.stopListening( this.collection );\n\n // bubble up error events\n this.listenTo( this.collection, {\n error : function( model, xhr, options, msg, details ){\n this.trigger( 'error', model, xhr, options, msg, details );\n },\n update : function( collection, options ){\n var changes = options.changes;\n // console.info( collection + ', update:', changes, '\\noptions:', options );\n // more than one: render everything\n if( options.renderAll || ( changes.added.length + changes.removed.length > 1 ) ){\n return this.renderItems();\n }\n // otherwise, let the single add/remove handlers do it\n if( changes.added.length === 1 ){\n return this.addItemView( _.first( changes.added ), collection, options );\n }\n if( changes.removed.length === 1 ){\n return this.removeItemView( _.first( changes.removed ), collection, options );\n }\n }\n });\n return this;\n },\n\n /** listening for sub-view events that bubble up with the 'view:' prefix */\n _setUpViewListeners : function(){\n this.log( this + '._setUpViewListeners' );\n\n // shift to select a range\n this.on({\n 'view:selected': function( view, ev ){\n if( ev && ev.shiftKey && this.lastSelected ){\n var lastSelectedView = this.viewFromModelId( this.lastSelected );\n if( lastSelectedView ){\n this.selectRange( view, lastSelectedView );\n }\n } else if( ev && ev.altKey && !this.selecting ){\n this.showSelectors();\n }\n this.selected.push( view.model.id );\n this.lastSelected = view.model.id;\n },\n\n 'view:de-selected': function( view, ev ){\n this.selected = _.without( this.selected, view.model.id );\n }\n });\n },\n\n // ------------------------------------------------------------------------ rendering\n /** Render this content, set up ui.\n * @param {Number or String} speed the speed of the render\n */\n render : function( speed ){\n this.log( this + '.render', speed );\n var $newRender = this._buildNewRender();\n this._setUpBehaviors( $newRender );\n this._queueNewRender( $newRender, speed );\n return this;\n },\n\n /** Build a temp div containing the new children for the view's $el. */\n _buildNewRender : function(){\n this.debug( this + '(ListPanel)._buildNewRender' );\n var $newRender = $( this.templates.el( {}, this ) );\n this._renderControls( $newRender );\n this._renderTitle( $newRender );\n this._renderSubtitle( $newRender );\n this._renderSearch( $newRender );\n this.renderItems( $newRender );\n return $newRender;\n },\n\n /** Build a temp div containing the new children for the view's $el. */\n _renderControls : function( $newRender ){\n this.debug( this + '(ListPanel)._renderControls' );\n var $controls = $( this.templates.controls( {}, this ) );\n $newRender.find( '.controls' ).replaceWith( $controls );\n return $controls;\n },\n\n /** return a jQuery object containing the title DOM */\n _renderTitle : function( $where ){\n //$where = $where || this.$el;\n //$where.find( '.title' ).replaceWith( ... )\n },\n\n /** return a jQuery object containing the subtitle DOM (if any) */\n _renderSubtitle : function( $where ){\n //$where = $where || this.$el;\n //$where.find( '.title' ).replaceWith( ... )\n },\n\n /** Fade out the old el, swap in the new contents, then fade in.\n * @param {Number or String} speed jq speed to use for rendering effects\n * @fires rendered when rendered\n */\n _queueNewRender : function( $newRender, speed ) {\n speed = ( speed === undefined )?( this.fxSpeed ):( speed );\n var panel = this;\n panel.log( '_queueNewRender:', $newRender, speed );\n\n $( panel ).queue( 'fx', [\n function( next ){\n panel.$el.fadeOut( speed, next );\n },\n function( next ){\n panel._swapNewRender( $newRender );\n next();\n },\n function( next ){\n panel.$el.fadeIn( speed, next );\n },\n function( next ){\n panel.trigger( 'rendered', panel );\n next();\n }\n ]);\n },\n\n /** empty out the current el, move the $newRender's children in */\n _swapNewRender : function( $newRender ){\n this.$el.empty().attr( 'class', this.className ).append( $newRender.children() );\n if( this.selecting ){ this.showSelectors( 0 ); }\n return this;\n },\n\n /** Set up any behaviors, handlers (ep. plugins) that need to be called when the entire view has been built but\n * not attached to the page yet.\n */\n _setUpBehaviors : function( $where ){\n $where = $where || this.$el;\n this.$controls( $where ).find('[title]').tooltip();\n // set up the pupup for actions available when multi selecting\n this._renderMultiselectActionMenu( $where );\n return this;\n },\n\n /** render a menu containing the actions available to sets of selected items */\n _renderMultiselectActionMenu : function( $where ){\n $where = $where || this.$el;\n var $menu = $where.find( '.list-action-menu' ),\n actions = this.multiselectActions();\n if( !actions.length ){\n return $menu.empty();\n }\n\n var $newMenu = $([\n '
              ',\n '',\n '
                ', '
              ',\n '
              '\n ].join(''));\n var $actions = actions.map( function( action ){\n var html = [ '
            • ', action.html, '
            • ' ].join( '' );\n return $( html ).click( function( ev ){\n ev.preventDefault();\n return action.func( ev );\n });\n });\n $newMenu.find( 'ul' ).append( $actions );\n $menu.replaceWith( $newMenu );\n return $newMenu;\n },\n\n /** return a list of plain objects used to render multiselect actions menu. Each object should have:\n * html: an html string used as the anchor contents\n * func: a function called when the anchor is clicked (passed the click event)\n */\n multiselectActions : function(){\n return [];\n },\n\n // ------------------------------------------------------------------------ sub-$element shortcuts\n /** the scroll container for this panel - can be $el, $el.parent(), or grandparent depending on context */\n $scrollContainer : function( $where ){\n // override or set via attributes.$scrollContainer\n return ( $where || this.$el ).parent().parent();\n },\n /** convenience selector for the section that displays the list controls */\n $controls : function( $where ){\n return ( $where || this.$el ).find( '> .controls' );\n },\n /** list-items: where the subviews are contained in the view's dom */\n $list : function( $where ){\n return ( $where || this.$el ).find( '> .list-items' );\n },\n /** container where list messages are attached */\n $messages : function( $where ){\n //TODO: controls isn't really correct here (only for ModelListPanel)\n return ( $where || this.$el ).find( '> .controls .messages' );\n },\n /** the message displayed when no views can be shown (no views, none matching search) */\n $emptyMessage : function( $where ){\n return ( $where || this.$el ).find( '> .empty-message' );\n },\n\n // ------------------------------------------------------------------------ hda sub-views\n /** render the subviews for the list's collection */\n renderItems : function( $whereTo ){\n $whereTo = $whereTo || this.$el;\n var panel = this;\n panel.log( this + '.renderItems', $whereTo );\n\n var $list = panel.$list( $whereTo );\n panel.freeViews();\n // console.log( 'views freed' );\n //TODO:? cache and re-use views?\n var shownModels = panel._filterCollection();\n // console.log( 'models filtered:', shownModels );\n\n panel.views = shownModels.map( function( itemModel ){\n var view = panel._createItemView( itemModel );\n return view;\n });\n\n $list.empty();\n // console.log( 'list emptied' );\n if( panel.views.length ){\n panel._attachItems( $whereTo );\n // console.log( 'items attached' );\n }\n panel._renderEmptyMessage( $whereTo ).toggle( !panel.views.length );\n panel.trigger( 'views:ready', panel.views );\n\n // console.log( '------------------------------------------- rendering items' );\n return panel.views;\n },\n\n /** Filter the collection to only those models that should be currently viewed */\n _filterCollection : function(){\n // override this\n var panel = this;\n return panel.collection.filter( _.bind( panel._filterItem, panel ) );\n },\n\n /** Should the model be viewable in the current state?\n * Checks against this.filters and this.searchFor\n */\n _filterItem : function( model ){\n // override this\n var panel = this;\n return ( _.every( panel.filters.map( function( fn ){ return fn.call( model ); }) ) )\n && ( !panel.searchFor || model.matchesAll( panel.searchFor ) );\n },\n\n /** Create a view for a model and set up it's listeners */\n _createItemView : function( model ){\n var ViewClass = this._getItemViewClass( model );\n var options = _.extend( this._getItemViewOptions( model ), {\n model : model\n });\n var view = new ViewClass( options );\n this._setUpItemViewListeners( view );\n return view;\n },\n\n /** Free a view for a model. Note: does not remove it from the DOM */\n _destroyItemView : function( view ){\n this.stopListening( view );\n this.views = _.without( this.views, view );\n },\n\n _destroyItemViews : function( view ){\n var self = this;\n self.views.forEach( function( v ){\n self.stopListening( v );\n });\n self.views = [];\n return self;\n },\n\n /** free any sub-views the list has */\n freeViews : function(){\n return this._destroyItemViews();\n },\n\n /** Get the bbone view class based on the model */\n _getItemViewClass : function( model ){\n // override this\n return this.viewClass;\n },\n\n /** Get the options passed to the new view based on the model */\n _getItemViewOptions : function( model ){\n // override this\n return {\n //logger : this.logger,\n fxSpeed : this.fxSpeed,\n expanded : false,\n selectable : this.selecting,\n selected : _.contains( this.selected, model.id ),\n draggable : this.dragItems\n };\n },\n\n /** Set up listeners for new models */\n _setUpItemViewListeners : function( view ){\n var panel = this;\n // send all events to the panel, re-namspaceing them with the view prefix\n this.listenTo( view, 'all', function(){\n var args = Array.prototype.slice.call( arguments, 0 );\n args[0] = 'view:' + args[0];\n panel.trigger.apply( panel, args );\n });\n\n // drag multiple - hijack ev.setData to add all selected items\n this.listenTo( view, 'draggable:dragstart', function( ev, v ){\n //TODO: set multiple drag data here\n var json = {},\n selected = this.getSelectedModels();\n if( selected.length ){\n json = selected.toJSON();\n } else {\n json = [ v.model.toJSON() ];\n }\n ev.dataTransfer.setData( 'text', JSON.stringify( json ) );\n //ev.dataTransfer.setDragImage( v.el, 60, 60 );\n }, this );\n\n return panel;\n },\n\n /** Attach views in this.views to the model based on $whereTo */\n _attachItems : function( $whereTo ){\n var self = this;\n // console.log( '_attachItems:', $whereTo, this.$list( $whereTo ) );\n //ASSUMES: $list has been emptied\n this.$list( $whereTo ).append( this.views.map( function( view ){\n return self._renderItemView$el( view );\n }));\n return this;\n },\n\n /** get a given subview's $el (or whatever may wrap it) and return it */\n _renderItemView$el : function( view ){\n // useful to wrap and override\n return view.render(0).$el;\n },\n\n /** render the empty/none-found message */\n _renderEmptyMessage : function( $whereTo ){\n this.debug( '_renderEmptyMessage', $whereTo, this.searchFor );\n var text = this.searchFor? this.noneFoundMsg : this.emptyMsg;\n return this.$emptyMessage( $whereTo ).text( text );\n },\n\n /** expand all item views */\n expandAll : function(){\n _.each( this.views, function( view ){\n view.expand();\n });\n },\n\n /** collapse all item views */\n collapseAll : function(){\n _.each( this.views, function( view ){\n view.collapse();\n });\n },\n\n // ------------------------------------------------------------------------ collection/views syncing\n /** Add a view (if the model should be viewable) to the panel */\n addItemView : function( model, collection, options ){\n // console.log( this + '.addItemView:', model );\n var panel = this;\n // get the index of the model in the list of filtered models shown by this list\n // in order to insert the view in the proper place\n //TODO:? potentially expensive\n var modelIndex = panel._filterCollection().indexOf( model );\n if( modelIndex === -1 ){ return undefined; }\n var view = panel._createItemView( model );\n // console.log( 'adding and rendering:', modelIndex, view.toString() );\n\n $( view ).queue( 'fx', [\n function( next ){\n // hide the empty message first if only view\n if( panel.$emptyMessage().is( ':visible' ) ){\n panel.$emptyMessage().fadeOut( panel.fxSpeed, next );\n } else {\n next();\n }\n },\n function( next ){\n panel._attachView( view, modelIndex );\n next();\n }\n ]);\n return view;\n },\n\n /** internal fn to add view (to both panel.views and panel.$list) */\n _attachView : function( view, modelIndex, useFx ){\n // console.log( this + '._attachView:', view, modelIndex, useFx );\n useFx = _.isUndefined( useFx )? true : useFx;\n modelIndex = modelIndex || 0;\n var panel = this;\n\n // use the modelIndex to splice into views and insert at the proper index in the DOM\n panel.views.splice( modelIndex, 0, view );\n panel._insertIntoListAt( modelIndex, panel._renderItemView$el( view ).hide() );\n\n panel.trigger( 'view:attached', view );\n if( useFx ){\n view.$el.slideDown( panel.fxSpeed, function(){\n panel.trigger( 'view:attached:rendered' );\n });\n } else {\n view.$el.show();\n panel.trigger( 'view:attached:rendered' );\n }\n return view;\n },\n\n /** insert a jq object as a child of list-items at the specified *DOM index* */\n _insertIntoListAt : function( index, $what ){\n // console.log( this + '._insertIntoListAt:', index, $what );\n var $list = this.$list();\n if( index === 0 ){\n $list.prepend( $what );\n } else {\n $list.children().eq( index - 1 ).after( $what );\n }\n return $what;\n },\n\n /** Remove a view from the panel (if found) */\n removeItemView : function( model, collection, options ){\n var panel = this;\n var view = _.find( panel.views, function( v ){ return v.model === model; });\n if( !view ){ return undefined; }\n panel.views = _.without( panel.views, view );\n panel.trigger( 'view:removed', view );\n\n // potentially show the empty message if no views left\n // use anonymous queue here - since remove can happen multiple times\n $({}).queue( 'fx', [\n function( next ){\n view.$el.fadeOut( panel.fxSpeed, next );\n },\n function( next ){\n view.remove();\n panel.trigger( 'view:removed:rendered' );\n if( !panel.views.length ){\n panel._renderEmptyMessage().fadeIn( panel.fxSpeed, next );\n } else {\n next();\n }\n }\n ]);\n return view;\n },\n\n /** get views based on model.id */\n viewFromModelId : function( id ){\n return _.find( this.views, function( v ){ return v.model.id === id; });\n },\n\n /** get views based on model */\n viewFromModel : function( model ){\n return model ? this.viewFromModelId( model.id ) : undefined;\n },\n\n /** get views based on model properties */\n viewsWhereModel : function( properties ){\n return this.views.filter( function( view ){\n return _.isMatch( view.model.attributes, properties );\n });\n },\n\n /** A range of views between (and including) viewA and viewB */\n viewRange : function( viewA, viewB ){\n if( viewA === viewB ){ return ( viewA )?( [ viewA ] ):( [] ); }\n\n var indexA = this.views.indexOf( viewA ),\n indexB = this.views.indexOf( viewB );\n\n // handle not found\n if( indexA === -1 || indexB === -1 ){\n if( indexA === indexB ){ return []; }\n return ( indexA === -1 )?( [ viewB ] ):( [ viewA ] );\n }\n // reverse if indeces are\n //note: end inclusive\n return ( indexA < indexB )?\n this.views.slice( indexA, indexB + 1 ) :\n this.views.slice( indexB, indexA + 1 );\n },\n\n // ------------------------------------------------------------------------ searching\n /** render a search input for filtering datasets shown\n * (see SearchableMixin in base-mvc for implementation of the actual searching)\n * return will start the search\n * esc will clear the search\n * clicking the clear button will clear the search\n * uses searchInput in ui.js\n */\n _renderSearch : function( $where ){\n $where.find( '.controls .search-input' ).searchInput({\n placeholder : this.searchPlaceholder,\n initialVal : this.searchFor,\n onfirstsearch : _.bind( this._firstSearch, this ),\n onsearch : _.bind( this.searchItems, this ),\n onclear : _.bind( this.clearSearch, this )\n });\n return $where;\n },\n\n /** What to do on the first search entered */\n _firstSearch : function( searchFor ){\n // override to load model details if necc.\n this.log( 'onFirstSearch', searchFor );\n return this.searchItems( searchFor );\n },\n\n /** filter view list to those that contain the searchFor terms */\n searchItems : function( searchFor, force ){\n this.log( 'searchItems', searchFor, this.searchFor, force );\n if( !force && this.searchFor === searchFor ){ return this; }\n this.searchFor = searchFor;\n this.renderItems();\n this.trigger( 'search:searching', searchFor, this );\n var $search = this.$( '> .controls .search-query' );\n if( $search.val() !== searchFor ){\n $search.val( searchFor );\n }\n return this;\n },\n\n /** clear the search filters and show all views that are normally shown */\n clearSearch : function( searchFor ){\n //this.log( 'onSearchClear', this );\n this.searchFor = '';\n this.trigger( 'search:clear', this );\n this.$( '> .controls .search-query' ).val( '' );\n this.renderItems();\n return this;\n },\n\n // ------------------------------------------------------------------------ selection\n /** @type Integer when the number of list item views is >= to this, don't animate selectors */\n THROTTLE_SELECTOR_FX_AT : 20,\n\n /** show selectors on all visible itemViews and associated controls */\n showSelectors : function( speed ){\n speed = ( speed !== undefined )?( speed ):( this.fxSpeed );\n this.selecting = true;\n this.$( '.list-actions' ).slideDown( speed );\n speed = this.views.length >= this.THROTTLE_SELECTOR_FX_AT? 0 : speed;\n _.each( this.views, function( view ){\n view.showSelector( speed );\n });\n //this.selected = [];\n //this.lastSelected = null;\n },\n\n /** hide selectors on all visible itemViews and associated controls */\n hideSelectors : function( speed ){\n speed = ( speed !== undefined )?( speed ):( this.fxSpeed );\n this.selecting = false;\n this.$( '.list-actions' ).slideUp( speed );\n speed = this.views.length >= this.THROTTLE_SELECTOR_FX_AT? 0 : speed;\n _.each( this.views, function( view ){\n view.hideSelector( speed );\n });\n this.selected = [];\n this.lastSelected = null;\n },\n\n /** show or hide selectors on all visible itemViews and associated controls */\n toggleSelectors : function(){\n if( !this.selecting ){\n this.showSelectors();\n } else {\n this.hideSelectors();\n }\n },\n\n /** select all visible items */\n selectAll : function( event ){\n _.each( this.views, function( view ){\n view.select( event );\n });\n },\n\n /** deselect all visible items */\n deselectAll : function( event ){\n this.lastSelected = null;\n _.each( this.views, function( view ){\n view.deselect( event );\n });\n },\n\n /** select a range of datasets between A and B */\n selectRange : function( viewA, viewB ){\n var range = this.viewRange( viewA, viewB );\n _.each( range, function( view ){\n view.select();\n });\n return range;\n },\n\n /** return an array of all currently selected itemViews */\n getSelectedViews : function(){\n return _.filter( this.views, function( v ){\n return v.selected;\n });\n },\n\n /** return a collection of the models of all currenly selected items */\n getSelectedModels : function(){\n // console.log( '(getSelectedModels)' );\n return new this.collection.constructor( _.map( this.getSelectedViews(), function( view ){\n return view.model;\n }));\n },\n\n // ------------------------------------------------------------------------ loading indicator\n /** hide the $el and display a loading indicator (in the $el's parent) when loading new data */\n _showLoadingIndicator : function( msg, speed, callback ){\n this.debug( '_showLoadingIndicator', this.indicator, msg, speed, callback );\n speed = ( speed !== undefined )?( speed ):( this.fxSpeed );\n if( !this.indicator ){\n this.indicator = new LoadingIndicator( this.$el );\n this.debug( '\\t created', this.indicator );\n }\n if( !this.$el.is( ':visible' ) ){\n this.indicator.show( 0, callback );\n } else {\n this.$el.fadeOut( speed );\n this.indicator.show( msg, speed, callback );\n }\n },\n\n /** hide the loading indicator */\n _hideLoadingIndicator : function( speed, callback ){\n this.debug( '_hideLoadingIndicator', this.indicator, speed, callback );\n speed = ( speed !== undefined )?( speed ):( this.fxSpeed );\n if( this.indicator ){\n this.indicator.hide( speed, callback );\n }\n },\n\n // ------------------------------------------------------------------------ scrolling\n /** get the current scroll position of the panel in its parent */\n scrollPosition : function(){\n return this.$scrollContainer().scrollTop();\n },\n\n /** set the current scroll position of the panel in its parent */\n scrollTo : function( pos, speed ){\n speed = speed || 0;\n this.$scrollContainer().animate({ scrollTop: pos }, speed );\n return this;\n },\n\n /** Scrolls the panel to the top. */\n scrollToTop : function( speed ){\n return this.scrollTo( 0, speed );\n },\n\n /** scroll to the given view in list-items */\n scrollToItem : function( view, speed ){\n if( !view ){ return this; }\n return this;\n },\n\n /** Scrolls the panel to show the content with the given id. */\n scrollToId : function( id, speed ){\n return this.scrollToItem( this.viewFromModelId( id ), speed );\n },\n\n // ------------------------------------------------------------------------ panel events\n /** event map */\n events : {\n 'click .select-all' : 'selectAll',\n 'click .deselect-all' : 'deselectAll'\n },\n\n // ------------------------------------------------------------------------ misc\n /** Return a string rep of the panel */\n toString : function(){\n return 'ListPanel(' + this.collection + ')';\n }\n});\n\n// ............................................................................ TEMPLATES\n/** underscore templates */\nListPanel.prototype.templates = (function(){\n\n var elTemplate = BASE_MVC.wrapTemplate([\n // temp container\n '
              ',\n '
              ',\n '
              ',\n '
              ',\n '
              '\n ]);\n\n var controlsTemplate = BASE_MVC.wrapTemplate([\n '
              ',\n '
              ',\n '
              <%- view.title %>
              ',\n '
              ',\n '
              <%- view.subtitle %>
              ',\n // buttons, controls go here\n '
              ',\n // deleted msg, etc.\n '
              ',\n\n '
              ',\n '
              ',\n '
              ',\n\n // show when selectors are shown\n '
              ',\n '
              ',\n '',\n '',\n '
              ',\n '
              ',\n '
              ',\n '
              ',\n '
              '\n ]);\n\n return {\n el : elTemplate,\n controls : controlsTemplate\n };\n}());\n\n\n//=============================================================================\n/** View for a model that has a sub-collection (e.g. History, DatasetCollection)\n * Allows:\n * the model to be reset\n * auto assign panel.collection to panel.model[ panel.modelCollectionKey ]\n *\n */\nvar ModelListPanel = ListPanel.extend({\n\n /** key of attribute in model to assign to this.collection */\n modelCollectionKey : 'contents',\n\n initialize : function( attributes ){\n ListPanel.prototype.initialize.call( this, attributes );\n this.selecting = ( attributes.selecting !== undefined )? attributes.selecting : false;\n\n this.setModel( this.model, attributes );\n },\n\n /** release/free/shutdown old models and set up panel for new models\n * @fires new-model with the panel as parameter\n */\n setModel : function( model, attributes ){\n attributes = attributes || {};\n this.debug( this + '.setModel:', model, attributes );\n\n this.freeModel();\n this.freeViews();\n\n if( model ){\n var oldModelId = this.model? this.model.get( 'id' ): null;\n\n // set up the new model with user, logger, storage, events\n this.model = model;\n if( this.logger ){\n this.model.logger = this.logger;\n }\n this._setUpModelListeners();\n\n //TODO: relation btwn model, collection becoming tangled here\n // free the collection, and assign the new collection to either\n // the model[ modelCollectionKey ], attributes.collection, or an empty vanilla collection\n this.stopListening( this.collection );\n this.collection = this.model[ this.modelCollectionKey ]\n || attributes.collection\n || this._createDefaultCollection();\n this._setUpCollectionListeners();\n\n if( oldModelId && model.get( 'id' ) !== oldModelId ){\n this.trigger( 'new-model', this );\n }\n }\n return this;\n },\n\n /** free the current model and all listeners for it, free any views for the model */\n freeModel : function(){\n // stop/release the previous model, and clear cache to sub-views\n if( this.model ){\n this.stopListening( this.model );\n //TODO: see base-mvc\n //this.model.free();\n //this.model = null;\n }\n return this;\n },\n\n // ------------------------------------------------------------------------ listening\n /** listening for model events */\n _setUpModelListeners : function(){\n // override\n this.log( this + '._setUpModelListeners', this.model );\n // bounce model errors up to the panel\n this.listenTo( this.model, 'error', function(){\n var args = Array.prototype.slice.call( arguments, 0 );\n //args.unshift( 'model:error' );\n args.unshift( 'error' );\n this.trigger.apply( this, args );\n }, this );\n\n // debugging\n if( this.logger ){\n this.listenTo( this.model, 'all', function( event ){\n this.info( this + '(model)', event, arguments );\n });\n }\n return this;\n },\n\n /** Build a temp div containing the new children for the view's $el.\n */\n _renderControls : function( $newRender ){\n this.debug( this + '(ModelListPanel)._renderControls' );\n var json = this.model? this.model.toJSON() : {},\n $controls = $( this.templates.controls( json, this ) );\n $newRender.find( '.controls' ).replaceWith( $controls );\n return $controls;\n },\n\n // ------------------------------------------------------------------------ misc\n /** Return a string rep of the panel */\n toString : function(){\n return 'ModelListPanel(' + this.model + ')';\n }\n});\n\n// ............................................................................ TEMPLATES\n/** underscore templates */\nModelListPanel.prototype.templates = (function(){\n\n var controlsTemplate = BASE_MVC.wrapTemplate([\n '
              ',\n '
              ',\n //TODO: this is really the only difference - consider factoring titlebar out\n '
              <%- model.name %>
              ',\n '
              ',\n '
              <%- view.subtitle %>
              ',\n '
              ',\n '
              ',\n\n '
              ',\n '
              ',\n '
              ',\n\n '
              ',\n '
              ',\n '',\n '',\n '
              ',\n '
              ',\n '
              ',\n '
              ',\n '
              '\n ]);\n\n return _.extend( _.clone( ListPanel.prototype.templates ), {\n controls : controlsTemplate\n });\n}());\n\n\n//=============================================================================\n return {\n ListPanel : ListPanel,\n ModelListPanel : ModelListPanel\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/list/list-view.js\n ** module id = 76\n ** module chunks = 3\n **/","define([\n \"mvc/base-mvc\",\n \"utils/localization\"\n], function( baseMVC, _l ){\n// =============================================================================\n/** A view on any model that has a 'tags' attribute (a list of tag strings)\n * Incorporates the select2 jQuery plugin for tags display/editing:\n * http://ivaynberg.github.io/select2/\n */\nvar TagsEditor = Backbone.View\n .extend( baseMVC.LoggableMixin )\n .extend( baseMVC.HiddenUntilActivatedViewMixin ).extend({\n\n tagName : 'div',\n className : 'tags-display',\n\n /** Set up listeners, parse options */\n initialize : function( options ){\n //console.debug( this, options );\n // only listen to the model only for changes to tags - re-render\n this.listenTo( this.model, 'change:tags', function(){\n this.render();\n });\n this.hiddenUntilActivated( options.$activator, options );\n },\n\n /** Build the DOM elements, call select to on the created input, and set up behaviors */\n render : function(){\n var view = this;\n this.$el.html( this._template() );\n\n this.$input().select2({\n placeholder : 'Add tags',\n width : '100%',\n tags : function(){\n // initialize possible tags in the dropdown based on all the tags the user has used so far\n return view._getTagsUsed();\n }\n });\n\n this._setUpBehaviors();\n return this;\n },\n\n /** @returns {String} the html text used to build the view's DOM */\n _template : function(){\n return [\n //TODO: make prompt optional\n '',\n // set up initial tags by adding as CSV to input vals (necc. to init select2)\n ''\n ].join( '' );\n },\n\n /** @returns {String} the sorted, comma-separated tags from the model */\n tagsToCSV : function(){\n var tagsArray = this.model.get( 'tags' );\n if( !_.isArray( tagsArray ) || _.isEmpty( tagsArray ) ){\n return '';\n }\n return tagsArray.map( function( tag ){\n return _.escape( tag );\n }).sort().join( ',' );\n },\n\n /** @returns {jQuery} the input for this view */\n $input : function(){\n return this.$el.find( 'input.tags-input' );\n },\n\n /** @returns {String[]} all tags used by the current user */\n _getTagsUsed : function(){\n//TODO: global\n return Galaxy.user.get( 'tags_used' );\n },\n\n /** set up any event listeners on the view's DOM (mostly handled by select2) */\n _setUpBehaviors : function(){\n var view = this;\n this.$input().on( 'change', function( event ){\n // save the model's tags in either remove or added event\n view.model.save({ tags: event.val }, { silent: true });\n // if it's new, add the tag to the users tags\n if( event.added ){\n //??: solve weird behavior in FF on test.galaxyproject.org where\n // event.added.text is string object: 'String{ 0=\"o\", 1=\"n\", 2=\"e\" }'\n view._addNewTagToTagsUsed( event.added.text + '' );\n }\n });\n },\n\n /** add a new tag (if not already there) to the list of all tags used by the user\n * @param {String} newTag the tag to add to the list of used\n */\n _addNewTagToTagsUsed : function( newTag ){\n//TODO: global\n var tagsUsed = Galaxy.user.get( 'tags_used' );\n if( !_.contains( tagsUsed, newTag ) ){\n tagsUsed.push( newTag );\n tagsUsed.sort();\n Galaxy.user.set( 'tags_used', tagsUsed );\n }\n },\n\n /** shut down event listeners and remove this view's DOM */\n remove : function(){\n this.$input.off();\n this.stopListening( this.model );\n Backbone.View.prototype.remove.call( this );\n },\n\n /** string rep */\n toString : function(){ return [ 'TagsEditor(', this.model + '', ')' ].join(''); }\n});\n\n// =============================================================================\nreturn {\n TagsEditor : TagsEditor\n};\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/tag.js\n ** module id = 77\n ** module chunks = 3\n **/","define([\n \"utils/localization\"\n], function( _l ){\n'use strict';\n\n//TODO: toastr is another possibility - I didn't see where I might add details, tho\n\n/* ============================================================================\nError modals meant to replace the o-so-easy alerts.\n\nThese are currently styled as errormessages but use the Galaxy.modal\ninfrastructure to be shown/closed. They're capable of showing details in a\ntogglable dropdown and the details are formatted in a pre.\n\nExample:\n errorModal( 'Heres a message', 'A Title', { some_details: 'here' });\n errorModal( 'Heres a message' ); // no details, title is 'Error'\n\nThere are three specialized forms:\n offlineErrorModal a canned response for when there's no connection\n badGatewayErrorModal canned response for when Galaxy is restarting\n ajaxErrorModal plugable into any Backbone class as an\n error event handler by accepting the error args: model, xhr, options\n\nExamples:\n if( navigator.offLine ){ offlineErrorModal(); }\n if( xhr.status === 502 ){ badGatewayErrorModal(); }\n this.listenTo( this.model, 'error', ajaxErrorModal );\n\n============================================================================ */\n\nvar CONTACT_MSG = _l( 'Please contact a Galaxy administrator if the problem persists.' );\nvar DEFAULT_AJAX_ERR_MSG = _l( 'An error occurred while updating information with the server.' );\nvar DETAILS_MSG = _l( 'The following information can assist the developers in finding the source of the error:' );\n\n/** private helper that builds the modal and handles adding details */\nfunction _errorModal( message, title, details ){\n // create and return the modal, adding details button only if needed\n Galaxy.modal.show({\n title : title,\n body : message,\n closing_events : true,\n buttons : { Ok: function(){ Galaxy.modal.hide(); } },\n });\n Galaxy.modal.$el.addClass( 'error-modal' );\n\n if( details ){\n Galaxy.modal.$( '.error-details' ).add( Galaxy.modal.$( 'button:contains(\"Details\")' ) ).remove();\n $( '
              ' ).addClass( 'error-details' )\n .hide().appendTo( Galaxy.modal.$( '.modal-content' ) )\n .append([\n $( '

              ' ).text( DETAILS_MSG ),\n $( '

              ' ).text( JSON.stringify( details, null, '  ' ) )\n            ]);\n\n        $( '' )\n            .appendTo( Galaxy.modal.$( '.buttons' ) )\n            .click( function(){ Galaxy.modal.$( '.error-details' ).toggle(); });\n    }\n    return Galaxy.modal;\n}\n\n/** Display a modal showing an error message but fallback to alert if there's no modal */\nfunction errorModal( message, title, details ){\n    if( !message ){ return; }\n\n    message = _l( message );\n    title = _l( title ) || _l( 'Error:' );\n    if( window.Galaxy && Galaxy.modal ){\n        return _errorModal( message, title, details );\n    }\n\n    alert( title + '\\n\\n' + message );\n    console.log( 'error details:', JSON.stringify( details ) );\n}\n\n\n// ----------------------------------------------------------------------------\n/** display a modal when the user may be offline */\nfunction offlineErrorModal(){\n    return errorModal(\n        _l( 'You appear to be offline. Please check your connection and try again.' ),\n        _l( 'Offline?' )\n    );\n}\n\n\n// ----------------------------------------------------------------------------\n/** 502 messages that should be displayed when galaxy is restarting */\nfunction badGatewayErrorModal(){\n    return errorModal(\n        _l( 'Galaxy is currently unreachable. Please try again in a few minutes.' ) + ' ' + CONTACT_MSG,\n        _l( 'Cannot connect to Galaxy' )\n    );\n}\n\n\n// ----------------------------------------------------------------------------\n/** display a modal (with details) about a failed Backbone ajax operation */\nfunction ajaxErrorModal( model, xhr, options, message, title ){\n    message = message || DEFAULT_AJAX_ERR_MSG;\n    message += ' ' + CONTACT_MSG;\n    title = title || _l( 'An error occurred' );\n    var details = _ajaxDetails( model, xhr, options );\n    return errorModal( message, title, details );\n}\n\n/** build details which may help debugging the ajax call */\nfunction _ajaxDetails( model, xhr, options ){\n    return {\n//TODO: still can't manage Raven id\n        raven       : _.result( window.Raven, 'lastEventId' ),\n        userAgent   : navigator.userAgent,\n        onLine      : navigator.onLine,\n        version     : _.result( Galaxy.config, 'version_major' ),\n        xhr         : _.omit( xhr, _.functions( xhr ) ),\n        options     : _.omit( options, 'xhr' ),\n        // add ajax data from Galaxy object cache\n        url         : _.result( Galaxy.lastAjax, 'url' ),\n        data        : _.result( Galaxy.lastAjax, 'data' ),\n        // backbone stuff (auto-redacting email for user)\n        model       : _.result( model, 'toJSON' , model + '' ),\n        user        : _.omit( _.result( Galaxy.user, 'toJSON' ), 'email' ),\n    };\n}\n\n\n//=============================================================================\n    return {\n        errorModal          : errorModal,\n        offlineErrorModal   : offlineErrorModal,\n        badGatewayErrorModal: badGatewayErrorModal,\n        ajaxErrorModal      : ajaxErrorModal\n    };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/ui/error-modal.js\n ** module id = 78\n ** module chunks = 3\n **/","define([\n    //jquery\n    //backbone\n], function(){\n// =============================================================================\n/**\n * view for a popup menu\n */\nvar PopupMenu = Backbone.View.extend({\n//TODO: maybe better as singleton off the Galaxy obj\n    /** Cache the desired button element and options, set up the button click handler\n     *  NOTE: attaches this view as HTML/jQ data on the button for later use.\n     */\n    initialize: function( $button, options ){\n        // default settings\n        this.$button = $button;\n        if( !this.$button.length ){\n            this.$button = $( '
              ' );\n }\n this.options = options || [];\n this.$button.data( 'popupmenu', this );\n\n // set up button click -> open menu behavior\n var menu = this;\n this.$button.click( function( event ){\n // if there's already a menu open, remove it\n $( '.popmenu-wrapper' ).remove();\n menu._renderAndShow( event );\n return false;\n });\n },\n\n // render the menu, append to the page body at the click position, and set up the 'click-away' handlers, show\n _renderAndShow: function( clickEvent ){\n this.render();\n this.$el.appendTo( 'body' ).css( this._getShownPosition( clickEvent )).show();\n this._setUpCloseBehavior();\n },\n\n // render the menu\n // this menu doesn't attach itself to the DOM ( see _renderAndShow )\n render: function(){\n // render the menu body absolute and hidden, fill with template\n this.$el.addClass( 'popmenu-wrapper' ).hide()\n .css({ position : 'absolute' })\n .html( this.template( this.$button.attr( 'id' ), this.options ));\n\n // set up behavior on each link/anchor elem\n if( this.options.length ){\n var menu = this;\n //precondition: there should be one option per li\n this.$el.find( 'li' ).each( function( i, li ){\n var option = menu.options[i];\n\n // if the option has 'func', call that function when the anchor is clicked\n if( option.func ){\n $( this ).children( 'a.popupmenu-option' ).click( function( event ){\n option.func.call( menu, event, option );\n // We must preventDefault otherwise clicking \"cancel\"\n // on a purge or something still navigates and causes\n // the action.\n event.preventDefault();\n // bubble up so that an option click will call the close behavior\n });\n }\n });\n }\n return this;\n },\n\n template : function( id, options ){\n return [\n '
                ', this._templateOptions( options ), '
              '\n ].join( '' );\n },\n\n _templateOptions : function( options ){\n if( !options.length ){\n return '
            • (no options)
            • ';\n }\n return _.map( options, function( option ){\n if( option.divider ){\n return '
            • ';\n } else if( option.header ){\n return [ '
            • ', option.html, '
            • ' ].join( '' );\n }\n var href = option.href || 'javascript:void(0);',\n target = ( option.target )?( ' target=\"' + option.target + '\"' ):( '' ),\n check = ( option.checked )?( '' ):( '' );\n return [\n '
            • ',\n check, option.html,\n '
            • '\n ].join( '' );\n }).join( '' );\n },\n\n // get the absolute position/offset for the menu\n _getShownPosition : function( clickEvent ){\n\n // display menu horiz. centered on click...\n var menuWidth = this.$el.width();\n var x = clickEvent.pageX - menuWidth / 2 ;\n\n // adjust to handle horiz. scroll and window dimensions ( draw entirely on visible screen area )\n x = Math.min( x, $( document ).scrollLeft() + $( window ).width() - menuWidth - 5 );\n x = Math.max( x, $( document ).scrollLeft() + 5 );\n return {\n top: clickEvent.pageY,\n left: x\n };\n },\n\n // bind an event handler to all available frames so that when anything is clicked\n // the menu is removed from the DOM and the event handler unbinds itself\n _setUpCloseBehavior: function(){\n var menu = this;\n//TODO: alternately: focus hack, blocking overlay, jquery.blockui\n\n // function to close popup and unbind itself\n function closePopup( event ){\n $( document ).off( 'click.close_popup' );\n if( window && window.parent !== window ){\n try {\n $( window.parent.document ).off( \"click.close_popup\" );\n } catch( err ){}\n } else {\n try {\n $( 'iframe#galaxy_main' ).contents().off( \"click.close_popup\" );\n } catch( err ){}\n }\n menu.remove();\n }\n\n $( 'html' ).one( \"click.close_popup\", closePopup );\n if( window && window.parent !== window ){\n try {\n $( window.parent.document ).find( 'html' ).one( \"click.close_popup\", closePopup );\n } catch( err ){}\n } else {\n try {\n $( 'iframe#galaxy_main' ).contents().one( \"click.close_popup\", closePopup );\n } catch( err ){}\n }\n },\n\n // add a menu option/item at the given index\n addItem: function( item, index ){\n // append to end if no index\n index = ( index >= 0 ) ? index : this.options.length;\n this.options.splice( index, 0, item );\n return this;\n },\n\n // remove a menu option/item at the given index\n removeItem: function( index ){\n if( index >=0 ){\n this.options.splice( index, 1 );\n }\n return this;\n },\n\n // search for a menu option by its html\n findIndexByHtml: function( html ){\n for( var i = 0; i < this.options.length; i++ ){\n if( _.has( this.options[i], 'html' ) && ( this.options[i].html === html )){\n return i;\n }\n }\n return null;\n },\n\n // search for a menu option by its html\n findItemByHtml: function( html ){\n return this.options[( this.findIndexByHtml( html ))];\n },\n\n // string representation\n toString: function(){\n return 'PopupMenu';\n }\n});\n/** shortcut to new for when you don't need to preserve the ref */\nPopupMenu.create = function _create( $button, options ){\n return new PopupMenu( $button, options );\n};\n\n// -----------------------------------------------------------------------------\n// the following class functions are bridges from the original make_popupmenu and make_popup_menus\n// to the newer backbone.js PopupMenu\n\n/** Create a PopupMenu from simple map initial_options activated by clicking button_element.\n * Converts initial_options to object array used by PopupMenu.\n * @param {jQuery|DOMElement} button_element element which, when clicked, activates menu\n * @param {Object} initial_options map of key -> values, where\n * key is option text, value is fn to call when option is clicked\n * @returns {PopupMenu} the PopupMenu created\n */\nPopupMenu.make_popupmenu = function( button_element, initial_options ){\n var convertedOptions = [];\n _.each( initial_options, function( optionVal, optionKey ){\n var newOption = { html: optionKey };\n\n // keys with null values indicate: header\n if( optionVal === null ){ // !optionVal? (null only?)\n newOption.header = true;\n\n // keys with function values indicate: a menu option\n } else if( jQuery.type( optionVal ) === 'function' ){\n newOption.func = optionVal;\n }\n //TODO:?? any other special optionVals?\n // there was no divider option originally\n convertedOptions.push( newOption );\n });\n return new PopupMenu( $( button_element ), convertedOptions );\n};\n\n/** Find all anchors in $parent (using selector) and covert anchors into a PopupMenu options map.\n * @param {jQuery} $parent the element that contains the links to convert to options\n * @param {String} selector jq selector string to find links\n * @returns {Object[]} the options array to initialize a PopupMenu\n */\n//TODO: lose parent and selector, pass in array of links, use map to return options\nPopupMenu.convertLinksToOptions = function( $parent, selector ){\n $parent = $( $parent );\n selector = selector || 'a';\n var options = [];\n $parent.find( selector ).each( function( elem, i ){\n var option = {}, $link = $( elem );\n\n // convert link text to the option text (html) and the href into the option func\n option.html = $link.text();\n if( $link.attr( 'href' ) ){\n var linkHref = $link.attr( 'href' ),\n linkTarget = $link.attr( 'target' ),\n confirmText = $link.attr( 'confirm' );\n\n option.func = function(){\n // if there's a \"confirm\" attribute, throw up a confirmation dialog, and\n // if the user cancels - do nothing\n if( ( confirmText ) && ( !confirm( confirmText ) ) ){ return; }\n\n // if there's no confirm attribute, or the user accepted the confirm dialog:\n switch( linkTarget ){\n // relocate the center panel\n case '_parent':\n window.parent.location = linkHref;\n break;\n\n // relocate the entire window\n case '_top':\n window.top.location = linkHref;\n break;\n\n // relocate this panel\n default:\n window.location = linkHref;\n }\n };\n }\n options.push( option );\n });\n return options;\n};\n\n/** Create a single popupmenu from existing DOM button and anchor elements\n * @param {jQuery} $buttonElement the element that when clicked will open the menu\n * @param {jQuery} $menuElement the element that contains the anchors to convert into a menu\n * @param {String} menuElementLinkSelector jq selector string used to find anchors to be made into menu options\n * @returns {PopupMenu} the PopupMenu (Backbone View) that can render, control the menu\n */\nPopupMenu.fromExistingDom = function( $buttonElement, $menuElement, menuElementLinkSelector ){\n $buttonElement = $( $buttonElement );\n $menuElement = $( $menuElement );\n var options = PopupMenu.convertLinksToOptions( $menuElement, menuElementLinkSelector );\n // we're done with the menu (having converted it to an options map)\n $menuElement.remove();\n return new PopupMenu( $buttonElement, options );\n};\n\n/** Create all popupmenus within a document or a more specific element\n * @param {DOMElement} parent the DOM element in which to search for popupmenus to build (defaults to document)\n * @param {String} menuSelector jq selector string to find popupmenu menu elements (defaults to \"div[popupmenu]\")\n * @param {Function} buttonSelectorBuildFn the function to build the jq button selector.\n * Will be passed $menuElement, parent.\n * (Defaults to return '#' + $menuElement.attr( 'popupmenu' ); )\n * @returns {PopupMenu[]} array of popupmenus created\n */\nPopupMenu.make_popup_menus = function( parent, menuSelector, buttonSelectorBuildFn ){\n parent = parent || document;\n // orig. Glx popupmenu menus have a (non-std) attribute 'popupmenu'\n // which contains the id of the button that activates the menu\n menuSelector = menuSelector || 'div[popupmenu]';\n // default to (orig. Glx) matching button to menu by using the popupmenu attr of the menu as the id of the button\n buttonSelectorBuildFn = buttonSelectorBuildFn || function( $menuElement, parent ){\n return '#' + $menuElement.attr( 'popupmenu' );\n };\n\n // aggregate and return all PopupMenus\n var popupMenusCreated = [];\n $( parent ).find( menuSelector ).each( function(){\n var $menuElement = $( this ),\n $buttonElement = $( parent ).find( buttonSelectorBuildFn( $menuElement, parent ) );\n popupMenusCreated.push( PopupMenu.fromDom( $buttonElement, $menuElement ) );\n $buttonElement.addClass( 'popup' );\n });\n return popupMenusCreated;\n};\n\n\n// =============================================================================\n return PopupMenu;\n});\n\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/ui/popup-menu.js\n ** module id = 79\n ** module chunks = 3\n **/","/** This renders the content of the ftp popup **/\ndefine( [ 'utils/utils' ], function( Utils ) {\n return Backbone.View.extend({\n initialize: function( options ) {\n var self = this;\n this.options = Utils.merge( options, {\n class_add : 'upload-icon-button fa fa-square-o',\n class_remove : 'upload-icon-button fa fa-check-square-o',\n class_partial : 'upload-icon-button fa fa-minus-square-o',\n collection : null,\n onchange : function() {},\n onadd : function() {},\n onremove : function() {}\n } );\n this.collection = this.options.collection;\n this.setElement( this._template() );\n this.rows = [];\n Utils.get({\n url : Galaxy.root + 'api/remote_files',\n success : function( ftp_files ) { self._fill( ftp_files ) },\n error : function() { self._fill(); }\n });\n },\n\n /** Fill table with ftp entries */\n _fill: function( ftp_files ) {\n if ( ftp_files && ftp_files.length > 0 ) {\n this.$( '.upload-ftp-content' ).html( $( this._templateTable() ) );\n var size = 0;\n for ( index in ftp_files ) {\n this.rows.push( this._add( ftp_files[ index ] ) );\n size += ftp_files[ index ].size;\n }\n this.$( '.upload-ftp-number' ).html( ftp_files.length + ' files' );\n this.$( '.upload-ftp-disk' ).html( Utils.bytesToString ( size, true ) );\n if ( this.collection ) {\n var self = this;\n this.$( '._has_collection' ).show();\n this.$select_all = this.$( '.upload-selectall' ).addClass( this.options.class_add );\n this.$select_all.on( 'click', function() {\n var add = self.$select_all.hasClass( self.options.class_add );\n for ( index in ftp_files ) {\n var ftp_file = ftp_files[ index ];\n var model_index = self._find( ftp_file );\n if( !model_index && add || model_index && !add ) {\n self.rows[ index ].trigger( 'click' );\n }\n }\n });\n this._refresh();\n }\n } else {\n this.$( '.upload-ftp-content' ).html( $( this._templateInfo() ) );\n }\n this.$( '.upload-ftp-wait' ).hide();\n },\n\n /** Add file to table */\n _add: function( ftp_file ) {\n var self = this;\n var $it = $( this._templateRow( ftp_file ) );\n var $icon = $it.find( '.icon' );\n this.$( 'tbody' ).append( $it );\n if ( this.collection ) {\n $icon.addClass( this._find( ftp_file ) ? this.options.class_remove : this.options.class_add );\n $it.on('click', function() {\n var model_index = self._find( ftp_file );\n $icon.removeClass();\n if ( !model_index ) {\n self.options.onadd( ftp_file );\n $icon.addClass( self.options.class_remove );\n } else {\n self.options.onremove( model_index );\n $icon.addClass( self.options.class_add );\n }\n self._refresh();\n });\n } else {\n $it.on('click', function() { self.options.onchange( ftp_file ) } );\n }\n return $it;\n },\n\n /** Refresh select all button state */\n _refresh: function() {\n var filtered = this.collection.where( { file_mode: 'ftp', enabled: true } );\n this.$select_all.removeClass();\n if ( filtered.length == 0 ) {\n this.$select_all.addClass( this.options.class_add );\n } else {\n this.$select_all.addClass( filtered.length == this.rows.length ? this.options.class_remove : this.options.class_partial );\n }\n },\n\n /** Get model index */\n _find: function( ftp_file ) {\n var item = this.collection.findWhere({\n file_path : ftp_file.path,\n file_mode : 'ftp',\n enabled : true\n });\n return item && item.get('id');\n },\n\n /** Template of row */\n _templateRow: function( options ) {\n return '' +\n '
              ' +\n '' + options.path + '' +\n '' + Utils.bytesToString( options.size ) + '' +\n '' + options.ctime + '' +\n '';\n },\n\n /** Template of table */\n _templateTable: function() {\n return 'Available files: ' +\n '' +\n '' +\n '  ' +\n '' +\n '' +\n '' +\n '' +\n '' +\n '' +\n '' +\n '' +\n '' +\n '' +\n '' +\n '' +\n '' +\n '
              NameSizeCreated
              ';\n },\n\n /** Template of info message */\n _templateInfo: function() {\n return '
              ' +\n 'Your FTP directory does not contain any files.' +\n '
              ';\n },\n\n /** Template of main view */\n _template: function() {\n return '
              ' +\n '
              ' +\n '
              This Galaxy server allows you to upload files via FTP. To upload some files, log in to the FTP server at ' + this.options.ftp_upload_site + ' using your Galaxy credentials (email address and password).
              ' +\n '
              ' +\n '
              ';\n }\n });\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/upload/upload-ftp.js\n ** module id = 80\n ** module chunks = 3\n **/","/** This renders the content of the settings popup, allowing users to specify flags i.e. for space-to-tab conversion **/\ndefine( [ 'utils/utils' ], function( Utils ) {\n return Backbone.View.extend({\n options: {\n class_check : 'fa-check-square-o',\n class_uncheck : 'fa-square-o',\n parameters : [{\n id : 'space_to_tab',\n title : 'Convert spaces to tabs',\n },{\n id : 'to_posix_lines',\n title : 'Use POSIX standard'\n }]\n },\n\n initialize: function( options ) {\n var self = this;\n this.model = options.model;\n this.setElement( $( '
              ' ).addClass( 'upload-settings' ) );\n this.$el.append( $( '
              ' ).addClass( 'upload-settings-cover' ) );\n this.$el.append( $( '' ).addClass( 'upload-settings-table ui-table-striped' ).append( '' ) );\n this.$cover = this.$( '.upload-settings-cover' );\n this.$table = this.$( '.upload-settings-table > tbody' );\n this.listenTo ( this.model, 'change', this.render, this );\n this.model.trigger( 'change' );\n },\n\n render: function() {\n var self = this;\n this.$table.empty();\n _.each( this.options.parameters, function( parameter ) {\n var $checkbox = $( '
              ' ).addClass( 'upload-' + parameter.id + ' upload-icon-button fa' )\n .addClass( self.model.get( parameter.id ) && self.options.class_check || self.options.class_uncheck )\n .on( 'click', function() {\n self.model.get( 'enabled' ) && self.model.set( parameter.id, !self.model.get( parameter.id ) )\n });\n self.$table.append( $( '
              ' ).append( $( '' +\n '');\n\t wrapper.append($el);\n\t this.row.append(wrapper);\n\t },\n\t \n\t // header\n\t appendHeader: function() {\n\t // append header row\n\t this.$thead.append(this.row);\n\t\n\t // row\n\t this.row = $('');\n\t },\n\t \n\t // add row cell\n\t add: function($el, width, align) {\n\t var wrapper = $('');\n\t if (width) {\n\t wrapper.css('width', width);\n\t }\n\t if (align) {\n\t wrapper.css('text-align', align);\n\t }\n\t wrapper.append($el);\n\t this.row.append(wrapper);\n\t },\n\t \n\t // append\n\t append: function(id, fade) {\n\t this._commit(id, fade, false);\n\t },\n\t \n\t // prepend\n\t prepend: function(id, fade) {\n\t this._commit(id, fade, true);\n\t },\n\t \n\t // get element\n\t get: function(id) {\n\t return this.$el.find('#' + id);\n\t },\n\t \n\t // delete\n\t del: function(id) {\n\t var item = this.$tbody.find('#' + id);\n\t if (item.length > 0) {\n\t item.remove();\n\t this.row_count--;\n\t this._refresh();\n\t }\n\t },\n\t\n\t // delete all\n\t delAll: function() {\n\t this.$tbody.empty();\n\t this.row_count = 0;\n\t this._refresh();\n\t },\n\t \n\t // value\n\t value: function(new_value) {\n\t // get current id/value\n\t this.before = this.$tbody.find('.current').attr('id');\n\t \n\t // check if new_value is defined\n\t if (new_value !== undefined) {\n\t this.$tbody.find('tr').removeClass('current');\n\t if (new_value) {\n\t this.$tbody.find('#' + new_value).addClass('current');\n\t }\n\t }\n\t \n\t // get current id/value\n\t var after = this.$tbody.find('.current').attr('id');\n\t if(after === undefined) {\n\t return null;\n\t } else {\n\t // fire onchange\n\t if (after != this.before && this.options.onchange) {\n\t this.options.onchange(new_value);\n\t }\n\t \n\t // return current value\n\t return after;\n\t }\n\t },\n\t \n\t // size\n\t size: function() {\n\t return this.$tbody.find('tr').length;\n\t },\n\t \n\t // commit\n\t _commit: function(id, fade, prepend) {\n\t // remove previous item with same id\n\t this.del(id);\n\t \n\t // add\n\t this.row.attr('id', id);\n\t \n\t // add row\n\t if (prepend) {\n\t this.$tbody.prepend(this.row);\n\t } else {\n\t this.$tbody.append(this.row);\n\t }\n\t \n\t // fade mode\n\t if (fade) {\n\t this.row.hide();\n\t this.row.fadeIn();\n\t }\n\t \n\t // row\n\t this.row = this._row();\n\t \n\t // row count\n\t this.row_count++;\n\t this._refresh();\n\t },\n\t \n\t // create new row\n\t _row: function() {\n\t return $('');\n\t },\n\t \n\t // onclick\n\t _onclick: function(e) {\n\t // get values\n\t var old_value = this.value();\n\t var new_value = $(e.target).closest('tr').attr('id');\n\t if (new_value != ''){\n\t // check equality\n\t if (new_value && old_value != new_value) {\n\t if (this.options.onconfirm) {\n\t this.options.onconfirm(new_value);\n\t } else {\n\t this.value(new_value);\n\t }\n\t }\n\t }\n\t },\n\t\n\t // ondblclick\n\t _ondblclick: function(e) {\n\t var value = this.value();\n\t if (value && this.options.ondblclick) {\n\t this.options.ondblclick(value);\n\t }\n\t },\n\t \n\t // refresh\n\t _refresh: function() {\n\t if (this.row_count == 0) {\n\t this.$tmessage.show();\n\t } else {\n\t this.$tmessage.hide();\n\t }\n\t },\n\t \n\t // load html template\n\t _template: function(options) {\n\t return '
              ' +\n\t '
              ' ).append( $checkbox ) )\n .append( $( '' ).append( parameter.title ) ) )\n });\n this.$cover[ this.model.get( 'enabled' ) && 'hide' || 'show' ]();\n }\n });\n});\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/upload/upload-settings.js\n ** module id = 81\n ** module chunks = 3\n **/","(function (factory) {\n if (typeof define === 'function' && define.amd) {\n define([], factory);\n } else {\n // Browser globals\n factory(jQuery);\n }\n\n}(function () {\n//=============================================================================\n\n jQuery.fn.extend({\n hoverhighlight : function $hoverhighlight( scope, color ){\n scope = scope || 'body';\n if( !this.length ){ return this; }\n\n $( this ).each( function(){\n var $this = $( this ),\n targetSelector = $this.data( 'target' );\n\n if( targetSelector ){\n $this.mouseover( function( ev ){\n $( targetSelector, scope ).css({\n background: color\n });\n })\n .mouseout( function( ev ){\n $( targetSelector ).css({\n background: ''\n });\n });\n }\n });\n return this;\n }\n });\n}));\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/ui/hoverhighlight.js\n ** module id = 84\n ** module chunks = 3\n **/","// from: https://raw.githubusercontent.com/umdjs/umd/master/jqueryPlugin.js\n// Uses AMD or browser globals to create a jQuery plugin.\n(function (factory) {\n if (typeof define === 'function' && define.amd) {\n //TODO: So...this turns out to be an all or nothing thing. If I load jQuery in the define below, it will\n // (of course) wipe the old jquery *and all the plugins loaded into it*. So the define below *is still\n // relying on jquery being loaded globally* in order to preserve plugins.\n define([], factory);\n } else {\n // Browser globals\n factory(jQuery);\n }\n\n}(function () {\n var _l = window._l || function( s ){ return s; };\n\n //TODO: consolidate with tool menu functionality, use there\n\n /** searchInput: (jQuery plugin)\n * Creates a search input, a clear button, and loading indicator\n * within the selected node.\n *\n * When the user either presses return or enters some minimal number\n * of characters, a callback is called. Pressing ESC when the input\n * is focused will clear the input and call a separate callback.\n */\n function searchInput( parentNode, options ){\n var KEYCODE_ESC = 27,\n KEYCODE_RETURN = 13,\n $parentNode = $( parentNode ),\n firstSearch = true,\n defaults = {\n initialVal : '',\n name : 'search',\n placeholder : 'search',\n classes : '',\n onclear : function(){},\n onfirstsearch : null,\n onsearch : function( inputVal ){},\n minSearchLen : 0,\n escWillClear : true,\n oninit : function(){}\n };\n\n // .................................................................... input rendering and events\n // visually clear the search, trigger an event, and call the callback\n function clearSearchInput( event ){\n var $input = $( this ).parent().children( 'input' );\n $input.val( '' ).trigger( 'searchInput.clear' ).blur();\n options.onclear();\n }\n\n // search for searchTerms, trigger an event, call the appropo callback (based on whether this is the first)\n function search( event, searchTerms ){\n if( !searchTerms ){\n return clearSearchInput();\n }\n $( this ).trigger( 'search.search', searchTerms );\n if( typeof options.onfirstsearch === 'function' && firstSearch ){\n firstSearch = false;\n options.onfirstsearch( searchTerms );\n } else {\n options.onsearch( searchTerms );\n }\n }\n\n // .................................................................... input rendering and events\n function inputTemplate(){\n // class search-query is bootstrap 2.3 style that now lives in base.less\n return [ '' ].join( '' );\n }\n\n // the search input that responds to keyboard events and displays the search value\n function $input(){\n return $( inputTemplate() )\n // select all text on a focus\n .focus( function( event ){\n $( this ).select();\n })\n // attach behaviors to esc, return if desired, search on some min len string\n .keyup( function( event ){\n event.preventDefault();\n event.stopPropagation();\n\n // esc key will clear if desired\n if( event.which === KEYCODE_ESC && options.escWillClear ){\n clearSearchInput.call( this, event );\n\n } else {\n var searchTerms = $( this ).val();\n // return key or the search string len > minSearchLen (if not 0) triggers search\n if( ( event.which === KEYCODE_RETURN )\n || ( options.minSearchLen && searchTerms.length >= options.minSearchLen ) ){\n search.call( this, event, searchTerms );\n }\n }\n })\n .val( options.initialVal );\n }\n\n // .................................................................... clear button rendering and events\n // a button for clearing the search bar, placed on the right hand side\n function $clearBtn(){\n return $([ '' ].join('') )\n .tooltip({ placement: 'bottom' })\n .click( function( event ){\n clearSearchInput.call( this, event );\n });\n }\n\n // .................................................................... loadingIndicator rendering\n // a button for clearing the search bar, placed on the right hand side\n function $loadingIndicator(){\n return $([ '' ].join('') )\n .hide().tooltip({ placement: 'bottom' });\n }\n\n // .................................................................... commands\n // visually swap the load, clear buttons\n function toggleLoadingIndicator(){\n $parentNode.find( '.search-loading' ).toggle();\n $parentNode.find( '.search-clear' ).toggle();\n }\n\n // .................................................................... init\n // string command (not constructor)\n if( jQuery.type( options ) === 'string' ){\n if( options === 'toggle-loading' ){\n toggleLoadingIndicator();\n }\n return $parentNode;\n }\n\n // initial render\n if( jQuery.type( options ) === 'object' ){\n options = jQuery.extend( true, {}, defaults, options );\n }\n //NOTE: prepended\n return $parentNode.addClass( 'search-input' ).prepend([ $input(), $clearBtn(), $loadingIndicator() ]);\n }\n\n // as jq plugin\n jQuery.fn.extend({\n searchInput : function $searchInput( options ){\n return this.each( function(){\n return searchInput( this, options );\n });\n }\n });\n}));\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/ui/search-input.js\n ** module id = 85\n ** module chunks = 3\n **/","define([], function(){\n// Alphanumeric/natural sort fn\nfunction naturalSort(a, b) {\n // setup temp-scope variables for comparison evauluation\n var re = /(-?[0-9\\.]+)/g,\n x = a.toString().toLowerCase() || '',\n y = b.toString().toLowerCase() || '',\n nC = String.fromCharCode(0),\n xN = x.replace( re, nC + '$1' + nC ).split(nC),\n yN = y.replace( re, nC + '$1' + nC ).split(nC),\n xD = (new Date(x)).getTime(),\n yD = xD ? (new Date(y)).getTime() : null;\n // natural sorting of dates\n if ( yD ) {\n if ( xD < yD ) { return -1; }\n else if ( xD > yD ) { return 1; }\n }\n // natural sorting through split numeric strings and default strings\n var oFxNcL, oFyNcL;\n for ( var cLoc = 0, numS = Math.max(xN.length, yN.length); cLoc < numS; cLoc++ ) {\n oFxNcL = parseFloat(xN[cLoc]) || xN[cLoc];\n oFyNcL = parseFloat(yN[cLoc]) || yN[cLoc];\n if (oFxNcL < oFyNcL) { return -1; }\n else if (oFxNcL > oFyNcL) { return 1; }\n }\n return 0;\n}\n\nreturn naturalSort;\n})\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/utils/natural-sort.js\n ** module id = 87\n ** module chunks = 3\n **/","/*\n galaxy upload plugins - requires FormData and XMLHttpRequest\n*/\n;(function($){\n // add event properties\n jQuery.event.props.push(\"dataTransfer\");\n\n /**\n Posts file data to the API\n */\n $.uploadpost = function (config) {\n // parse options\n var cnf = $.extend({}, {\n data : {},\n success : function() {},\n error : function() {},\n progress : function() {},\n url : null,\n maxfilesize : 2048,\n error_filesize : 'File exceeds 2GB. Please use a FTP client.',\n error_default : 'Please make sure the file is available.',\n error_server : 'Upload request failed.',\n error_login : 'Uploads require you to log in.'\n }, config);\n\n // link data\n var data = cnf.data;\n\n // check errors\n if (data.error_message) {\n cnf.error(data.error_message);\n return;\n }\n\n // construct form data\n var form = new FormData();\n for (var key in data.payload) {\n form.append(key, data.payload[key]);\n }\n\n // add files to submission\n var sizes = 0;\n for (var key in data.files) {\n var d = data.files[key];\n form.append(d.name, d.file, d.file.name);\n sizes += d.file.size;\n }\n\n // check file size, unless it's an ftp file\n if (sizes > 1048576 * cnf.maxfilesize) {\n cnf.error(cnf.error_filesize);\n return;\n }\n\n // prepare request\n xhr = new XMLHttpRequest();\n xhr.open('POST', cnf.url, true);\n xhr.setRequestHeader('Accept', 'application/json');\n xhr.setRequestHeader('Cache-Control', 'no-cache');\n xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');\n\n // captures state changes\n xhr.onreadystatechange = function() {\n // check for request completed, server connection closed\n if (xhr.readyState == xhr.DONE) {\n // parse response\n var response = null;\n if (xhr.responseText) {\n try {\n response = jQuery.parseJSON(xhr.responseText);\n } catch (e) {\n response = xhr.responseText;\n }\n }\n // pass any error to the error option\n if (xhr.status < 200 || xhr.status > 299) {\n var text = xhr.statusText;\n if (xhr.status == 403) {\n text = cnf.error_login;\n } else if (xhr.status == 0) {\n text = cnf.error_server;\n } else if (!text) {\n text = cnf.error_default;\n }\n cnf.error(text + ' (' + xhr.status + ')');\n } else {\n cnf.success(response);\n }\n }\n }\n\n // prepare upload progress\n xhr.upload.addEventListener('progress', function(e) {\n if (e.lengthComputable) {\n cnf.progress(Math.round((e.loaded * 100) / e.total));\n }\n }, false);\n\n // send request\n Galaxy.emit.debug('uploadbox::uploadpost()', 'Posting following data.', cnf);\n xhr.send(form);\n }\n\n /**\n Handles the upload events drag/drop etc.\n */\n $.fn.uploadinput = function(options) {\n // initialize\n var el = this;\n var opts = $.extend({}, {\n ondragover : function() {},\n ondragleave : function() {},\n onchange : function() {},\n multiple : false\n }, options);\n\n // append hidden upload field\n var $input = $('');\n el.append($input.change(function (e) {\n opts.onchange(e.target.files);\n $(this).val('');\n }));\n\n // drag/drop events\n el.on('drop', function (e) {\n opts.ondragleave(e);\n if(e.dataTransfer) {\n opts.onchange(e.dataTransfer.files);\n e.preventDefault();\n }\n });\n el.on('dragover', function (e) {\n e.preventDefault();\n opts.ondragover(e);\n });\n el.on('dragleave', function (e) {\n e.stopPropagation();\n opts.ondragleave(e);\n });\n\n // exports\n return {\n dialog: function () {\n $input.trigger('click');\n }\n }\n }\n\n /**\n Handles the upload queue and events such as drag/drop etc.\n */\n $.fn.uploadbox = function(options) {\n // parse options\n var opts = $.extend({}, {\n dragover : function() {},\n dragleave : function() {},\n announce : function(d) {},\n initialize : function(d) {},\n progress : function(d, m) {},\n success : function(d, m) {},\n error : function(d, m) { alert(m); },\n complete : function() {}\n }, options);\n\n // file queue\n var queue = {};\n\n // queue index/length counter\n var queue_index = 0;\n var queue_length = 0;\n\n // indicates if queue is currently running\n var queue_running = false;\n var queue_stop = false;\n\n // element\n var uploadinput = $(this).uploadinput({\n multiple : true,\n onchange : function(files) { add(files); },\n ondragover : options.ondragover,\n ondragleave : options.ondragleave\n });\n\n // add new files to upload queue\n function add(files) {\n if (files && files.length && !queue_running) {\n var current_index = queue_index;\n _.each(files, function(file, key) {\n if (file.mode !== 'new' && _.filter(queue, function(f) {\n return f.name === file.name && f.size === file.size;\n }).length) {\n file.duplicate = true;\n }\n });\n _.each(files, function(file) {\n if (!file.duplicate) {\n var index = String(queue_index++);\n queue[index] = file;\n opts.announce(index, queue[index]);\n queue_length++;\n }\n });\n return current_index;\n }\n }\n\n // remove file from queue\n function remove(index) {\n if (queue[index]) {\n delete queue[index];\n queue_length--;\n }\n }\n\n // process an upload, recursive\n function process() {\n // validate\n if (queue_length == 0 || queue_stop) {\n queue_stop = false;\n queue_running = false;\n opts.complete();\n return;\n } else {\n queue_running = true;\n }\n\n // get an identifier from the queue\n var index = -1;\n for (var key in queue) {\n index = key;\n break;\n }\n\n // get current file from queue\n var file = queue[index];\n\n // remove from queue\n remove(index)\n\n // create and submit data\n $.uploadpost({\n url : opts.url,\n data : opts.initialize(index),\n success : function(message) { opts.success(index, message); process();},\n error : function(message) { opts.error(index, message); process();},\n progress : function(percentage) { opts.progress(index, percentage); }\n });\n }\n\n /*\n public interface\n */\n\n // open file browser for selection\n function select() {\n uploadinput.dialog();\n }\n\n // remove all entries from queue\n function reset(index) {\n for (index in queue) {\n remove(index);\n }\n }\n\n // initiate upload process\n function start() {\n if (!queue_running) {\n queue_running = true;\n process();\n }\n }\n\n // stop upload process\n function stop() {\n queue_stop = true;\n }\n\n // set options\n function configure(options) {\n opts = $.extend({}, opts, options);\n return opts;\n }\n\n // verify browser compatibility\n function compatible() {\n return window.File && window.FormData && window.XMLHttpRequest && window.FileList;\n }\n\n // export functions\n return {\n 'select' : select,\n 'add' : add,\n 'remove' : remove,\n 'start' : start,\n 'stop' : stop,\n 'reset' : reset,\n 'configure' : configure,\n 'compatible' : compatible\n };\n }\n})(jQuery);\n\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/utils/uploadbox.js\n ** module id = 88\n ** module chunks = 3\n **/","var RightPanel = require( 'layout/panel' ).RightPanel,\n Ui = require( 'mvc/ui/ui-misc' ),\n historyOptionsMenu = require( 'mvc/history/options-menu' );\n CurrentHistoryView = require( 'mvc/history/history-view-edit-current' ).CurrentHistoryView,\n _l = require( 'utils/localization' );\n\n/** the right hand panel in the analysis page that shows the current history */\nvar HistoryPanel = RightPanel.extend({\n\n title : _l( 'History' ),\n\n initialize : function( options ){\n RightPanel.prototype.initialize.call( this, options );\n this.options = _.pick( options, 'userIsAnonymous', 'allow_user_dataset_purge', 'galaxyRoot' );\n\n // view of the current history\n this.historyView = new CurrentHistoryView({\n className : CurrentHistoryView.prototype.className + ' middle',\n purgeAllowed : options.allow_user_dataset_purge,\n linkTarget : 'galaxy_main'\n });\n },\n\n /** override to change footer selector */\n $toggleButton : function(){\n return this.$( '.footer > .panel-collapse' );\n },\n\n render : function(){\n RightPanel.prototype.render.call( this );\n this.optionsMenu = historyOptionsMenu( this.$( '#history-options-button' ), {\n anonymous : this.options.userIsAnonymous,\n purgeAllowed : this.options.allow_user_dataset_purge,\n root : this.options.galaxyRoot\n });\n this.$( '> .header .buttons [title]' ).tooltip({ placement: 'bottom' });\n this.historyView.setElement( this.$( '.history-panel' ) );\n this.$el.attr( 'class', 'history-right-panel' );\n },\n\n /** override to add buttons */\n _templateHeader: function( data ){\n var historyUrl = this.options.galaxyRoot + 'history';\n var multiUrl = this.options.galaxyRoot + 'history/view_multiple';\n return [\n '
              ',\n '
              ',\n // this button re-fetches the history and contents and re-renders the history panel\n '',\n // opens a drop down menu with history related functions (like view all, delete, share, etc.)\n '',\n !this.options.userIsAnonymous?\n [ '' ].join('') : '',\n '
              ',\n '
              ', _.escape( this.title ), '
              ',\n '
              ',\n ].join('');\n },\n\n /** add history view div */\n _templateBody : function( data ){\n return [\n '
              ',\n ].join('');\n },\n\n /** override to use simplified selector */\n _templateFooter: function( data ){\n return [\n '
              ',\n '
              ',\n '
              ',\n '
              ',\n ].join('');\n },\n\n events : {\n 'click #history-refresh-button' : '_clickRefresh',\n // override to change footer selector\n 'mousedown .footer > .drag' : '_mousedownDragHandler',\n 'click .footer > .panel-collapse' : 'toggle'\n },\n\n _clickRefresh : function( ev ){\n ev.preventDefault();\n this.historyView.loadCurrentHistory();\n },\n\n toString : function(){ return 'HistoryPanel'; }\n});\n\nmodule.exports = HistoryPanel;\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/apps/history-panel.js\n ** module id = 89\n ** module chunks = 3\n **/","var LeftPanel = require( 'layout/panel' ).LeftPanel,\n Tools = require( 'mvc/tool/tools' ),\n Upload = require( 'mvc/upload/upload-view' ),\n _l = require( 'utils/localization' );\n\n/* Builds the tool menu panel on the left of the analysis page */\nvar ToolPanel = LeftPanel.extend({\n\n title : _l( 'Tools' ),\n\n initialize: function( options ){\n LeftPanel.prototype.initialize.call( this, options );\n this.log( this + '.initialize:', options );\n\n /** @type {Object[]} descriptions of user's workflows to be shown in the tool menu */\n this.stored_workflow_menu_entries = options.stored_workflow_menu_entries || [];\n\n // create tool search, tool panel, and tool panel view.\n var tool_search = new Tools.ToolSearch({\n search_url : options.search_url,\n hidden : false\n });\n var tools = new Tools.ToolCollection( options.toolbox );\n this.tool_panel = new Tools.ToolPanel({\n tool_search : tool_search,\n tools : tools,\n layout : options.toolbox_in_panel\n });\n this.tool_panel_view = new Tools.ToolPanelView({ model: this.tool_panel });\n\n // add upload modal\n this.uploadButton = new Upload({\n nginx_upload_path : options.nginx_upload_path,\n ftp_upload_site : options.ftp_upload_site,\n default_genome : options.default_genome,\n default_extension : options.default_extension,\n });\n },\n\n render : function(){\n var self = this;\n LeftPanel.prototype.render.call( self );\n self.$( '.panel-header-buttons' ).append( self.uploadButton.$el );\n\n // if there are tools, render panel and display everything\n if (self.tool_panel.get( 'layout' ).size() > 0) {\n self.tool_panel_view.render();\n //TODO: why the hide/show?\n self.$( '.toolMenu' ).show();\n }\n self.$( '.toolMenuContainer' ).prepend( self.tool_panel_view.$el );\n\n self._renderWorkflowMenu();\n\n // if a tool link has the minsizehint attribute, handle it here (gen. by hiding the tool panel)\n self.$( 'a[minsizehint]' ).click( function() {\n if ( parent.handle_minwidth_hint ) {\n parent.handle_minwidth_hint( $( self ).attr( 'minsizehint' ) );\n }\n });\n },\n\n /** build the dom for the workflow portion of the tool menu */\n _renderWorkflowMenu : function(){\n var self = this;\n // add internal workflow list\n self.$( '#internal-workflows' ).append( self._templateTool({\n title : _l( 'All workflows' ),\n href : 'workflow/list_for_run'\n }));\n _.each( self.stored_workflow_menu_entries, function( menu_entry ){\n self.$( '#internal-workflows' ).append( self._templateTool({\n title : menu_entry.stored_workflow.name,\n href : 'workflow/run?id=' + menu_entry.encoded_stored_workflow_id\n }));\n });\n },\n\n /** build a link to one tool */\n _templateTool: function( tool ) {\n return [\n '
              ',\n // global\n '', tool.title, '',\n '
              '\n ].join('');\n },\n\n /** override to include inital menu dom and workflow section */\n _templateBody : function(){\n return [\n '
              ',\n '
              ',\n '
              ',\n '
              ',\n '', _l( 'Search did not match any tools.' ), '',\n '
              ',\n '
              ',\n '
              ',\n '
              ',\n '
              ',\n '', _l( 'Workflows' ), '',\n '
              ',\n '
              ',\n '
              ',\n '
              ',\n '
              ',\n '
              '\n ].join('');\n },\n\n toString : function(){ return 'ToolPanel'; }\n});\n\nmodule.exports = ToolPanel;\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/apps/tool-panel.js\n ** module id = 90\n ** module chunks = 3\n **/","define([\n \"mvc/collection/collection-li\",\n \"mvc/dataset/dataset-li-edit\",\n \"mvc/base-mvc\",\n \"utils/localization\"\n], function( DC_LI, DATASET_LI_EDIT, BASE_MVC, _l ){\n\n'use strict';\n//==============================================================================\nvar DCListItemView = DC_LI.DCListItemView;\n/** @class Edit view for DatasetCollection.\n */\nvar DCListItemEdit = DCListItemView.extend(\n/** @lends DCListItemEdit.prototype */{\n\n /** override to add linkTarget */\n initialize : function( attributes ){\n DCListItemView.prototype.initialize.call( this, attributes );\n },\n\n // ......................................................................... misc\n /** String representation */\n toString : function(){\n var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n return 'DCListItemEdit(' + modelString + ')';\n }\n});\n\n\n//==============================================================================\nvar DCEListItemView = DC_LI.DCEListItemView;\n/** @class Read only view for DatasetCollectionElement.\n */\nvar DCEListItemEdit = DCEListItemView.extend(\n/** @lends DCEListItemEdit.prototype */{\n//TODO: this might be expendable - compacted with HDAListItemView\n\n /** set up */\n initialize : function( attributes ){\n DCEListItemView.prototype.initialize.call( this, attributes );\n },\n\n // ......................................................................... misc\n /** String representation */\n toString : function(){\n var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n return 'DCEListItemEdit(' + modelString + ')';\n }\n});\n\n\n//==============================================================================\n// NOTE: this does not inherit from DatasetDCEListItemView as you would expect\n//TODO: but should - if we can find something simpler than using diamond\n/** @class Editable view for a DatasetCollectionElement that is also an DatasetAssociation\n * (a dataset contained in a dataset collection).\n */\nvar DatasetDCEListItemEdit = DATASET_LI_EDIT.DatasetListItemEdit.extend(\n/** @lends DatasetDCEListItemEdit.prototype */{\n\n /** set up */\n initialize : function( attributes ){\n DATASET_LI_EDIT.DatasetListItemEdit.prototype.initialize.call( this, attributes );\n },\n\n // NOTE: this does not inherit from DatasetDCEListItemView - so we duplicate this here\n //TODO: fix\n /** In this override, only get details if in the ready state.\n * Note: fetch with no 'change' event triggering to prevent automatic rendering.\n */\n _fetchModelDetails : function(){\n var view = this;\n if( view.model.inReadyState() && !view.model.hasDetails() ){\n return view.model.fetch({ silent: true });\n }\n return jQuery.when();\n },\n\n /** Override to remove delete button */\n _renderDeleteButton : function(){\n return null;\n },\n\n // ......................................................................... misc\n /** String representation */\n toString : function(){\n var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n return 'DatasetDCEListItemEdit(' + modelString + ')';\n }\n});\n\n// ............................................................................ TEMPLATES\n/** underscore templates */\nDatasetDCEListItemEdit.prototype.templates = (function(){\n\n return _.extend( {}, DATASET_LI_EDIT.DatasetListItemEdit.prototype.templates, {\n titleBar : DC_LI.DatasetDCEListItemView.prototype.templates.titleBar\n });\n}());\n\n\n//==============================================================================\n/** @class Read only view for a DatasetCollectionElement that is also a DatasetCollection\n * (a nested DC).\n */\nvar NestedDCDCEListItemEdit = DC_LI.NestedDCDCEListItemView.extend(\n/** @lends NestedDCDCEListItemEdit.prototype */{\n\n /** String representation */\n toString : function(){\n var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n return 'NestedDCDCEListItemEdit(' + modelString + ')';\n }\n});\n\n\n//==============================================================================\n return {\n DCListItemEdit : DCListItemEdit,\n DCEListItemEdit : DCEListItemEdit,\n DatasetDCEListItemEdit : DatasetDCEListItemEdit,\n NestedDCDCEListItemEdit : NestedDCDCEListItemEdit\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/collection/collection-li-edit.js\n ** module id = 106\n ** module chunks = 3\n **/","define([\n \"mvc/collection/collection-view\",\n \"mvc/collection/collection-model\",\n \"mvc/collection/collection-li-edit\",\n \"mvc/base-mvc\",\n \"utils/localization\",\n \"ui/editable-text\",\n], function( DC_VIEW, DC_MODEL, DC_EDIT, BASE_MVC, _l ){\n\n'use strict';\n/* =============================================================================\nTODO:\n\n============================================================================= */\n/** @class editable View/Controller for a dataset collection.\n */\nvar _super = DC_VIEW.CollectionView;\nvar CollectionViewEdit = _super.extend(\n/** @lends CollectionView.prototype */{\n //MODEL is either a DatasetCollection (or subclass) or a DatasetCollectionElement (list of pairs)\n\n /** logger used to record this.log messages, commonly set to console */\n //logger : console,\n\n /** sub view class used for datasets */\n DatasetDCEViewClass : DC_EDIT.DatasetDCEListItemEdit,\n /** sub view class used for nested collections */\n NestedDCDCEViewClass: DC_EDIT.NestedDCDCEListItemEdit,\n\n // ......................................................................... SET UP\n /** Set up the view, set up storage, bind listeners to HistoryContents events\n * @param {Object} attributes optional settings for the panel\n */\n initialize : function( attributes ){\n _super.prototype.initialize.call( this, attributes );\n },\n\n /** In this override, make the collection name editable\n */\n _setUpBehaviors : function( $where ){\n $where = $where || this.$el;\n _super.prototype._setUpBehaviors.call( this, $where );\n if( !this.model ){ return; }\n\n // anon users shouldn't have access to any of the following\n if( !Galaxy.user || Galaxy.user.isAnonymous() ){\n return;\n }\n\n //TODO: extract\n var panel = this,\n nameSelector = '> .controls .name';\n $where.find( nameSelector )\n .attr( 'title', _l( 'Click to rename collection' ) )\n .tooltip({ placement: 'bottom' })\n .make_text_editable({\n on_finish: function( newName ){\n var previousName = panel.model.get( 'name' );\n if( newName && newName !== previousName ){\n panel.$el.find( nameSelector ).text( newName );\n panel.model.save({ name: newName })\n .fail( function(){\n panel.$el.find( nameSelector ).text( panel.model.previous( 'name' ) );\n });\n } else {\n panel.$el.find( nameSelector ).text( previousName );\n }\n }\n });\n },\n\n // ........................................................................ misc\n /** string rep */\n toString : function(){\n return 'CollectionViewEdit(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';\n }\n});\n\n\n// =============================================================================\n/** @class non-editable, read-only View/Controller for a dataset collection. */\nvar ListCollectionViewEdit = CollectionViewEdit.extend(\n/** @lends ListCollectionView.prototype */{\n\n //TODO: not strictly needed - due to switch in CollectionView._getContentClass\n /** sub view class used for datasets */\n DatasetDCEViewClass : DC_EDIT.DatasetDCEListItemEdit,\n\n // ........................................................................ misc\n /** string rep */\n toString : function(){\n return 'ListCollectionViewEdit(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';\n }\n});\n\n\n// =============================================================================\n/** @class Editable, read-only View/Controller for a dataset collection. */\nvar PairCollectionViewEdit = ListCollectionViewEdit.extend(\n/** @lends PairCollectionViewEdit.prototype */{\n\n // ........................................................................ misc\n /** string rep */\n toString : function(){\n return 'PairCollectionViewEdit(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';\n }\n});\n\n\n// =============================================================================\n/** @class Editable (roughly since these collections are immutable),\n * View/Controller for a dataset collection.\n */\nvar NestedPairCollectionViewEdit = PairCollectionViewEdit.extend(\n/** @lends NestedPairCollectionViewEdit.prototype */{\n\n /** Override to remove the editable text from the name/identifier - these collections are considered immutable */\n _setUpBehaviors : function( $where ){\n _super.prototype._setUpBehaviors.call( this, $where );\n },\n\n // ........................................................................ misc\n /** string rep */\n toString : function(){\n return 'NestedPairCollectionViewEdit(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';\n }\n});\n\n\n// =============================================================================\n/** @class editable, View/Controller for a list of pairs dataset collection. */\nvar ListOfPairsCollectionViewEdit = CollectionViewEdit.extend(\n/** @lends ListOfPairsCollectionView.prototype */{\n\n //TODO: not strictly needed - due to switch in CollectionView._getContentClass\n /** sub view class used for nested collections */\n NestedDCDCEViewClass : DC_EDIT.NestedDCDCEListItemEdit.extend({\n foldoutPanelClass : NestedPairCollectionViewEdit\n }),\n\n // ........................................................................ misc\n /** string rep */\n toString : function(){\n return 'ListOfPairsCollectionViewEdit(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';\n }\n});\n\n\n// =============================================================================\n/** @class View/Controller for a list of lists dataset collection. */\nvar ListOfListsCollectionViewEdit = CollectionViewEdit.extend(\n/** @lends ListOfListsCollectionView.prototype */{\n\n //TODO: not strictly needed - due to switch in CollectionView._getContentClass\n /** sub view class used for nested collections */\n NestedDCDCEViewClass : DC_EDIT.NestedDCDCEListItemEdit.extend({\n foldoutPanelClass : NestedPairCollectionViewEdit\n }),\n\n // ........................................................................ misc\n /** string rep */\n toString : function(){\n return 'ListOfListsCollectionViewEdit(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';\n }\n});\n\n\n//==============================================================================\n return {\n CollectionViewEdit : CollectionViewEdit,\n ListCollectionViewEdit : ListCollectionViewEdit,\n PairCollectionViewEdit : PairCollectionViewEdit,\n ListOfPairsCollectionViewEdit : ListOfPairsCollectionViewEdit,\n ListOfListsCollectionViewEdit : ListOfListsCollectionViewEdit\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/collection/collection-view-edit.js\n ** module id = 107\n ** module chunks = 3\n **/","define([\n \"utils/levenshtein\",\n \"utils/natural-sort\",\n \"mvc/collection/list-collection-creator\",\n \"mvc/base-mvc\",\n \"utils/localization\",\n \"ui/hoverhighlight\"\n], function( levenshteinDistance, naturalSort, LIST_COLLECTION_CREATOR, baseMVC, _l ){\n\n'use strict';\n\nvar logNamespace = 'collections';\n/* ============================================================================\nTODO:\n\n\nPROGRAMMATICALLY:\ncurrPanel.once( 'rendered', function(){\n currPanel.showSelectors();\n currPanel.selectAll();\n _.last( currPanel.actionsPopup.options ).func();\n});\n\n============================================================================ */\n/** A view for paired datasets in the collections creator.\n */\nvar PairView = Backbone.View.extend( baseMVC.LoggableMixin ).extend({\n _logNamespace : logNamespace,\n\n tagName : 'li',\n className : 'dataset paired',\n\n initialize : function( attributes ){\n this.pair = attributes.pair || {};\n },\n\n template : _.template([\n '<%- pair.forward.name %>',\n '',\n '<%- pair.name %>',\n '',\n '<%- pair.reverse.name %>'\n ].join('')),\n\n render : function(){\n this.$el\n .attr( 'draggable', true )\n .data( 'pair', this.pair )\n .html( this.template({ pair: this.pair }) )\n .addClass( 'flex-column-container' );\n return this;\n },\n\n events : {\n 'dragstart' : '_dragstart',\n 'dragend' : '_dragend',\n 'dragover' : '_sendToParent',\n 'drop' : '_sendToParent'\n },\n\n /** dragging pairs for re-ordering */\n _dragstart : function( ev ){\n ev.currentTarget.style.opacity = '0.4';\n if( ev.originalEvent ){ ev = ev.originalEvent; }\n\n ev.dataTransfer.effectAllowed = 'move';\n ev.dataTransfer.setData( 'text/plain', JSON.stringify( this.pair ) );\n\n this.$el.parent().trigger( 'pair.dragstart', [ this ] );\n },\n\n /** dragging pairs for re-ordering */\n _dragend : function( ev ){\n ev.currentTarget.style.opacity = '1.0';\n this.$el.parent().trigger( 'pair.dragend', [ this ] );\n },\n\n /** manually bubble up an event to the parent/container */\n _sendToParent : function( ev ){\n this.$el.parent().trigger( ev );\n },\n\n /** string rep */\n toString : function(){\n return 'PairView(' + this.pair.name + ')';\n }\n});\n\n\n// ============================================================================\n/** returns an autopair function that uses the provided options.match function */\nfunction autoPairFnBuilder( options ){\n options = options || {};\n options.createPair = options.createPair || function _defaultCreatePair( params ){\n params = params || {};\n var a = params.listA.splice( params.indexA, 1 )[0],\n b = params.listB.splice( params.indexB, 1 )[0],\n aInBIndex = params.listB.indexOf( a ),\n bInAIndex = params.listA.indexOf( b );\n if( aInBIndex !== -1 ){ params.listB.splice( aInBIndex, 1 ); }\n if( bInAIndex !== -1 ){ params.listA.splice( bInAIndex, 1 ); }\n return this._pair( a, b, { silent: true });\n };\n // compile these here outside of the loop\n var _regexps = [];\n function getRegExps(){\n if( !_regexps.length ){\n _regexps = [\n new RegExp( this.filters[0] ),\n new RegExp( this.filters[1] )\n ];\n }\n return _regexps;\n }\n // mangle params as needed\n options.preprocessMatch = options.preprocessMatch || function _defaultPreprocessMatch( params ){\n var regexps = getRegExps.call( this );\n return _.extend( params, {\n matchTo : params.matchTo.name.replace( regexps[0], '' ),\n possible : params.possible.name.replace( regexps[1], '' )\n });\n };\n\n return function _strategy( params ){\n this.debug( 'autopair _strategy ---------------------------' );\n params = params || {};\n var listA = params.listA,\n listB = params.listB,\n indexA = 0, indexB,\n bestMatch = {\n score : 0.0,\n index : null\n },\n paired = [];\n //console.debug( 'params:', JSON.stringify( params, null, ' ' ) );\n this.debug( 'starting list lens:', listA.length, listB.length );\n this.debug( 'bestMatch (starting):', JSON.stringify( bestMatch, null, ' ' ) );\n\n while( indexA < listA.length ){\n var matchTo = listA[ indexA ];\n bestMatch.score = 0.0;\n\n for( indexB=0; indexB= scoreThreshold ){\n //console.debug( 'autoPairFnBuilder.strategy', listA[ indexA ].name, listB[ bestMatch.index ].name );\n paired.push( options.createPair.call( this, {\n listA : listA,\n indexA : indexA,\n listB : listB,\n indexB : bestMatch.index\n }));\n //console.debug( 'list lens now:', listA.length, listB.length );\n } else {\n indexA += 1;\n }\n if( !listA.length || !listB.length ){\n return paired;\n }\n }\n this.debug( 'paired:', JSON.stringify( paired, null, ' ' ) );\n this.debug( 'autopair _strategy ---------------------------' );\n return paired;\n };\n}\n\n\n// ============================================================================\n/** An interface for building collections of paired datasets.\n */\nvar PairedCollectionCreator = Backbone.View.extend( baseMVC.LoggableMixin ).extend({\n _logNamespace : logNamespace,\n\n className: 'list-of-pairs-collection-creator collection-creator flex-row-container',\n\n /** set up initial options, instance vars, behaviors, and autopair (if set to do so) */\n initialize : function( attributes ){\n this.metric( 'PairedCollectionCreator.initialize', attributes );\n //this.debug( '-- PairedCollectionCreator:', attributes );\n\n attributes = _.defaults( attributes, {\n datasets : [],\n filters : this.DEFAULT_FILTERS,\n automaticallyPair : true,\n strategy : 'lcs',\n matchPercentage : 0.9,\n twoPassAutopairing : true\n });\n\n /** unordered, original list */\n this.initialList = attributes.datasets;\n\n /** is this from a history? if so, what's its id? */\n this.historyId = attributes.historyId;\n\n /** which filters should be used initially? (String[2] or name in commonFilters) */\n this.filters = this.commonFilters[ attributes.filters ] || this.commonFilters[ this.DEFAULT_FILTERS ];\n if( _.isArray( attributes.filters ) ){\n this.filters = attributes.filters;\n }\n\n /** try to auto pair the unpaired datasets on load? */\n this.automaticallyPair = attributes.automaticallyPair;\n\n /** what method to use for auto pairing (will be passed aggression level) */\n this.strategy = this.strategies[ attributes.strategy ] || this.strategies[ this.DEFAULT_STRATEGY ];\n if( _.isFunction( attributes.strategy ) ){\n this.strategy = attributes.strategy;\n }\n\n /** distance/mismatch level allowed for autopairing */\n this.matchPercentage = attributes.matchPercentage;\n\n /** try to autopair using simple first, then this.strategy on the remainder */\n this.twoPassAutopairing = attributes.twoPassAutopairing;\n\n /** remove file extensions (\\.*) from created pair names? */\n this.removeExtensions = true;\n //this.removeExtensions = false;\n\n /** fn to call when the cancel button is clicked (scoped to this) - if falsy, no btn is displayed */\n this.oncancel = attributes.oncancel;\n /** fn to call when the collection is created (scoped to this) */\n this.oncreate = attributes.oncreate;\n\n /** fn to call when the cancel button is clicked (scoped to this) - if falsy, no btn is displayed */\n this.autoscrollDist = attributes.autoscrollDist || 24;\n\n /** is the unpaired panel shown? */\n this.unpairedPanelHidden = false;\n /** is the paired panel shown? */\n this.pairedPanelHidden = false;\n\n /** DOM elements currently being dragged */\n this.$dragging = null;\n\n /** Used for blocking UI events during ajax/operations (don't post twice) */\n this.blocking = false;\n\n this._setUpBehaviors();\n this._dataSetUp();\n },\n\n /** map of common filter pairs by name */\n commonFilters : {\n illumina : [ '_1', '_2' ],\n Rs : [ '_R1', '_R2' ]\n },\n /** which commonFilter to use by default */\n DEFAULT_FILTERS : 'illumina',\n\n /** map of name->fn for autopairing */\n strategies : {\n 'simple' : 'autopairSimple',\n 'lcs' : 'autopairLCS',\n 'levenshtein' : 'autopairLevenshtein'\n },\n /** default autopair strategy name */\n DEFAULT_STRATEGY : 'lcs',\n\n // ------------------------------------------------------------------------ process raw list\n /** set up main data: cache initialList, sort, and autopair */\n _dataSetUp : function(){\n //this.debug( '-- _dataSetUp' );\n\n this.paired = [];\n this.unpaired = [];\n\n this.selectedIds = [];\n\n // sort initial list, add ids if needed, and save new working copy to unpaired\n this._sortInitialList();\n this._ensureIds();\n this.unpaired = this.initialList.slice( 0 );\n\n if( this.automaticallyPair ){\n this.autoPair();\n this.once( 'rendered:initial', function(){\n this.trigger( 'autopair' );\n });\n }\n },\n\n /** sort initial list */\n _sortInitialList : function(){\n //this.debug( '-- _sortInitialList' );\n this._sortDatasetList( this.initialList );\n },\n\n /** sort a list of datasets */\n _sortDatasetList : function( list ){\n // currently only natural sort by name\n list.sort( function( a, b ){ return naturalSort( a.name, b.name ); });\n return list;\n },\n\n /** add ids to dataset objs in initial list if none */\n _ensureIds : function(){\n this.initialList.forEach( function( dataset ){\n if( !dataset.hasOwnProperty( 'id' ) ){\n dataset.id = _.uniqueId();\n }\n });\n return this.initialList;\n },\n\n /** split initial list into two lists, those that pass forward filters & those passing reverse */\n _splitByFilters : function(){\n var regexFilters = this.filters.map( function( stringFilter ){\n return new RegExp( stringFilter );\n }),\n split = [ [], [] ];\n\n function _filter( unpaired, filter ){\n return filter.test( unpaired.name );\n //return dataset.name.indexOf( filter ) >= 0;\n }\n this.unpaired.forEach( function _filterEach( unpaired ){\n // 90% of the time this seems to work, but:\n //TODO: this treats *all* strings as regex which may confuse people - possibly check for // surrounding?\n // would need explanation in help as well\n regexFilters.forEach( function( filter, i ){\n if( _filter( unpaired, filter ) ){\n split[i].push( unpaired );\n }\n });\n });\n return split;\n },\n\n /** add a dataset to the unpaired list in it's proper order */\n _addToUnpaired : function( dataset ){\n // currently, unpaired is natural sorted by name, use binary search to find insertion point\n var binSearchSortedIndex = function( low, hi ){\n if( low === hi ){ return low; }\n\n var mid = Math.floor( ( hi - low ) / 2 ) + low,\n compared = naturalSort( dataset.name, this.unpaired[ mid ].name );\n\n if( compared < 0 ){\n return binSearchSortedIndex( low, mid );\n } else if( compared > 0 ){\n return binSearchSortedIndex( mid + 1, hi );\n }\n // walk the equal to find the last\n while( this.unpaired[ mid ] && this.unpaired[ mid ].name === dataset.name ){ mid++; }\n return mid;\n\n }.bind( this );\n\n this.unpaired.splice( binSearchSortedIndex( 0, this.unpaired.length ), 0, dataset );\n },\n\n // ------------------------------------------------------------------------ auto pairing\n /** two passes to automatically create pairs:\n * use both simpleAutoPair, then the fn mentioned in strategy\n */\n autoPair : function( strategy ){\n // split first using exact matching\n var split = this._splitByFilters(),\n paired = [];\n if( this.twoPassAutopairing ){\n paired = this.autopairSimple({\n listA : split[0],\n listB : split[1]\n });\n split = this._splitByFilters();\n }\n\n // uncomment to see printlns while running tests\n //this.debug = function(){ console.log.apply( console, arguments ); };\n\n // then try the remainder with something less strict\n strategy = strategy || this.strategy;\n split = this._splitByFilters();\n paired = paired.concat( this[ strategy ].call( this, {\n listA : split[0],\n listB : split[1]\n }));\n return paired;\n },\n\n /** autopair by exact match */\n autopairSimple : autoPairFnBuilder({\n scoreThreshold: function(){ return 1.0; },\n match : function _match( params ){\n params = params || {};\n if( params.matchTo === params.possible ){\n return {\n index: params.index,\n score: 1.0\n };\n }\n return params.bestMatch;\n }\n }),\n\n /** autopair by levenshtein edit distance scoring */\n autopairLevenshtein : autoPairFnBuilder({\n scoreThreshold: function(){ return this.matchPercentage; },\n match : function _matches( params ){\n params = params || {};\n var distance = levenshteinDistance( params.matchTo, params.possible ),\n score = 1.0 - ( distance / ( Math.max( params.matchTo.length, params.possible.length ) ) );\n if( score > params.bestMatch.score ){\n return {\n index: params.index,\n score: score\n };\n }\n return params.bestMatch;\n }\n }),\n\n /** autopair by longest common substrings scoring */\n autopairLCS : autoPairFnBuilder({\n scoreThreshold: function(){ return this.matchPercentage; },\n match : function _matches( params ){\n params = params || {};\n var match = this._naiveStartingAndEndingLCS( params.matchTo, params.possible ).length,\n score = match / ( Math.max( params.matchTo.length, params.possible.length ) );\n if( score > params.bestMatch.score ){\n return {\n index: params.index,\n score: score\n };\n }\n return params.bestMatch;\n }\n }),\n\n /** return the concat'd longest common prefix and suffix from two strings */\n _naiveStartingAndEndingLCS : function( s1, s2 ){\n var fwdLCS = '',\n revLCS = '',\n i = 0, j = 0;\n while( i < s1.length && i < s2.length ){\n if( s1[ i ] !== s2[ i ] ){\n break;\n }\n fwdLCS += s1[ i ];\n i += 1;\n }\n if( i === s1.length ){ return s1; }\n if( i === s2.length ){ return s2; }\n\n i = ( s1.length - 1 );\n j = ( s2.length - 1 );\n while( i >= 0 && j >= 0 ){\n if( s1[ i ] !== s2[ j ] ){\n break;\n }\n revLCS = [ s1[ i ], revLCS ].join( '' );\n i -= 1;\n j -= 1;\n }\n return fwdLCS + revLCS;\n },\n\n // ------------------------------------------------------------------------ pairing / unpairing\n /** create a pair from fwd and rev, removing them from unpaired, and placing the new pair in paired */\n _pair : function( fwd, rev, options ){\n options = options || {};\n this.debug( '_pair:', fwd, rev );\n var pair = this._createPair( fwd, rev, options.name );\n this.paired.push( pair );\n this.unpaired = _.without( this.unpaired, fwd, rev );\n if( !options.silent ){\n this.trigger( 'pair:new', pair );\n }\n return pair;\n },\n\n /** create a pair Object from fwd and rev, adding the name attribute (will guess if not given) */\n _createPair : function( fwd, rev, name ){\n // ensure existance and don't pair something with itself\n if( !( fwd && rev ) || ( fwd === rev ) ){\n throw new Error( 'Bad pairing: ' + [ JSON.stringify( fwd ), JSON.stringify( rev ) ] );\n }\n name = name || this._guessNameForPair( fwd, rev );\n return { forward : fwd, name : name, reverse : rev };\n },\n\n /** try to find a good pair name for the given fwd and rev datasets */\n _guessNameForPair : function( fwd, rev, removeExtensions ){\n removeExtensions = ( removeExtensions !== undefined )?( removeExtensions ):( this.removeExtensions );\n var fwdName = fwd.name,\n revName = rev.name,\n lcs = this._naiveStartingAndEndingLCS(\n fwdName.replace( new RegExp( this.filters[0] ), '' ),\n revName.replace( new RegExp( this.filters[1] ), '' )\n );\n if( removeExtensions ){\n var lastDotIndex = lcs.lastIndexOf( '.' );\n if( lastDotIndex > 0 ){\n var extension = lcs.slice( lastDotIndex, lcs.length );\n lcs = lcs.replace( extension, '' );\n fwdName = fwdName.replace( extension, '' );\n revName = revName.replace( extension, '' );\n }\n }\n return lcs || ( fwdName + ' & ' + revName );\n },\n\n /** unpair a pair, removing it from paired, and adding the fwd,rev datasets back into unpaired */\n _unpair : function( pair, options ){\n options = options || {};\n if( !pair ){\n throw new Error( 'Bad pair: ' + JSON.stringify( pair ) );\n }\n this.paired = _.without( this.paired, pair );\n this._addToUnpaired( pair.forward );\n this._addToUnpaired( pair.reverse );\n\n if( !options.silent ){\n this.trigger( 'pair:unpair', [ pair ] );\n }\n return pair;\n },\n\n /** unpair all paired datasets */\n unpairAll : function(){\n var pairs = [];\n while( this.paired.length ){\n pairs.push( this._unpair( this.paired[ 0 ], { silent: true }) );\n }\n this.trigger( 'pair:unpair', pairs );\n },\n\n // ------------------------------------------------------------------------ API\n /** convert a pair into JSON compatible with the collections API */\n _pairToJSON : function( pair, src ){\n src = src || 'hda';\n //TODO: consider making this the pair structure when created instead\n return {\n collection_type : 'paired',\n src : 'new_collection',\n name : pair.name,\n element_identifiers : [{\n name : 'forward',\n id : pair.forward.id,\n src : src\n }, {\n name : 'reverse',\n id : pair.reverse.id,\n src : src\n }]\n };\n },\n\n /** create the collection via the API\n * @returns {jQuery.xhr Object} the jquery ajax request\n */\n createList : function( name ){\n var creator = this,\n url = Galaxy.root + 'api/histories/' + this.historyId + '/contents/dataset_collections';\n\n //TODO: use ListPairedCollection.create()\n var ajaxData = {\n type : 'dataset_collection',\n collection_type : 'list:paired',\n name : _.escape( name || creator.$( '.collection-name' ).val() ),\n element_identifiers : creator.paired.map( function( pair ){\n return creator._pairToJSON( pair );\n })\n\n };\n //this.debug( JSON.stringify( ajaxData ) );\n creator.blocking = true;\n return jQuery.ajax( url, {\n type : 'POST',\n contentType : 'application/json',\n dataType : 'json',\n data : JSON.stringify( ajaxData )\n })\n .always( function(){\n creator.blocking = false;\n })\n .fail( function( xhr, status, message ){\n creator._ajaxErrHandler( xhr, status, message );\n })\n .done( function( response, message, xhr ){\n //this.info( 'ok', response, message, xhr );\n creator.trigger( 'collection:created', response, message, xhr );\n creator.metric( 'collection:created', response );\n if( typeof creator.oncreate === 'function' ){\n creator.oncreate.call( this, response, message, xhr );\n }\n });\n },\n\n /** handle ajax errors with feedback and details to the user (if available) */\n _ajaxErrHandler : function( xhr, status, message ){\n this.error( xhr, status, message );\n var content = _l( 'An error occurred while creating this collection' );\n if( xhr ){\n if( xhr.readyState === 0 && xhr.status === 0 ){\n content += ': ' + _l( 'Galaxy could not be reached and may be updating.' )\n + _l( ' Try again in a few minutes.' );\n } else if( xhr.responseJSON ){\n content += '
              ' + JSON.stringify( xhr.responseJSON ) + '
              ';\n } else {\n content += ': ' + message;\n }\n }\n creator._showAlert( content, 'alert-danger' );\n },\n\n // ------------------------------------------------------------------------ rendering\n /** render the entire interface */\n render : function( speed, callback ){\n //this.debug( '-- _render' );\n //this.$el.empty().html( PairedCollectionCreator.templates.main() );\n this.$el.empty().html( PairedCollectionCreator.templates.main() );\n this._renderHeader( speed );\n this._renderMiddle( speed );\n this._renderFooter( speed );\n this._addPluginComponents();\n this.trigger( 'rendered', this );\n return this;\n },\n\n /** render the header section */\n _renderHeader : function( speed, callback ){\n //this.debug( '-- _renderHeader' );\n var $header = this.$( '.header' ).empty().html( PairedCollectionCreator.templates.header() )\n .find( '.help-content' ).prepend( $( PairedCollectionCreator.templates.helpContent() ) );\n\n this._renderFilters();\n return $header;\n },\n /** fill the filter inputs with the filter values */\n _renderFilters : function(){\n return this.$( '.forward-column .column-header input' ).val( this.filters[0] )\n .add( this.$( '.reverse-column .column-header input' ).val( this.filters[1] ) );\n },\n\n /** render the middle including unpaired and paired sections (which may be hidden) */\n _renderMiddle : function( speed, callback ){\n var $middle = this.$( '.middle' ).empty().html( PairedCollectionCreator.templates.middle() );\n\n // (re-) hide the un/paired panels based on instance vars\n if( this.unpairedPanelHidden ){\n this.$( '.unpaired-columns' ).hide();\n } else if( this.pairedPanelHidden ){\n this.$( '.paired-columns' ).hide();\n }\n\n this._renderUnpaired();\n this._renderPaired();\n return $middle;\n },\n /** render the unpaired section, showing datasets accrd. to filters, update the unpaired counts */\n _renderUnpaired : function( speed, callback ){\n //this.debug( '-- _renderUnpaired' );\n var creator = this,\n $fwd, $rev, $prd = [],\n split = this._splitByFilters();\n // update unpaired counts\n this.$( '.forward-column .title' )\n .text([ split[0].length, _l( 'unpaired forward' ) ].join( ' ' ));\n this.$( '.forward-column .unpaired-info' )\n .text( this._renderUnpairedDisplayStr( this.unpaired.length - split[0].length ) );\n this.$( '.reverse-column .title' )\n .text([ split[1].length, _l( 'unpaired reverse' ) ].join( ' ' ));\n this.$( '.reverse-column .unpaired-info' )\n .text( this._renderUnpairedDisplayStr( this.unpaired.length - split[1].length ) );\n\n this.$( '.unpaired-columns .column-datasets' ).empty();\n\n // show/hide the auto pair button if any unpaired are left\n this.$( '.autopair-link' ).toggle( this.unpaired.length !== 0 );\n if( this.unpaired.length === 0 ){\n this._renderUnpairedEmpty();\n return;\n }\n\n // create the dataset dom arrays\n $rev = split[1].map( function( dataset, i ){\n // if there'll be a fwd dataset across the way, add a button to pair the row\n if( ( split[0][ i ] !== undefined )\n && ( split[0][ i ] !== dataset ) ){\n $prd.push( creator._renderPairButton() );\n }\n return creator._renderUnpairedDataset( dataset );\n });\n $fwd = split[0].map( function( dataset ){\n return creator._renderUnpairedDataset( dataset );\n });\n\n if( !$fwd.length && !$rev.length ){\n this._renderUnpairedNotShown();\n return;\n }\n // add to appropo cols\n //TODO: not the best way to render - consider rendering the entire unpaired-columns section in a fragment\n // and swapping out that\n this.$( '.unpaired-columns .forward-column .column-datasets' ).append( $fwd )\n .add( this.$( '.unpaired-columns .paired-column .column-datasets' ).append( $prd ) )\n .add( this.$( '.unpaired-columns .reverse-column .column-datasets' ).append( $rev ) );\n this._adjUnpairedOnScrollbar();\n },\n /** return a string to display the count of filtered out datasets */\n _renderUnpairedDisplayStr : function( numFiltered ){\n return [ '(', numFiltered, ' ', _l( 'filtered out' ), ')' ].join('');\n },\n /** return an unattached jQuery DOM element to represent an unpaired dataset */\n _renderUnpairedDataset : function( dataset ){\n //TODO: to underscore template\n return $( '
            • ')\n .attr( 'id', 'dataset-' + dataset.id )\n .addClass( 'dataset unpaired' )\n .attr( 'draggable', true )\n .addClass( dataset.selected? 'selected': '' )\n .append( $( '' ).addClass( 'dataset-name' ).text( dataset.name ) )\n //??\n .data( 'dataset', dataset );\n },\n /** render the button that may go between unpaired datasets, allowing the user to pair a row */\n _renderPairButton : function(){\n //TODO: *not* a dataset - don't pretend like it is\n return $( '
            • ').addClass( 'dataset unpaired' )\n .append( $( '' ).addClass( 'dataset-name' ).text( _l( 'Pair these datasets' ) ) );\n },\n /** a message to display when no unpaired left */\n _renderUnpairedEmpty : function(){\n //this.debug( '-- renderUnpairedEmpty' );\n var $msg = $( '
              ' )\n .text( '(' + _l( 'no remaining unpaired datasets' ) + ')' );\n this.$( '.unpaired-columns .paired-column .column-datasets' ).empty().prepend( $msg );\n return $msg;\n },\n /** a message to display when no unpaired can be shown with the current filters */\n _renderUnpairedNotShown : function(){\n //this.debug( '-- renderUnpairedEmpty' );\n var $msg = $( '
              ' )\n .text( '(' + _l( 'no datasets were found matching the current filters' ) + ')' );\n this.$( '.unpaired-columns .paired-column .column-datasets' ).empty().prepend( $msg );\n return $msg;\n },\n /** try to detect if the unpaired section has a scrollbar and adjust left column for better centering of all */\n _adjUnpairedOnScrollbar : function(){\n var $unpairedColumns = this.$( '.unpaired-columns' ).last(),\n $firstDataset = this.$( '.unpaired-columns .reverse-column .dataset' ).first();\n if( !$firstDataset.length ){ return; }\n var ucRight = $unpairedColumns.offset().left + $unpairedColumns.outerWidth(),\n dsRight = $firstDataset.offset().left + $firstDataset.outerWidth(),\n rightDiff = Math.floor( ucRight ) - Math.floor( dsRight );\n //this.debug( 'rightDiff:', ucRight, '-', dsRight, '=', rightDiff );\n this.$( '.unpaired-columns .forward-column' )\n .css( 'margin-left', ( rightDiff > 0 )? rightDiff: 0 );\n },\n\n /** render the paired section and update counts of paired datasets */\n _renderPaired : function( speed, callback ){\n //this.debug( '-- _renderPaired' );\n this.$( '.paired-column-title .title' ).text([ this.paired.length, _l( 'paired' ) ].join( ' ' ) );\n // show/hide the unpair all link\n this.$( '.unpair-all-link' ).toggle( this.paired.length !== 0 );\n if( this.paired.length === 0 ){\n this._renderPairedEmpty();\n return;\n //TODO: would be best to return here (the $columns)\n } else {\n // show/hide 'remove extensions link' when any paired and they seem to have extensions\n this.$( '.remove-extensions-link' ).show();\n }\n\n this.$( '.paired-columns .column-datasets' ).empty();\n var creator = this;\n this.paired.forEach( function( pair, i ){\n //TODO: cache these?\n var pairView = new PairView({ pair: pair });\n creator.$( '.paired-columns .column-datasets' )\n .append( pairView.render().$el )\n .append([\n ''\n ].join( '' ));\n });\n },\n /** a message to display when none paired */\n _renderPairedEmpty : function(){\n var $msg = $( '
              ' )\n .text( '(' + _l( 'no paired datasets yet' ) + ')' );\n this.$( '.paired-columns .column-datasets' ).empty().prepend( $msg );\n return $msg;\n },\n\n /** render the footer, completion controls, and cancel controls */\n _renderFooter : function( speed, callback ){\n var $footer = this.$( '.footer' ).empty().html( PairedCollectionCreator.templates.footer() );\n this.$( '.remove-extensions' ).prop( 'checked', this.removeExtensions );\n if( typeof this.oncancel === 'function' ){\n this.$( '.cancel-create.btn' ).show();\n }\n return $footer;\n },\n\n /** add any jQuery/bootstrap/custom plugins to elements rendered */\n _addPluginComponents : function(){\n this._chooseFiltersPopover( '.choose-filters-link' );\n this.$( '.help-content i' ).hoverhighlight( '.collection-creator', 'rgba( 64, 255, 255, 1.0 )' );\n },\n\n /** build a filter selection popover allowing selection of common filter pairs */\n _chooseFiltersPopover : function( selector ){\n function filterChoice( val1, val2 ){\n return [\n ''\n ].join('');\n }\n var $popoverContent = $( _.template([\n '
              ',\n '
              ',\n _l( 'Choose from the following filters to change which unpaired reads are shown in the display' ),\n ':
              ',\n _.values( this.commonFilters ).map( function( filterSet ){\n return filterChoice( filterSet[0], filterSet[1] );\n }).join( '' ),\n '
              '\n ].join(''))({}));\n\n return this.$( selector ).popover({\n container : '.collection-creator',\n placement : 'bottom',\n html : true,\n //animation : false,\n content : $popoverContent\n });\n },\n\n /** add (or clear if clear is truthy) a validation warning to what */\n _validationWarning : function( what, clear ){\n var VALIDATION_CLASS = 'validation-warning';\n if( what === 'name' ){\n what = this.$( '.collection-name' ).add( this.$( '.collection-name-prompt' ) );\n this.$( '.collection-name' ).focus().select();\n }\n if( clear ){\n what = what || this.$( '.' + VALIDATION_CLASS );\n what.removeClass( VALIDATION_CLASS );\n } else {\n what.addClass( VALIDATION_CLASS );\n }\n },\n\n // ------------------------------------------------------------------------ events\n /** set up event handlers on self */\n _setUpBehaviors : function(){\n this.once( 'rendered', function(){\n this.trigger( 'rendered:initial', this );\n });\n\n this.on( 'pair:new', function(){\n //TODO: ideally only re-render the columns (or even elements) involved\n this._renderUnpaired();\n this._renderPaired();\n\n // scroll to bottom where new pairs are added\n //TODO: this doesn't seem to work - innerHeight sticks at 133...\n // may have to do with improper flex columns\n //var $pairedView = this.$( '.paired-columns' );\n //$pairedView.scrollTop( $pairedView.innerHeight() );\n //this.debug( $pairedView.height() )\n this.$( '.paired-columns' ).scrollTop( 8000000 );\n });\n this.on( 'pair:unpair', function( pairs ){\n //TODO: ideally only re-render the columns (or even elements) involved\n this._renderUnpaired();\n this._renderPaired();\n this.splitView();\n });\n\n this.on( 'filter-change', function(){\n this.filters = [\n this.$( '.forward-unpaired-filter input' ).val(),\n this.$( '.reverse-unpaired-filter input' ).val()\n ];\n this.metric( 'filter-change', this.filters );\n this._renderFilters();\n this._renderUnpaired();\n });\n\n this.on( 'autopair', function(){\n this._renderUnpaired();\n this._renderPaired();\n\n var message, msgClass = null;\n if( this.paired.length ){\n msgClass = 'alert-success';\n message = this.paired.length + ' ' + _l( 'pairs created' );\n if( !this.unpaired.length ){\n message += ': ' + _l( 'all datasets have been successfully paired' );\n this.hideUnpaired();\n this.$( '.collection-name' ).focus();\n }\n } else {\n message = _l([\n 'Could not automatically create any pairs from the given dataset names.',\n 'You may want to choose or enter different filters and try auto-pairing again.',\n 'Close this message using the X on the right to view more help.'\n ].join( ' ' ));\n }\n this._showAlert( message, msgClass );\n });\n\n //this.on( 'all', function(){\n // this.info( arguments );\n //});\n return this;\n },\n\n events : {\n // header\n 'click .more-help' : '_clickMoreHelp',\n 'click .less-help' : '_clickLessHelp',\n 'click .header .alert button' : '_hideAlert',\n 'click .forward-column .column-title' : '_clickShowOnlyUnpaired',\n 'click .reverse-column .column-title' : '_clickShowOnlyUnpaired',\n 'click .unpair-all-link' : '_clickUnpairAll',\n //TODO: this seems kinda backasswards - re-sending jq event as a backbone event, can we listen directly?\n 'change .forward-unpaired-filter input' : function( ev ){ this.trigger( 'filter-change' ); },\n 'focus .forward-unpaired-filter input' : function( ev ){ $( ev.currentTarget ).select(); },\n 'click .autopair-link' : '_clickAutopair',\n 'click .choose-filters .filter-choice' : '_clickFilterChoice',\n 'click .clear-filters-link' : '_clearFilters',\n 'change .reverse-unpaired-filter input' : function( ev ){ this.trigger( 'filter-change' ); },\n 'focus .reverse-unpaired-filter input' : function( ev ){ $( ev.currentTarget ).select(); },\n // unpaired\n 'click .forward-column .dataset.unpaired' : '_clickUnpairedDataset',\n 'click .reverse-column .dataset.unpaired' : '_clickUnpairedDataset',\n 'click .paired-column .dataset.unpaired' : '_clickPairRow',\n 'click .unpaired-columns' : 'clearSelectedUnpaired',\n 'mousedown .unpaired-columns .dataset' : '_mousedownUnpaired',\n // divider\n 'click .paired-column-title' : '_clickShowOnlyPaired',\n 'mousedown .flexible-partition-drag' : '_startPartitionDrag',\n // paired\n 'click .paired-columns .dataset.paired' : 'selectPair',\n 'click .paired-columns' : 'clearSelectedPaired',\n 'click .paired-columns .pair-name' : '_clickPairName',\n 'click .unpair-btn' : '_clickUnpair',\n // paired - drop target\n //'dragenter .paired-columns' : '_dragenterPairedColumns',\n //'dragleave .paired-columns .column-datasets': '_dragleavePairedColumns',\n 'dragover .paired-columns .column-datasets' : '_dragoverPairedColumns',\n 'drop .paired-columns .column-datasets' : '_dropPairedColumns',\n\n 'pair.dragstart .paired-columns .column-datasets' : '_pairDragstart',\n 'pair.dragend .paired-columns .column-datasets' : '_pairDragend',\n\n // footer\n 'change .remove-extensions' : function( ev ){ this.toggleExtensions(); },\n 'change .collection-name' : '_changeName',\n 'keydown .collection-name' : '_nameCheckForEnter',\n 'click .cancel-create' : function( ev ){\n if( typeof this.oncancel === 'function' ){\n this.oncancel.call( this );\n }\n },\n 'click .create-collection' : '_clickCreate'//,\n },\n\n // ........................................................................ header\n /** expand help */\n _clickMoreHelp : function( ev ){\n this.$( '.main-help' ).addClass( 'expanded' );\n this.$( '.more-help' ).hide();\n },\n /** collapse help */\n _clickLessHelp : function( ev ){\n this.$( '.main-help' ).removeClass( 'expanded' );\n this.$( '.more-help' ).show();\n },\n\n /** show an alert on the top of the interface containing message (alertClass is bootstrap's alert-*)*/\n _showAlert : function( message, alertClass ){\n alertClass = alertClass || 'alert-danger';\n this.$( '.main-help' ).hide();\n this.$( '.header .alert' ).attr( 'class', 'alert alert-dismissable' ).addClass( alertClass ).show()\n .find( '.alert-message' ).html( message );\n },\n /** hide the alerts at the top */\n _hideAlert : function( message ){\n this.$( '.main-help' ).show();\n this.$( '.header .alert' ).hide();\n },\n\n /** toggle between showing only unpaired and split view */\n _clickShowOnlyUnpaired : function( ev ){\n //this.debug( 'click unpaired', ev.currentTarget );\n if( this.$( '.paired-columns' ).is( ':visible' ) ){\n this.hidePaired();\n } else {\n this.splitView();\n }\n },\n /** toggle between showing only paired and split view */\n _clickShowOnlyPaired : function( ev ){\n //this.debug( 'click paired' );\n if( this.$( '.unpaired-columns' ).is( ':visible' ) ){\n this.hideUnpaired();\n } else {\n this.splitView();\n }\n },\n\n /** hide unpaired, show paired */\n hideUnpaired : function( speed, callback ){\n this.unpairedPanelHidden = true;\n this.pairedPanelHidden = false;\n this._renderMiddle( speed, callback );\n },\n /** hide paired, show unpaired */\n hidePaired : function( speed, callback ){\n this.unpairedPanelHidden = false;\n this.pairedPanelHidden = true;\n this._renderMiddle( speed, callback );\n },\n /** show both paired and unpaired (splitting evenly) */\n splitView : function( speed, callback ){\n this.unpairedPanelHidden = this.pairedPanelHidden = false;\n this._renderMiddle( speed, callback );\n return this;\n },\n\n /** unpair all paired and do other super neat stuff which I'm not really sure about yet... */\n _clickUnpairAll : function( ev ){\n this.metric( 'unpairAll' );\n this.unpairAll();\n },\n\n /** attempt to autopair */\n _clickAutopair : function( ev ){\n var paired = this.autoPair();\n this.metric( 'autopair', paired.length, this.unpaired.length );\n this.trigger( 'autopair' );\n },\n\n /** set the filters based on the data attributes of the button click target */\n _clickFilterChoice : function( ev ){\n var $selected = $( ev.currentTarget );\n this.$( '.forward-unpaired-filter input' ).val( $selected.data( 'forward' ) );\n this.$( '.reverse-unpaired-filter input' ).val( $selected.data( 'reverse' ) );\n this._hideChooseFilters();\n this.trigger( 'filter-change' );\n },\n\n /** hide the choose filters popover */\n _hideChooseFilters : function(){\n //TODO: update bootstrap and remove the following hack\n // see also: https://github.com/twbs/bootstrap/issues/10260\n this.$( '.choose-filters-link' ).popover( 'hide' );\n this.$( '.popover' ).css( 'display', 'none' );\n },\n\n /** clear both filters */\n _clearFilters : function( ev ){\n this.$( '.forward-unpaired-filter input' ).val( '' );\n this.$( '.reverse-unpaired-filter input' ).val( '' );\n this.trigger( 'filter-change' );\n },\n\n // ........................................................................ unpaired\n /** select an unpaired dataset */\n _clickUnpairedDataset : function( ev ){\n ev.stopPropagation();\n return this.toggleSelectUnpaired( $( ev.currentTarget ) );\n },\n\n /** Toggle the selection of an unpaired dataset representation.\n * @param [jQuery] $dataset the unpaired dataset dom rep to select\n * @param [Boolean] options.force if defined, force selection based on T/F; otherwise, toggle\n */\n toggleSelectUnpaired : function( $dataset, options ){\n options = options || {};\n var dataset = $dataset.data( 'dataset' ),\n select = options.force !== undefined? options.force: !$dataset.hasClass( 'selected' );\n //this.debug( id, options.force, $dataset, dataset );\n if( !$dataset.length || dataset === undefined ){ return $dataset; }\n\n if( select ){\n $dataset.addClass( 'selected' );\n if( !options.waitToPair ){\n this.pairAllSelected();\n }\n\n } else {\n $dataset.removeClass( 'selected' );\n //delete dataset.selected;\n }\n return $dataset;\n },\n\n /** pair all the currently selected unpaired datasets */\n pairAllSelected : function( options ){\n options = options || {};\n var creator = this,\n fwds = [],\n revs = [],\n pairs = [];\n creator.$( '.unpaired-columns .forward-column .dataset.selected' ).each( function(){\n fwds.push( $( this ).data( 'dataset' ) );\n });\n creator.$( '.unpaired-columns .reverse-column .dataset.selected' ).each( function(){\n revs.push( $( this ).data( 'dataset' ) );\n });\n fwds.length = revs.length = Math.min( fwds.length, revs.length );\n //this.debug( fwds );\n //this.debug( revs );\n fwds.forEach( function( fwd, i ){\n try {\n pairs.push( creator._pair( fwd, revs[i], { silent: true }) );\n\n } catch( err ){\n //TODO: preserve selected state of those that couldn't be paired\n //TODO: warn that some could not be paired\n creator.error( err );\n }\n });\n if( pairs.length && !options.silent ){\n this.trigger( 'pair:new', pairs );\n }\n return pairs;\n },\n\n /** clear the selection on all unpaired datasets */\n clearSelectedUnpaired : function(){\n this.$( '.unpaired-columns .dataset.selected' ).removeClass( 'selected' );\n },\n\n /** when holding down the shift key on a click, 'paint' the moused over datasets as selected */\n _mousedownUnpaired : function( ev ){\n if( ev.shiftKey ){\n var creator = this,\n $startTarget = $( ev.target ).addClass( 'selected' ),\n moveListener = function( ev ){\n creator.$( ev.target ).filter( '.dataset' ).addClass( 'selected' );\n };\n $startTarget.parent().on( 'mousemove', moveListener );\n\n // on any mouseup, stop listening to the move and try to pair any selected\n $( document ).one( 'mouseup', function( ev ){\n $startTarget.parent().off( 'mousemove', moveListener );\n creator.pairAllSelected();\n });\n }\n },\n\n /** attempt to pair two datasets directly across from one another */\n _clickPairRow : function( ev ){\n //if( !ev.currentTarget ){ return true; }\n var rowIndex = $( ev.currentTarget ).index(),\n fwd = $( '.unpaired-columns .forward-column .dataset' ).eq( rowIndex ).data( 'dataset' ),\n rev = $( '.unpaired-columns .reverse-column .dataset' ).eq( rowIndex ).data( 'dataset' );\n //this.debug( 'row:', rowIndex, fwd, rev );\n this._pair( fwd, rev );\n },\n\n // ........................................................................ divider/partition\n /** start dragging the visible divider/partition between unpaired and paired panes */\n _startPartitionDrag : function( ev ){\n var creator = this,\n startingY = ev.pageY;\n //this.debug( 'partition drag START:', ev );\n $( 'body' ).css( 'cursor', 'ns-resize' );\n creator.$( '.flexible-partition-drag' ).css( 'color', 'black' );\n\n function endDrag( ev ){\n //creator.debug( 'partition drag STOP:', ev );\n // doing this by an added class didn't really work well - kept flashing still\n creator.$( '.flexible-partition-drag' ).css( 'color', '' );\n $( 'body' ).css( 'cursor', '' ).unbind( 'mousemove', trackMouse );\n }\n function trackMouse( ev ){\n var offset = ev.pageY - startingY;\n //creator.debug( 'partition:', startingY, offset );\n if( !creator.adjPartition( offset ) ){\n //creator.debug( 'mouseup triggered' );\n $( 'body' ).trigger( 'mouseup' );\n }\n creator._adjUnpairedOnScrollbar();\n startingY += offset;\n }\n $( 'body' ).mousemove( trackMouse );\n $( 'body' ).one( 'mouseup', endDrag );\n },\n\n /** adjust the parition up/down +/-adj pixels */\n adjPartition : function( adj ){\n var $unpaired = this.$( '.unpaired-columns' ),\n $paired = this.$( '.paired-columns' ),\n unpairedHi = parseInt( $unpaired.css( 'height' ), 10 ),\n pairedHi = parseInt( $paired.css( 'height' ), 10 );\n //this.debug( adj, 'hi\\'s:', unpairedHi, pairedHi, unpairedHi + adj, pairedHi - adj );\n\n unpairedHi = Math.max( 10, unpairedHi + adj );\n pairedHi = pairedHi - adj;\n\n var movingUpwards = adj < 0;\n // when the divider gets close to the top - lock into hiding the unpaired section\n if( movingUpwards ){\n if( this.unpairedPanelHidden ){\n return false;\n } else if( unpairedHi <= 10 ){\n this.hideUnpaired();\n return false;\n }\n } else {\n if( this.unpairedPanelHidden ){\n $unpaired.show();\n this.unpairedPanelHidden = false;\n }\n }\n\n // when the divider gets close to the bottom - lock into hiding the paired section\n if( !movingUpwards ){\n if( this.pairedPanelHidden ){\n return false;\n } else if( pairedHi <= 15 ){\n this.hidePaired();\n return false;\n }\n\n } else {\n if( this.pairedPanelHidden ){\n $paired.show();\n this.pairedPanelHidden = false;\n }\n }\n\n $unpaired.css({\n height : unpairedHi + 'px',\n flex : '0 0 auto'\n });\n return true;\n },\n\n // ........................................................................ paired\n /** select a pair when clicked */\n selectPair : function( ev ){\n ev.stopPropagation();\n $( ev.currentTarget ).toggleClass( 'selected' );\n },\n\n /** deselect all pairs */\n clearSelectedPaired : function( ev ){\n this.$( '.paired-columns .dataset.selected' ).removeClass( 'selected' );\n },\n\n /** rename a pair when the pair name is clicked */\n _clickPairName : function( ev ){\n ev.stopPropagation();\n var $name = $( ev.currentTarget ),\n $pair = $name.parent().parent(),\n index = $pair.index( '.dataset.paired' ),\n pair = this.paired[ index ],\n response = prompt( 'Enter a new name for the pair:', pair.name );\n if( response ){\n pair.name = response;\n // set a flag (which won't be passed in json creation) for manual naming so we don't overwrite these\n // when adding/removing extensions\n //hackish\n pair.customizedName = true;\n $name.text( pair.name );\n }\n },\n\n /** unpair this pair */\n _clickUnpair : function( ev ){\n //if( !ev.currentTarget ){ return true; }\n var pairIndex = Math.floor( $( ev.currentTarget ).index( '.unpair-btn' ) );\n //this.debug( 'pair:', pairIndex );\n this._unpair( this.paired[ pairIndex ] );\n },\n\n // ........................................................................ paired - drag and drop re-ordering\n //_dragenterPairedColumns : function( ev ){\n // this.debug( '_dragenterPairedColumns:', ev );\n //},\n //_dragleavePairedColumns : function( ev ){\n // //this.debug( '_dragleavePairedColumns:', ev );\n //},\n /** track the mouse drag over the paired list adding a placeholder to show where the drop would occur */\n _dragoverPairedColumns : function( ev ){\n //this.debug( '_dragoverPairedColumns:', ev );\n ev.preventDefault();\n\n var $list = this.$( '.paired-columns .column-datasets' );\n this._checkForAutoscroll( $list, ev.originalEvent.clientY );\n //this.debug( ev.originalEvent.clientX, ev.originalEvent.clientY );\n var $nearest = this._getNearestPairedDatasetLi( ev.originalEvent.clientY );\n\n $( '.element-drop-placeholder' ).remove();\n var $placeholder = $( '
              ' );\n if( !$nearest.length ){\n $list.append( $placeholder );\n } else {\n $nearest.before( $placeholder );\n }\n },\n\n /** If the mouse is near enough to the list's top or bottom, scroll the list */\n _checkForAutoscroll : function( $element, y ){\n var AUTOSCROLL_SPEED = 2;\n var offset = $element.offset(),\n scrollTop = $element.scrollTop(),\n upperDist = y - offset.top,\n lowerDist = ( offset.top + $element.outerHeight() ) - y;\n //this.debug( '_checkForAutoscroll:', scrollTop, upperDist, lowerDist );\n if( upperDist >= 0 && upperDist < this.autoscrollDist ){\n $element.scrollTop( scrollTop - AUTOSCROLL_SPEED );\n } else if( lowerDist >= 0 && lowerDist < this.autoscrollDist ){\n $element.scrollTop( scrollTop + AUTOSCROLL_SPEED );\n }\n },\n\n /** get the nearest *previous* paired dataset PairView based on the mouse's Y coordinate.\n * If the y is at the end of the list, return an empty jQuery object.\n */\n _getNearestPairedDatasetLi : function( y ){\n var WIGGLE = 4,\n lis = this.$( '.paired-columns .column-datasets li' ).toArray();\n for( var i=0; i y && top - halfHeight < y ){\n //this.debug( y, top + halfHeight, top - halfHeight )\n return $li;\n }\n }\n return $();\n },\n /** drop (dragged/selected PairViews) onto the list, re-ordering both the DOM and the internal array of pairs */\n _dropPairedColumns : function( ev ){\n // both required for firefox\n ev.preventDefault();\n ev.dataTransfer.dropEffect = 'move';\n\n var $nearest = this._getNearestPairedDatasetLi( ev.originalEvent.clientY );\n if( $nearest.length ){\n this.$dragging.insertBefore( $nearest );\n\n } else {\n // no nearest before - insert after last element (unpair button)\n this.$dragging.insertAfter( this.$( '.paired-columns .unpair-btn' ).last() );\n }\n // resync the creator's list of paired based on the new DOM order\n this._syncPairsToDom();\n return false;\n },\n /** resync the creator's list of paired based on the DOM order of pairs */\n _syncPairsToDom : function(){\n var newPaired = [];\n //TODO: doesn't seem wise to use the dom to store these - can't we sync another way?\n this.$( '.paired-columns .dataset.paired' ).each( function(){\n newPaired.push( $( this ).data( 'pair' ) );\n });\n //this.debug( newPaired );\n this.paired = newPaired;\n this._renderPaired();\n },\n /** drag communication with pair sub-views: dragstart */\n _pairDragstart : function( ev, pair ){\n //this.debug( '_pairDragstart', ev, pair )\n // auto select the pair causing the event and move all selected\n pair.$el.addClass( 'selected' );\n var $selected = this.$( '.paired-columns .dataset.selected' );\n this.$dragging = $selected;\n },\n /** drag communication with pair sub-views: dragend - remove the placeholder */\n _pairDragend : function( ev, pair ){\n //this.debug( '_pairDragend', ev, pair )\n $( '.element-drop-placeholder' ).remove();\n this.$dragging = null;\n },\n\n // ........................................................................ footer\n toggleExtensions : function( force ){\n var creator = this;\n creator.removeExtensions = ( force !== undefined )?( force ):( !creator.removeExtensions );\n\n _.each( creator.paired, function( pair ){\n // don't overwrite custom names\n if( pair.customizedName ){ return; }\n pair.name = creator._guessNameForPair( pair.forward, pair.reverse );\n });\n\n creator._renderPaired();\n creator._renderFooter();\n },\n\n /** handle a collection name change */\n _changeName : function( ev ){\n this._validationWarning( 'name', !!this._getName() );\n },\n\n /** check for enter key press when in the collection name and submit */\n _nameCheckForEnter : function( ev ){\n if( ev.keyCode === 13 && !this.blocking ){\n this._clickCreate();\n }\n },\n\n /** get the current collection name */\n _getName : function(){\n return _.escape( this.$( '.collection-name' ).val() );\n },\n\n /** attempt to create the current collection */\n _clickCreate : function( ev ){\n var name = this._getName();\n if( !name ){\n this._validationWarning( 'name' );\n } else if( !this.blocking ){\n this.createList();\n }\n },\n\n // ------------------------------------------------------------------------ misc\n /** debug a dataset list */\n _printList : function( list ){\n var creator = this;\n _.each( list, function( e ){\n if( list === creator.paired ){\n creator._printPair( e );\n } else {\n //creator.debug( e );\n }\n });\n },\n\n /** print a pair Object */\n _printPair : function( pair ){\n this.debug( pair.forward.name, pair.reverse.name, ': ->', pair.name );\n },\n\n /** string rep */\n toString : function(){ return 'PairedCollectionCreator'; }\n});\n\n\n//TODO: move to require text plugin and load these as text\n//TODO: underscore currently unnecc. bc no vars are used\n//TODO: better way of localizing text-nodes in long strings\n/** underscore template fns attached to class */\nPairedCollectionCreator.templates = PairedCollectionCreator.templates || {\n\n /** the skeleton */\n main : _.template([\n '
              ',\n '
              ',\n '
              '\n ].join('')),\n\n /** the header (not including help text) */\n header : _.template([\n '
              ',\n '', _l( 'More help' ), '',\n '
              ',\n '', _l( 'Less' ), '',\n '
              ',\n '
              ',\n '
              ',\n '',\n '',\n '
              ',\n\n '
              ',\n '
              ',\n '
              ',\n '
              ',\n '', _l( 'Unpaired forward' ), '',\n '',\n '
              ',\n '
              ',\n '',\n '
              ',\n '
              ',\n '
              ',\n '',\n '
              ',\n '
              ',\n '
              ',\n '', _l( 'Unpaired reverse' ), '',\n '',\n '
              ',\n '
              ',\n '',\n '
              ',\n '
              ',\n '
              ',\n '
              '\n ].join('')),\n\n /** the middle: unpaired, divider, and paired */\n middle : _.template([\n // contains two flex rows (rows that fill available space) and a divider btwn\n '
              ',\n '
              ',\n '
                ',\n '
                ',\n '
                ',\n '
                  ',\n '
                  ',\n '
                  ',\n '
                    ',\n '
                    ',\n '
                    ',\n '
                    ',\n '
                    ',\n '
                    ',\n '
                    ',\n '',\n '
                    ',\n '',\n _l( 'Unpair all' ),\n '',\n '
                    ',\n '
                    ',\n '
                    ',\n '
                      ',\n '
                      '\n ].join('')),\n\n /** creation and cancel controls */\n footer : _.template([\n '
                      ',\n '
                      ',\n '',\n '
                      ',\n '
                      ',\n '',\n '
                      ', _l( 'Name' ), ':
                      ',\n '
                      ',\n '
                      ',\n\n '
                      ',\n '
                      ',\n '',\n '
                      ',\n '',\n '',\n '
                      ',\n '
                      ',\n\n '
                      ',\n '',\n '
                      ',\n '
                      '\n ].join('')),\n\n /** help content */\n helpContent : _.template([\n '

                      ', _l([\n 'Collections of paired datasets are ordered lists of dataset pairs (often forward and reverse reads). ',\n 'These collections can be passed to tools and workflows in order to have analyses done on each member of ',\n 'the entire group. This interface allows you to create a collection, choose which datasets are paired, ',\n 'and re-order the final collection.'\n ].join( '' )), '

                      ',\n '

                      ', _l([\n 'Unpaired datasets are shown in the unpaired section ',\n '(hover over the underlined words to highlight below). ',\n 'Paired datasets are shown in the paired section.',\n '

                        To pair datasets, you can:',\n '
                      • Click a dataset in the ',\n 'forward column ',\n 'to select it then click a dataset in the ',\n 'reverse column.',\n '
                      • ',\n '
                      • Click one of the \"Pair these datasets\" buttons in the ',\n 'middle column ',\n 'to pair the datasets in a particular row.',\n '
                      • ',\n '
                      • Click \"Auto-pair\" ',\n 'to have your datasets automatically paired based on name.',\n '
                      • ',\n '
                      '\n ].join( '' )), '

                      ',\n '

                      ', _l([\n '

                        You can filter what is shown in the unpaired sections by:',\n '
                      • Entering partial dataset names in either the ',\n 'forward filter or ',\n 'reverse filter.',\n '
                      • ',\n '
                      • Choosing from a list of preset filters by clicking the ',\n '\"Choose filters\" link.',\n '
                      • ',\n '
                      • Entering regular expressions to match dataset names. See: ',\n 'MDN\\'s JavaScript Regular Expression Tutorial. ',\n 'Note: forward slashes (\\\\) are not needed.',\n '
                      • ',\n '
                      • Clearing the filters by clicking the ',\n '\"Clear filters\" link.',\n '
                      • ',\n '
                      '\n ].join( '' )), '

                      ',\n '

                      ', _l([\n 'To unpair individual dataset pairs, click the ',\n 'unpair buttons ( ). ',\n 'Click the \"Unpair all\" link to unpair all pairs.'\n ].join( '' )), '

                      ',\n '

                      ', _l([\n 'You can include or remove the file extensions (e.g. \".fastq\") from your pair names by toggling the ',\n '\"Remove file extensions from pair names?\" control.'\n ].join( '' )), '

                      ',\n '

                      ', _l([\n 'Once your collection is complete, enter a name and ',\n 'click \"Create list\". ',\n '(Note: you do not have to pair all unpaired datasets to finish.)'\n ].join( '' )), '

                      '\n ].join(''))\n};\n\n\n//=============================================================================\n/** a modal version of the paired collection creator */\nvar pairedCollectionCreatorModal = function _pairedCollectionCreatorModal( datasets, options ){\n\n var deferred = jQuery.Deferred(),\n creator;\n\n options = _.defaults( options || {}, {\n datasets : datasets,\n oncancel : function(){\n Galaxy.modal.hide();\n deferred.reject( 'cancelled' );\n },\n oncreate : function( creator, response ){\n Galaxy.modal.hide();\n deferred.resolve( response );\n }\n });\n\n if( !window.Galaxy || !Galaxy.modal ){\n throw new Error( 'Galaxy or Galaxy.modal not found' );\n }\n\n creator = new PairedCollectionCreator( options );\n Galaxy.modal.show({\n title : 'Create a collection of paired datasets',\n body : creator.$el,\n width : '80%',\n height : '800px',\n closing_events: true\n });\n creator.render();\n window.creator = creator;\n\n //TODO: remove modal header\n return deferred;\n};\n\n\n//=============================================================================\nfunction createListOfPairsCollection( collection ){\n var elements = collection.toJSON();\n//TODO: validate elements\n return pairedCollectionCreatorModal( elements, {\n historyId : collection.historyId\n });\n}\n\n\n//=============================================================================\n return {\n PairedCollectionCreator : PairedCollectionCreator,\n pairedCollectionCreatorModal : pairedCollectionCreatorModal,\n createListOfPairsCollection : createListOfPairsCollection\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/collection/list-of-pairs-collection-creator.js\n ** module id = 108\n ** module chunks = 3\n **/","define([\n \"mvc/collection/list-collection-creator\",\n \"mvc/history/hdca-model\",\n \"mvc/base-mvc\",\n \"utils/localization\"\n], function( LIST_CREATOR, HDCA, BASE_MVC, _l ){\n\n'use strict';\n\nvar logNamespace = 'collections';\n/*==============================================================================\nTODO:\n the paired creator doesn't really mesh with the list creator as parent\n it may be better to make an abstract super class for both\n composites may inherit from this (or vis-versa)\n PairedDatasetCollectionElementView doesn't make a lot of sense\n\n==============================================================================*/\n/** */\nvar PairedDatasetCollectionElementView = Backbone.View.extend( BASE_MVC.LoggableMixin ).extend({\n _logNamespace : logNamespace,\n\n//TODO: use proper class (DatasetDCE or NestedDCDCE (or the union of both))\n tagName : 'li',\n className : 'collection-element',\n\n initialize : function( attributes ){\n this.element = attributes.element || {};\n this.identifier = attributes.identifier;\n },\n\n render : function(){\n this.$el\n .attr( 'data-element-id', this.element.id )\n .html( this.template({ identifier: this.identifier, element: this.element }) );\n return this;\n },\n\n //TODO: lots of unused space in the element - possibly load details and display them horiz.\n template : _.template([\n '<%- identifier %>',\n '<%- element.name %>',\n ].join('')),\n\n /** remove the DOM and any listeners */\n destroy : function(){\n this.off();\n this.$el.remove();\n },\n\n /** string rep */\n toString : function(){\n return 'DatasetCollectionElementView()';\n }\n});\n\n\n// ============================================================================\nvar _super = LIST_CREATOR.ListCollectionCreator;\n\n/** An interface for building collections.\n */\nvar PairCollectionCreator = _super.extend({\n\n /** the class used to display individual elements */\n elementViewClass : PairedDatasetCollectionElementView,\n /** the class this creator will create and save */\n collectionClass : HDCA.HistoryPairDatasetCollection,\n className : 'pair-collection-creator collection-creator flex-row-container',\n\n /** override to no-op */\n _mangleDuplicateNames : function(){},\n\n // TODO: this whole pattern sucks. There needs to be two classes of problem area:\n // bad inital choices and\n // when the user has painted his/her self into a corner during creation/use-of-the-creator\n /** render the entire interface */\n render : function( speed, callback ){\n if( this.workingElements.length === 2 ){\n return _super.prototype.render.call( this, speed, callback );\n }\n return this._renderInvalid( speed, callback );\n },\n\n // ------------------------------------------------------------------------ rendering elements\n /** render forward/reverse */\n _renderList : function( speed, callback ){\n //this.debug( '-- _renderList' );\n //precondition: there are two valid elements in workingElements\n var creator = this,\n $tmp = jQuery( '
                      ' ),\n $list = creator.$list();\n\n // lose the original views, create the new, append all at once, then call their renders\n _.each( this.elementViews, function( view ){\n view.destroy();\n creator.removeElementView( view );\n });\n $tmp.append( creator._createForwardElementView().$el );\n $tmp.append( creator._createReverseElementView().$el );\n $list.empty().append( $tmp.children() );\n _.invoke( creator.elementViews, 'render' );\n },\n\n /** create the forward element view */\n _createForwardElementView : function(){\n return this._createElementView( this.workingElements[0], { identifier: 'forward' } );\n },\n\n /** create the forward element view */\n _createReverseElementView : function(){\n return this._createElementView( this.workingElements[1], { identifier: 'reverse' } );\n },\n\n /** create an element view, cache in elementViews, and return */\n _createElementView : function( element, options ){\n var elementView = new this.elementViewClass( _.extend( options, {\n element : element,\n }));\n this.elementViews.push( elementView );\n return elementView;\n },\n\n /** swap the forward, reverse elements and re-render */\n swap : function(){\n this.workingElements = [\n this.workingElements[1],\n this.workingElements[0],\n ];\n this._renderList();\n },\n\n events : _.extend( _.clone( _super.prototype.events ), {\n 'click .swap' : 'swap',\n }),\n\n // ------------------------------------------------------------------------ templates\n //TODO: move to require text plugin and load these as text\n //TODO: underscore currently unnecc. bc no vars are used\n //TODO: better way of localizing text-nodes in long strings\n /** underscore template fns attached to class */\n templates : _.extend( _.clone( _super.prototype.templates ), {\n /** the middle: element list */\n middle : _.template([\n '',\n '
                      ',\n '
                      '\n ].join('')),\n\n /** help content */\n helpContent : _.template([\n '

                      ', _l([\n 'Pair collections are permanent collections containing two datasets: one forward and one reverse. ',\n 'Often these are forward and reverse reads. The pair collections can be passed to tools and ',\n 'workflows in order to have analyses done on both datasets. This interface allows ',\n 'you to create a pair, name it, and swap which is forward and which reverse.'\n ].join( '' )), '

                      ',\n '
                        ',\n '
                      • ', _l([\n 'Click the \"Swap\" link to make your forward dataset the reverse ',\n 'and the reverse dataset forward.'\n ].join( '' )), '
                      • ',\n '
                      • ', _l([\n 'Click the \"Cancel\" button to exit the interface.'\n ].join( '' )), '
                      • ',\n '

                      ',\n '

                      ', _l([\n 'Once your collection is complete, enter a name and ',\n 'click \"Create list\".'\n ].join( '' )), '

                      '\n ].join('')),\n\n /** a simplified page communicating what went wrong and why the user needs to reselect something else */\n invalidInitial : _.template([\n '
                      ',\n '
                      ',\n '',\n '<% if( _.size( problems ) ){ %>',\n _l( 'The following selections could not be included due to problems' ),\n '
                        <% _.each( problems, function( problem ){ %>',\n '
                      • <%- problem.element.name %>: <%- problem.text %>
                      • ',\n '<% }); %>
                      ',\n '<% } else if( _.size( elements ) === 0 ){ %>',\n _l( 'No datasets were selected' ), '.',\n '<% } else if( _.size( elements ) === 1 ){ %>',\n _l( 'Only one dataset was selected' ), ': <%- elements[0].name %>',\n '<% } else if( _.size( elements ) > 2 ){ %>',\n _l( 'Too many datasets were selected' ),\n ': <%- _.pluck( elements, \"name\" ).join( \", \") %>',\n '<% } %>',\n '
                      ',\n _l( 'Two (and only two) elements are needed for the pair' ), '. ',\n _l( 'You may need to ' ),\n '', _l( 'cancel' ), ' ',\n _l( 'and reselect new elements' ), '.',\n '
                      ',\n '
                      ',\n '
                      ',\n '
                      ',\n '
                      ',\n '
                      ',\n '',\n // _l( 'Create a different kind of collection' ),\n '
                      ',\n '
                      ',\n '
                      '\n ].join('')),\n }),\n\n // ------------------------------------------------------------------------ misc\n /** string rep */\n toString : function(){ return 'PairCollectionCreator'; }\n});\n\n\n//==============================================================================\n/** List collection flavor of collectionCreatorModal. */\nvar pairCollectionCreatorModal = function _pairCollectionCreatorModal( elements, options ){\n options = options || {};\n options.title = _l( 'Create a collection from a pair of datasets' );\n return LIST_CREATOR.collectionCreatorModal( elements, options, PairCollectionCreator );\n};\n\n\n//==============================================================================\n/** Use a modal to create a pair collection, then add it to the given history contents.\n * @returns {Deferred} resolved when the collection is added to the history.\n */\nfunction createPairCollection( contents ){\n var elements = contents.toJSON(),\n promise = pairCollectionCreatorModal( elements, {\n creationFn : function( elements, name ){\n elements = [\n { name: \"forward\", src: \"hda\", id: elements[0].id },\n { name: \"reverse\", src: \"hda\", id: elements[1].id }\n ];\n return contents.createHDCA( elements, 'paired', name );\n }\n });\n return promise;\n}\n\n//==============================================================================\n return {\n PairCollectionCreator : PairCollectionCreator,\n pairCollectionCreatorModal : pairCollectionCreatorModal,\n createPairCollection : createPairCollection,\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/collection/pair-collection-creator.js\n ** module id = 109\n ** module chunks = 3\n **/","define([\n \"mvc/ui/ui-modal\",\n \"mvc/ui/error-modal\",\n \"utils/localization\"\n], function( MODAL, ERROR_MODAL, _l ){\n\n'use strict';\n\n//==============================================================================\n/**\n * A dialog/modal that allows copying a user history or 'importing' from user\n * another. Generally called via historyCopyDialog below.\n * @type {Object}\n */\nvar CopyDialog = {\n\n // language related strings/fns\n defaultName : _.template( \"Copy of '<%- name %>'\" ),\n title : _.template( _l( 'Copying history' ) + ' \"<%- name %>\"' ),\n submitLabel : _l( 'Copy' ),\n errorMessage : _l( 'History could not be copied.' ),\n progressive : _l( 'Copying history' ),\n activeLabel : _l( 'Copy only the active, non-deleted datasets' ),\n allLabel : _l( 'Copy all datasets including deleted ones' ),\n anonWarning : _l( 'As an anonymous user, unless you login or register, you will lose your current history ' ) +\n _l( 'after copying this history. ' ),\n\n // template for modal body\n _template : _.template([\n //TODO: remove inline styles\n // show a warning message for losing current to anon users\n '<% if( isAnon ){ %>',\n '
                      ',\n '<%- anonWarning %>',\n _l( 'You can' ),\n ' ', _l( 'login here' ), ' ', _l( 'or' ), ' ',\n ' ', _l( 'register here' ), '.',\n '
                      ',\n '<% } %>',\n '
                      ',\n '
                      ',\n // TODO: could use required here and the form validators\n // NOTE: use unescaped here if escaped in the modal function below\n '\" />',\n '

                      ',\n _l( 'Please enter a valid history title' ),\n '

                      ',\n // if allowAll, add the option to copy deleted datasets, too\n '<% if( allowAll ){ %>',\n '
                      ',\n '

                      ', _l( 'Choose which datasets from the original history to include:' ), '

                      ',\n // copy non-deleted is the default\n '/>',\n '',\n '
                      ',\n '/>',\n '',\n '<% } %>',\n '
                      '\n ].join( '' )),\n\n // empty modal body and let the user know the copy is happening\n _showAjaxIndicator : function _showAjaxIndicator(){\n var indicator = '

                      ' + this.progressive + '...

                      ';\n this.modal.$( '.modal-body' ).empty().append( indicator ).css({ 'margin-top': '8px' });\n },\n\n // (sorta) public interface - display the modal, render the form, and potentially copy the history\n // returns a jQuery.Deferred done->history copied, fail->user cancelled\n dialog : function _dialog( modal, history, options ){\n options = options || {};\n\n var dialog = this,\n deferred = jQuery.Deferred(),\n // TODO: getting a little byzantine here\n defaultCopyNameFn = options.nameFn || this.defaultName,\n defaultCopyName = defaultCopyNameFn({ name: history.get( 'name' ) }),\n // TODO: these two might be simpler as one 3 state option (all,active,no-choice)\n defaultCopyWhat = options.allDatasets? 'copy-all' : 'copy-non-deleted',\n allowAll = !_.isUndefined( options.allowAll )? options.allowAll : true,\n autoClose = !_.isUndefined( options.autoClose )? options.autoClose : true;\n\n this.modal = modal;\n\n\n // validate the name and copy if good\n function checkNameAndCopy(){\n var name = modal.$( '#copy-modal-title' ).val();\n if( !name ){\n modal.$( '.invalid-title' ).show();\n return;\n }\n // get further settings, shut down and indicate the ajax call, then hide and resolve/reject\n var copyAllDatasets = modal.$( 'input[name=\"copy-what\"]:checked' ).val() === 'copy-all';\n modal.$( 'button' ).prop( 'disabled', true );\n dialog._showAjaxIndicator();\n history.copy( true, name, copyAllDatasets )\n .done( function( response ){\n deferred.resolve( response );\n })\n .fail( function( xhr, status, message ){\n var options = { name: name, copyAllDatasets: copyAllDatasets };\n ERROR_MODAL.ajaxErrorModal( history, xhr, options, dialog.errorMessage );\n deferred.rejectWith( deferred, arguments );\n })\n .done( function(){\n if( autoClose ){ modal.hide(); }\n });\n }\n\n var originalClosingCallback = options.closing_callback;\n modal.show( _.extend( options, {\n title : this.title({ name: history.get( 'name' ) }),\n body : $( dialog._template({\n name : defaultCopyName,\n isAnon : Galaxy.user.isAnonymous(),\n allowAll : allowAll,\n copyWhat : defaultCopyWhat,\n activeLabel : this.activeLabel,\n allLabel : this.allLabel,\n anonWarning : this.anonWarning,\n })),\n buttons : _.object([\n [ _l( 'Cancel' ), function(){ modal.hide(); } ],\n [ this.submitLabel, checkNameAndCopy ]\n ]),\n height : 'auto',\n closing_events : true,\n closing_callback: function _historyCopyClose( cancelled ){\n if( cancelled ){\n deferred.reject({ cancelled : true });\n }\n if( originalClosingCallback ){\n originalClosingCallback( cancelled );\n }\n }\n }));\n\n // set the default dataset copy, autofocus the title, and set up for a simple return\n modal.$( '#copy-modal-title' ).focus().select();\n modal.$( '#copy-modal-title' ).on( 'keydown', function( ev ){\n if( ev.keyCode === 13 ){\n ev.preventDefault();\n checkNameAndCopy();\n }\n });\n\n return deferred;\n },\n};\n\n//==============================================================================\n// maintain the (slight) distinction between copy and import\n/**\n * Subclass CopyDialog to use the import language.\n */\nvar ImportDialog = _.extend( {}, CopyDialog, {\n defaultName : _.template( \"imported: <%- name %>\" ),\n title : _.template( _l( 'Importing history' ) + ' \"<%- name %>\"' ),\n submitLabel : _l( 'Import' ),\n errorMessage : _l( 'History could not be imported.' ),\n progressive : _l( 'Importing history' ),\n activeLabel : _l( 'Import only the active, non-deleted datasets' ),\n allLabel : _l( 'Import all datasets including deleted ones' ),\n anonWarning : _l( 'As an anonymous user, unless you login or register, you will lose your current history ' ) +\n _l( 'after importing this history. ' ),\n\n});\n\n//==============================================================================\n/**\n * Main interface for both history import and history copy dialogs.\n * @param {Backbone.Model} history the history to copy\n * @param {Object} options a hash\n * @return {jQuery.Deferred} promise that fails on close and succeeds on copy\n *\n * options:\n * (this object is also passed to the modal used to display the dialog and accepts modal options)\n * {Function} nameFn if defined, use this to build the default name shown to the user\n * (the fn is passed: {name: })\n * {bool} useImport if true, use the 'import' language (instead of Copy)\n * {bool} allowAll if true, allow the user to choose between copying all datasets and\n * only non-deleted datasets\n * {String} allDatasets default initial checked radio button: 'copy-all' or 'copy-non-deleted',\n */\nvar historyCopyDialog = function( history, options ){\n options = options || {};\n // create our own modal if Galaxy doesn't have one (mako tab without use_panels)\n var modal = window.parent.Galaxy.modal || new MODAL.View({});\n return options.useImport?\n ImportDialog.dialog( modal, history, options ):\n CopyDialog.dialog( modal, history, options );\n};\n\n\n//==============================================================================\n return historyCopyDialog;\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/history/copy-dialog.js\n ** module id = 110\n ** module chunks = 3\n **/","define([\n \"mvc/dataset/dataset-li-edit\",\n \"mvc/history/hda-li\",\n \"mvc/base-mvc\",\n \"utils/localization\"\n], function( DATASET_LI_EDIT, HDA_LI, BASE_MVC, _l ){\n\n'use strict';\n\n//==============================================================================\nvar _super = DATASET_LI_EDIT.DatasetListItemEdit;\n/** @class Editing view for HistoryDatasetAssociation.\n */\nvar HDAListItemEdit = _super.extend(\n/** @lends HDAListItemEdit.prototype */{\n\n className : _super.prototype.className + \" history-content\",\n\n /** In this override, only get details if in the ready state, get rerunnable if in other states.\n * Note: fetch with no 'change' event triggering to prevent automatic rendering.\n */\n _fetchModelDetails : function(){\n var view = this;\n if( view.model.inReadyState() && !view.model.hasDetails() ){\n return view.model.fetch({ silent: true });\n\n // special case the need for the rerunnable and creating_job attributes\n // needed for rendering re-run button on queued, running datasets\n } else if( !view.model.has( 'rerunnable' ) ){\n return view.model.fetch({ silent: true, data: {\n // only fetch rerunnable and creating_job to keep overhead down\n keys: [ 'rerunnable', 'creating_job' ].join(',')\n }});\n }\n return jQuery.when();\n },\n\n /** event map */\n events : _.extend( _.clone( _super.prototype.events ), {\n 'click .unhide-link' : function( ev ){ this.model.unhide(); return false; }\n }),\n\n /** string rep */\n toString : function(){\n var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n return 'HDAListItemEdit(' + modelString + ')';\n }\n});\n\n\n// ............................................................................ TEMPLATES\n/** underscore templates */\nHDAListItemEdit.prototype.templates = (function(){\n\n var warnings = _.extend( {}, _super.prototype.templates.warnings, {\n hidden : BASE_MVC.wrapTemplate([\n '<% if( !dataset.visible ){ %>',\n // add a link to unhide a dataset\n '
                      ',\n _l( 'This dataset has been hidden' ),\n '
                      ', _l( 'Unhide it' ), '',\n '
                      ',\n '<% } %>'\n ], 'dataset' )\n });\n\n return _.extend( {}, _super.prototype.templates, {\n //NOTE: *steal* the HDAListItemView titleBar\n titleBar : HDA_LI.HDAListItemView.prototype.templates.titleBar,\n warnings : warnings\n });\n}());\n\n\n//==============================================================================\n return {\n HDAListItemEdit : HDAListItemEdit\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/history/hda-li-edit.js\n ** module id = 111\n ** module chunks = 3\n **/","define([\n \"mvc/history/hdca-li\",\n \"mvc/collection/collection-view-edit\",\n \"ui/fa-icon-button\",\n \"utils/localization\"\n], function( HDCA_LI, DC_VIEW_EDIT, faIconButton, _l ){\n\n'use strict';\n\n//==============================================================================\nvar _super = HDCA_LI.HDCAListItemView;\n/** @class Editing view for HistoryDatasetCollectionAssociation.\n */\nvar HDCAListItemEdit = _super.extend(\n/** @lends HDCAListItemEdit.prototype */{\n\n /** logger used to record this.log messages, commonly set to console */\n //logger : console,\n\n /** Override to return editable versions of the collection panels */\n _getFoldoutPanelClass : function(){\n switch( this.model.get( 'collection_type' ) ){\n case 'list':\n return DC_VIEW_EDIT.ListCollectionViewEdit;\n case 'paired':\n return DC_VIEW_EDIT.PairCollectionViewEdit;\n case 'list:paired':\n return DC_VIEW_EDIT.ListOfPairsCollectionViewEdit;\n case 'list:list':\n return DC_VIEW_EDIT.ListOfListsCollectionViewEdit;\n }\n throw new TypeError( 'Uknown collection_type: ' + this.model.get( 'collection_type' ) );\n },\n\n // ......................................................................... delete\n /** In this override, add the delete button. */\n _renderPrimaryActions : function(){\n this.log( this + '._renderPrimaryActions' );\n // render the display, edit attr and delete icon-buttons\n return _super.prototype._renderPrimaryActions.call( this )\n .concat([\n this._renderDeleteButton()\n ]);\n },\n\n /** Render icon-button to delete this collection. */\n _renderDeleteButton : function(){\n var self = this,\n deleted = this.model.get( 'deleted' );\n return faIconButton({\n title : deleted? _l( 'Dataset collection is already deleted' ): _l( 'Delete' ),\n classes : 'delete-btn',\n faIcon : 'fa-times',\n disabled : deleted,\n onclick : function() {\n // ...bler... tooltips being left behind in DOM (hover out never called on deletion)\n self.$el.find( '.icon-btn.delete-btn' ).trigger( 'mouseout' );\n self.model[ 'delete' ]();\n }\n });\n },\n\n // ......................................................................... misc\n /** string rep */\n toString : function(){\n var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n return 'HDCAListItemEdit(' + modelString + ')';\n }\n});\n\n//==============================================================================\n return {\n HDCAListItemEdit : HDCAListItemEdit\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/history/hdca-li-edit.js\n ** module id = 112\n ** module chunks = 3\n **/","define([\n \"mvc/history/history-model\",\n \"mvc/history/history-view-edit\",\n \"mvc/base-mvc\",\n \"utils/localization\"\n], function( HISTORY_MODEL, HISTORY_VIEW_EDIT, BASE_MVC, _l ){\n\n'use strict';\n\n// ============================================================================\n/** session storage for history panel preferences (and to maintain state)\n */\nvar HistoryViewPrefs = BASE_MVC.SessionStorageModel.extend(\n/** @lends HistoryViewPrefs.prototype */{\n defaults : {\n /** should the tags editor be shown or hidden initially? */\n tagsEditorShown : false,\n /** should the annotation editor be shown or hidden initially? */\n annotationEditorShown : false,\n ///** what is the currently focused content (dataset or collection) in the current history?\n // * (the history panel will highlight and scroll to the focused content view)\n // */\n //focusedContentId : null\n /** Current scroll position */\n scrollPosition : 0\n },\n toString : function(){\n return 'HistoryViewPrefs(' + JSON.stringify( this.toJSON() ) + ')';\n }\n});\n\n/** key string to store panel prefs (made accessible on class so you can access sessionStorage directly) */\nHistoryViewPrefs.storageKey = function storageKey(){\n return ( 'history-panel' );\n};\n\n/* =============================================================================\nTODO:\n\n============================================================================= */\nvar _super = HISTORY_VIEW_EDIT.HistoryViewEdit;\n// used in root/index.mako\n/** @class View/Controller for the user's current history model as used in the history\n * panel (current right hand panel) of the analysis page.\n *\n * The only history panel that:\n * will poll for updates.\n * displays datasets in reverse hid order.\n */\nvar CurrentHistoryView = _super.extend(/** @lends CurrentHistoryView.prototype */{\n\n className : _super.prototype.className + ' current-history-panel',\n\n /** override to use drilldown (and not foldout) for how collections are displayed */\n HDCAViewClass : _super.prototype.HDCAViewClass.extend({\n foldoutStyle : 'drilldown'\n }),\n\n emptyMsg : [\n _l( 'This history is empty' ), '. ',\n _l( 'You can ' ),\n '',\n _l( 'load your own data' ),\n '',\n _l( ' or ' ),\n '',\n _l( 'get data from an external source' ),\n ''\n ].join(''),\n\n // ......................................................................... SET UP\n /** Set up the view, set up storage, bind listeners to HistoryContents events */\n initialize : function( attributes ){\n attributes = attributes || {};\n\n // ---- persistent preferences\n /** maintain state / preferences over page loads */\n this.preferences = new HistoryViewPrefs( _.extend({\n id : HistoryViewPrefs.storageKey()\n }, _.pick( attributes, _.keys( HistoryViewPrefs.prototype.defaults ) )));\n\n _super.prototype.initialize.call( this, attributes );\n\n /** sub-views that will overlay this panel (collections) */\n this.panelStack = [];\n\n /** id of currently focused content */\n this.currentContentId = attributes.currentContentId || null;\n //NOTE: purposely not sent to localstorage since panel recreation roughly lines up with a reset of this value\n },\n\n /** Override to cache the current scroll position with a listener */\n _setUpListeners : function(){\n _super.prototype._setUpListeners.call( this );\n\n var panel = this;\n // reset scroll position when there's a new history\n this.on( 'new-model', function(){\n panel.preferences.set( 'scrollPosition', 0 );\n });\n },\n\n // ------------------------------------------------------------------------ loading history/item models\n // TODO: next three more appropriate moved to the app level\n /** (re-)loads the user's current history & contents w/ details */\n loadCurrentHistory : function(){\n return this.loadHistory( null, { url : Galaxy.root + 'history/current_history_json' });\n },\n\n /** loads a history & contents w/ details and makes them the current history */\n switchToHistory : function( historyId, attributes ){\n if( Galaxy.user.isAnonymous() ){\n this.trigger( 'error', _l( 'You must be logged in to switch histories' ), _l( 'Anonymous user' ) );\n return $.when();\n }\n return this.loadHistory( historyId, { url : Galaxy.root + 'history/set_as_current?id=' + historyId });\n },\n\n /** creates a new history on the server and sets it as the user's current history */\n createNewHistory : function( attributes ){\n if( Galaxy.user.isAnonymous() ){\n this.trigger( 'error', _l( 'You must be logged in to create histories' ), _l( 'Anonymous user' ) );\n return $.when();\n }\n return this.loadHistory( null, { url : Galaxy.root + 'history/create_new_current' });\n },\n\n /** release/free/shutdown old models and set up panel for new models */\n setModel : function( model, attributes, render ){\n _super.prototype.setModel.call( this, model, attributes, render );\n if( this.model && this.model.id ){\n this.log( 'checking for updates' );\n this.model.checkForUpdates();\n }\n return this;\n },\n\n // ------------------------------------------------------------------------ history/content event listening\n /** listening for history events */\n _setUpModelListeners : function(){\n _super.prototype._setUpModelListeners.call( this );\n // re-broadcast any model change events so that listeners don't have to re-bind to each history\n return this.listenTo( this.model, {\n 'change:nice_size change:size' : function(){\n this.trigger( 'history-size-change', this, this.model, arguments );\n },\n 'change:id' : function(){\n this.once( 'loading-done', function(){ this.model.checkForUpdates(); });\n }\n });\n },\n\n /** listening for collection events */\n _setUpCollectionListeners : function(){\n _super.prototype._setUpCollectionListeners.call( this );\n // if a hidden item is created (gen. by a workflow), moves thru the updater to the ready state,\n // then: remove it from the collection if the panel is set to NOT show hidden datasets\n this.listenTo( this.collection, 'state:ready', function( model, newState, oldState ){\n if( ( !model.get( 'visible' ) )\n && ( !this.collection.storage.includeHidden() ) ){\n this.removeItemView( model );\n }\n });\n },\n\n // ------------------------------------------------------------------------ panel rendering\n /** override to add a handler to capture the scroll position when the parent scrolls */\n _setUpBehaviors : function( $where ){\n $where = $where || this.$el;\n // console.log( '_setUpBehaviors', this.$scrollContainer( $where ).get(0), this.$list( $where ) );\n // we need to call this in _setUpBehaviors which is called after render since the $el\n // may not be attached to $el.parent and $scrollContainer() may not work\n var panel = this;\n _super.prototype._setUpBehaviors.call( panel, $where );\n\n // cache the handler to remove and re-add so we don't pile up the handlers\n if( !this._debouncedScrollCaptureHandler ){\n this._debouncedScrollCaptureHandler = _.debounce( function scrollCapture(){\n // cache the scroll position (only if visible)\n if( panel.$el.is( ':visible' ) ){\n panel.preferences.set( 'scrollPosition', $( this ).scrollTop() );\n }\n }, 40 );\n }\n\n panel.$scrollContainer( $where )\n .off( 'scroll', this._debouncedScrollCaptureHandler )\n .on( 'scroll', this._debouncedScrollCaptureHandler );\n return panel;\n },\n\n /** In this override, handle null models and move the search input to the top */\n _buildNewRender : function(){\n if( !this.model ){ return $(); }\n var $newRender = _super.prototype._buildNewRender.call( this );\n $newRender.find( '.search' ).prependTo( $newRender.find( '> .controls' ) );\n this._renderQuotaMessage( $newRender );\n return $newRender;\n },\n\n /** render the message displayed when a user is over quota and can't run jobs */\n _renderQuotaMessage : function( $whereTo ){\n $whereTo = $whereTo || this.$el;\n return $( this.templates.quotaMsg( {}, this ) ).prependTo( $whereTo.find( '.messages' ) );\n },\n\n /** In this override, get and set current panel preferences when editor is used */\n _renderTags : function( $where ){\n var panel = this;\n // render tags and show/hide based on preferences\n _super.prototype._renderTags.call( panel, $where );\n if( panel.preferences.get( 'tagsEditorShown' ) ){\n panel.tagsEditor.toggle( true );\n }\n // store preference when shown or hidden\n panel.listenTo( panel.tagsEditor, 'hiddenUntilActivated:shown hiddenUntilActivated:hidden',\n function( tagsEditor ){\n panel.preferences.set( 'tagsEditorShown', tagsEditor.hidden );\n }\n );\n },\n\n /** In this override, get and set current panel preferences when editor is used */\n _renderAnnotation : function( $where ){\n var panel = this;\n // render annotation and show/hide based on preferences\n _super.prototype._renderAnnotation.call( panel, $where );\n if( panel.preferences.get( 'annotationEditorShown' ) ){\n panel.annotationEditor.toggle( true );\n }\n // store preference when shown or hidden\n panel.listenTo( panel.annotationEditor, 'hiddenUntilActivated:shown hiddenUntilActivated:hidden',\n function( annotationEditor ){\n panel.preferences.set( 'annotationEditorShown', annotationEditor.hidden );\n }\n );\n },\n\n /** Override to scroll to cached position (in prefs) after swapping */\n _swapNewRender : function( $newRender ){\n _super.prototype._swapNewRender.call( this, $newRender );\n var panel = this;\n _.delay( function(){\n var pos = panel.preferences.get( 'scrollPosition' );\n if( pos ){\n panel.scrollTo( pos, 0 );\n }\n }, 10 );\n //TODO: is this enough of a delay on larger histories?\n\n return this;\n },\n\n // ------------------------------------------------------------------------ sub-views\n /** Override to add the current-content highlight class to currentContentId's view */\n _attachItems : function( $whereTo ){\n _super.prototype._attachItems.call( this, $whereTo );\n var panel = this;\n if( panel.currentContentId ){\n panel._setCurrentContentById( panel.currentContentId );\n }\n return this;\n },\n\n /** Override to remove any drill down panels */\n addItemView : function( model, collection, options ){\n var view = _super.prototype.addItemView.call( this, model, collection, options );\n if( !view ){ return view; }\n if( this.panelStack.length ){ return this._collapseDrilldownPanel(); }\n return view;\n },\n\n // ------------------------------------------------------------------------ collection sub-views\n /** In this override, add/remove expanded/collapsed model ids to/from web storage */\n _setUpItemViewListeners : function( view ){\n var panel = this;\n _super.prototype._setUpItemViewListeners.call( panel, view );\n // use pub-sub to: handle drilldown expansion and collapse\n return panel.listenTo( view, {\n 'expanded:drilldown' : function( v, drilldown ){\n this._expandDrilldownPanel( drilldown );\n },\n 'collapsed:drilldown' : function( v, drilldown ){\n this._collapseDrilldownPanel( drilldown );\n },\n });\n },\n\n /** display 'current content': add a visible highlight and store the id of a content item */\n setCurrentContent : function( view ){\n this.$( '.history-content.current-content' ).removeClass( 'current-content' );\n if( view ){\n view.$el.addClass( 'current-content' );\n this.currentContentId = view.model.id;\n } else {\n this.currentContentId = null;\n }\n },\n\n /** find the view with the id and then call setCurrentContent on it */\n _setCurrentContentById : function( id ){\n var view = this.viewFromModelId( id ) || null;\n this.setCurrentContent( view );\n },\n\n /** Handle drill down by hiding this panels list and controls and showing the sub-panel */\n _expandDrilldownPanel : function( drilldown ){\n this.panelStack.push( drilldown );\n // hide this panel's controls and list, set the name for back navigation, and attach to the $el\n this.$controls().add( this.$list() ).hide();\n drilldown.parentName = this.model.get( 'name' );\n drilldown.delegateEvents().render().$el.appendTo( this.$el );\n },\n\n /** Handle drilldown close by freeing the panel and re-rendering this panel */\n _collapseDrilldownPanel : function( drilldown ){\n this.panelStack.pop();\n //TODO: MEM: free the panel\n this.$controls().add( this.$list() ).show();\n },\n\n // ........................................................................ panel events\n /** event map */\n events : _.extend( _.clone( _super.prototype.events ), {\n // the two links in the empty message\n 'click .uploader-link' : function( ev ){ Galaxy.upload.show( ev ); },\n 'click .get-data-link' : function( ev ){\n var $toolMenu = $( '.toolMenuContainer' );\n $toolMenu.parent().scrollTop( 0 );\n $toolMenu.find( 'span:contains(\"Get Data\")' ).click();\n }\n }),\n\n // ........................................................................ external objects/MVC\n listenToGalaxy : function( galaxy ){\n this.listenTo( galaxy, {\n // when the galaxy_main iframe is loaded with a new page,\n // compare the url to the following list and if there's a match\n // pull the id from url and indicate in the history view that\n // the dataset with that id is the 'current'ly active dataset\n 'galaxy_main:load': function( data ){\n var pathToMatch = data.fullpath;\n var hdaId = null;\n var useToURLRegexMap = {\n 'display' : /datasets\\/([a-f0-9]+)\\/display/,\n 'edit' : /datasets\\/([a-f0-9]+)\\/edit/,\n 'report_error' : /dataset\\/errors\\?id=([a-f0-9]+)/,\n 'rerun' : /tool_runner\\/rerun\\?id=([a-f0-9]+)/,\n 'show_params' : /datasets\\/([a-f0-9]+)\\/show_params/,\n // no great way to do this here? (leave it in the dataset event handlers above?)\n // 'visualization' : 'visualization',\n };\n _.find( useToURLRegexMap, function( regex, use ){\n // grab the more specific match result (1), save, and use it as the find flag\n hdaId = _.result( pathToMatch.match( regex ), 1 );\n return hdaId;\n });\n // need to type mangle to go from web route to history contents\n this._setCurrentContentById( hdaId? ( 'dataset-' + hdaId ) : null );\n },\n // when the center panel is given a new view, clear the current indicator\n 'center-panel:load': function( view ){\n this._setCurrentContentById();\n }\n });\n },\n\n //TODO: remove quota meter from panel and remove this\n /** add listeners to an external quota meter (mvc/user/user-quotameter.js) */\n connectToQuotaMeter : function( quotaMeter ){\n if( !quotaMeter ){\n return this;\n }\n // show/hide the 'over quota message' in the history when the meter tells it to\n this.listenTo( quotaMeter, 'quota:over', this.showQuotaMessage );\n this.listenTo( quotaMeter, 'quota:under', this.hideQuotaMessage );\n\n // having to add this to handle re-render of hview while overquota (the above do not fire)\n this.on( 'rendered rendered:initial', function(){\n if( quotaMeter && quotaMeter.isOverQuota() ){\n this.showQuotaMessage();\n }\n });\n return this;\n },\n\n /** Override to preserve the quota message */\n clearMessages : function( ev ){\n var $target = !_.isUndefined( ev )?\n $( ev.currentTarget )\n :this.$messages().children( '[class$=\"message\"]' );\n $target = $target.not( '.quota-message' );\n $target.fadeOut( this.fxSpeed, function(){\n $( this ).remove();\n });\n return this;\n },\n\n /** Show the over quota message (which happens to be in the history panel).\n */\n showQuotaMessage : function(){\n var $msg = this.$( '.quota-message' );\n if( $msg.is( ':hidden' ) ){ $msg.slideDown( this.fxSpeed ); }\n },\n\n /** Hide the over quota message (which happens to be in the history panel).\n */\n hideQuotaMessage : function(){\n var $msg = this.$( '.quota-message' );\n if( !$msg.is( ':hidden' ) ){ $msg.slideUp( this.fxSpeed ); }\n },\n\n // ........................................................................ options menu\n //TODO: remove to batch\n /** unhide any hidden datasets */\n unhideHidden : function() {\n var self = this;\n if( confirm( _l( 'Really unhide all hidden datasets?' ) ) ){\n // get all hidden, regardless of deleted/purged\n return self.model.contents._filterAndUpdate(\n { visible: false, deleted: '', purged: '' },\n { visible : true }\n ).done( function(){\n // TODO: would be better to render these as they're unhidden instead of all at once\n if( !self.model.contents.includeHidden ){\n self.renderItems();\n }\n });\n }\n return jQuery.when();\n },\n\n /** delete any hidden datasets */\n deleteHidden : function() {\n var self = this;\n if( confirm( _l( 'Really delete all hidden datasets?' ) ) ){\n return self.model.contents._filterAndUpdate(\n // get all hidden, regardless of deleted/purged\n { visible: false, deleted: '', purged: '' },\n // both delete *and* unhide them\n { deleted : true, visible: true }\n );\n }\n return jQuery.when();\n },\n\n /** Return a string rep of the history */\n toString : function(){\n return 'CurrentHistoryView(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';\n }\n});\n\n\n//------------------------------------------------------------------------------ TEMPLATES\nCurrentHistoryView.prototype.templates = (function(){\n\n var quotaMsgTemplate = BASE_MVC.wrapTemplate([\n '
                      ',\n _l( 'You are over your disk quota' ), '. ',\n _l( 'Tool execution is on hold until your disk usage drops below your allocated quota' ), '.',\n '
                      '\n ], 'history' );\n return _.extend( _.clone( _super.prototype.templates ), {\n quotaMsg : quotaMsgTemplate\n });\n\n}());\n\n\n//==============================================================================\n return {\n CurrentHistoryView : CurrentHistoryView\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/history/history-view-edit-current.js\n ** module id = 113\n ** module chunks = 3\n **/","define([\n \"mvc/history/history-view\",\n \"mvc/history/history-contents\",\n \"mvc/dataset/states\",\n \"mvc/history/hda-model\",\n \"mvc/history/hda-li-edit\",\n \"mvc/history/hdca-li-edit\",\n \"mvc/tag\",\n \"mvc/annotation\",\n \"mvc/collection/list-collection-creator\",\n \"mvc/collection/pair-collection-creator\",\n \"mvc/collection/list-of-pairs-collection-creator\",\n \"ui/fa-icon-button\",\n \"mvc/ui/popup-menu\",\n \"mvc/base-mvc\",\n \"utils/localization\",\n \"ui/editable-text\",\n], function(\n HISTORY_VIEW,\n HISTORY_CONTENTS,\n STATES,\n HDA_MODEL,\n HDA_LI_EDIT,\n HDCA_LI_EDIT,\n TAGS,\n ANNOTATIONS,\n LIST_COLLECTION_CREATOR,\n PAIR_COLLECTION_CREATOR,\n LIST_OF_PAIRS_COLLECTION_CREATOR,\n faIconButton,\n PopupMenu,\n BASE_MVC,\n _l\n){\n\n'use strict';\n\n/* =============================================================================\nTODO:\n\n============================================================================= */\nvar _super = HISTORY_VIEW.HistoryView;\n// base class for history-view-edit-current and used as-is in history/view.mako\n/** @class Editable View/Controller for the history model.\n *\n * Allows:\n * (everything HistoryView allows)\n * changing the name\n * displaying and editing tags and annotations\n * multi-selection and operations on mulitple content items\n */\nvar HistoryViewEdit = _super.extend(\n/** @lends HistoryViewEdit.prototype */{\n\n /** class to use for constructing the HistoryDatasetAssociation views */\n HDAViewClass : HDA_LI_EDIT.HDAListItemEdit,\n /** class to use for constructing the HistoryDatasetCollectionAssociation views */\n HDCAViewClass : HDCA_LI_EDIT.HDCAListItemEdit,\n\n // ......................................................................... SET UP\n /** Set up the view, set up storage, bind listeners to HistoryContents events\n * @param {Object} attributes\n */\n initialize : function( attributes ){\n attributes = attributes || {};\n _super.prototype.initialize.call( this, attributes );\n\n // ---- set up instance vars\n /** editor for tags - sub-view */\n this.tagsEditor = null;\n /** editor for annotations - sub-view */\n this.annotationEditor = null;\n\n /** allow user purge of dataset files? */\n this.purgeAllowed = attributes.purgeAllowed || false;\n\n // states/modes the panel can be in\n /** is the panel currently showing the dataset selection controls? */\n this.annotationEditorShown = attributes.annotationEditorShown || false;\n this.tagsEditorShown = attributes.tagsEditorShown || false;\n },\n\n /** Override to handle history as drag-drop target */\n _setUpListeners : function(){\n _super.prototype._setUpListeners.call( this );\n return this.on({\n 'droptarget:drop': function( ev, data ){\n // process whatever was dropped and re-hide the drop target\n this.dataDropped( data );\n this.dropTargetOff();\n },\n 'view:attached view:removed': function(){\n this._renderCounts();\n },\n 'search:loading-progress': this._renderSearchProgress,\n 'search:searching': this._renderSearchFindings,\n });\n },\n\n // ------------------------------------------------------------------------ listeners\n /** listening for history and HDA events */\n _setUpModelListeners : function(){\n _super.prototype._setUpModelListeners.call( this );\n this.listenTo( this.model, 'change:size', this.updateHistoryDiskSize );\n return this;\n },\n\n /** listening for collection events */\n _setUpCollectionListeners : function(){\n _super.prototype._setUpCollectionListeners.call( this );\n this.listenTo( this.collection, {\n 'change:deleted': this._handleItemDeletedChange,\n 'change:visible': this._handleItemVisibleChange,\n 'change:purged' : function( model ){\n // hafta get the new nice-size w/o the purged model\n this.model.fetch();\n },\n // loading indicators for deleted/hidden\n 'fetching-deleted' : function( collection ){\n this.$( '> .controls .deleted-count' )\n .html( '' + _l( 'loading...' ) + '' );\n },\n 'fetching-hidden' : function( collection ){\n this.$( '> .controls .hidden-count' )\n .html( '' + _l( 'loading...' ) + '' );\n },\n 'fetching-deleted-done fetching-hidden-done' : this._renderCounts,\n });\n return this;\n },\n\n // ------------------------------------------------------------------------ panel rendering\n /** In this override, add tag and annotation editors and a btn to toggle the selectors */\n _buildNewRender : function(){\n // create a new render using a skeleton template, render title buttons, render body, and set up events, etc.\n var $newRender = _super.prototype._buildNewRender.call( this );\n if( !this.model ){ return $newRender; }\n\n if( Galaxy && Galaxy.user && Galaxy.user.id && Galaxy.user.id === this.model.get( 'user_id' ) ){\n this._renderTags( $newRender );\n this._renderAnnotation( $newRender );\n }\n return $newRender;\n },\n\n /** Update the history size display (curr. upper right of panel). */\n updateHistoryDiskSize : function(){\n this.$( '.history-size' ).text( this.model.get( 'nice_size' ) );\n },\n\n /** override to render counts when the items are rendered */\n renderItems : function( $whereTo ){\n var views = _super.prototype.renderItems.call( this, $whereTo );\n if( !this.searchFor ){ this._renderCounts( $whereTo ); }\n return views;\n },\n\n /** override to show counts, what's deleted/hidden, and links to toggle those */\n _renderCounts : function( $whereTo ){\n $whereTo = $whereTo instanceof jQuery? $whereTo : this.$el;\n var html = this.templates.counts( this.model.toJSON(), this );\n return $whereTo.find( '> .controls .subtitle' ).html( html );\n },\n\n /** render the tags sub-view controller */\n _renderTags : function( $where ){\n var panel = this;\n this.tagsEditor = new TAGS.TagsEditor({\n model : this.model,\n el : $where.find( '.controls .tags-display' ),\n onshowFirstTime : function(){ this.render(); },\n // show hide sub-view tag editors when this is shown/hidden\n onshow : function(){\n panel.toggleHDATagEditors( true, panel.fxSpeed );\n },\n onhide : function(){\n panel.toggleHDATagEditors( false, panel.fxSpeed );\n },\n $activator : faIconButton({\n title : _l( 'Edit history tags' ),\n classes : 'history-tag-btn',\n faIcon : 'fa-tags'\n }).appendTo( $where.find( '.controls .actions' ) )\n });\n },\n /** render the annotation sub-view controller */\n _renderAnnotation : function( $where ){\n var panel = this;\n this.annotationEditor = new ANNOTATIONS.AnnotationEditor({\n model : this.model,\n el : $where.find( '.controls .annotation-display' ),\n onshowFirstTime : function(){ this.render(); },\n // show hide sub-view view annotation editors when this is shown/hidden\n onshow : function(){\n panel.toggleHDAAnnotationEditors( true, panel.fxSpeed );\n },\n onhide : function(){\n panel.toggleHDAAnnotationEditors( false, panel.fxSpeed );\n },\n $activator : faIconButton({\n title : _l( 'Edit history annotation' ),\n classes : 'history-annotate-btn',\n faIcon : 'fa-comment'\n }).appendTo( $where.find( '.controls .actions' ) )\n });\n },\n\n /** Set up HistoryViewEdit js/widget behaviours\n * In this override, make the name editable\n */\n _setUpBehaviors : function( $where ){\n $where = $where || this.$el;\n _super.prototype._setUpBehaviors.call( this, $where );\n if( !this.model ){ return; }\n\n // anon users shouldn't have access to any of the following\n if( ( !Galaxy.user || Galaxy.user.isAnonymous() )\n || ( Galaxy.user.id !== this.model.get( 'user_id' ) ) ){\n return;\n }\n\n var panel = this,\n nameSelector = '> .controls .name';\n $where.find( nameSelector )\n .attr( 'title', _l( 'Click to rename history' ) )\n .tooltip({ placement: 'bottom' })\n .make_text_editable({\n on_finish: function( newName ){\n var previousName = panel.model.get( 'name' );\n if( newName && newName !== previousName ){\n panel.$el.find( nameSelector ).text( newName );\n panel.model.save({ name: newName })\n .fail( function(){\n panel.$el.find( nameSelector ).text( panel.model.previous( 'name' ) );\n });\n } else {\n panel.$el.find( nameSelector ).text( previousName );\n }\n }\n });\n },\n\n /** return a new popup menu for choosing a multi selection action\n * ajax calls made for multiple datasets are queued\n */\n multiselectActions : function(){\n var panel = this,\n actions = [\n { html: _l( 'Hide datasets' ), func: function(){\n var action = HDA_MODEL.HistoryDatasetAssociation.prototype.hide;\n panel.getSelectedModels().ajaxQueue( action );\n }\n },\n { html: _l( 'Unhide datasets' ), func: function(){\n var action = HDA_MODEL.HistoryDatasetAssociation.prototype.unhide;\n panel.getSelectedModels().ajaxQueue( action );\n }\n },\n { html: _l( 'Delete datasets' ), func: function(){\n var action = HDA_MODEL.HistoryDatasetAssociation.prototype['delete'];\n panel.getSelectedModels().ajaxQueue( action );\n }\n },\n { html: _l( 'Undelete datasets' ), func: function(){\n var action = HDA_MODEL.HistoryDatasetAssociation.prototype.undelete;\n panel.getSelectedModels().ajaxQueue( action );\n }\n }\n ];\n if( panel.purgeAllowed ){\n actions.push({\n html: _l( 'Permanently delete datasets' ), func: function(){\n if( confirm( _l( 'This will permanently remove the data in your datasets. Are you sure?' ) ) ){\n var action = HDA_MODEL.HistoryDatasetAssociation.prototype.purge;\n panel.getSelectedModels().ajaxQueue( action );\n }\n }\n });\n }\n actions = actions.concat( panel._collectionActions() );\n return actions;\n },\n\n /** */\n _collectionActions : function(){\n var panel = this;\n return [\n { html: _l( 'Build Dataset List' ), func: function() {\n LIST_COLLECTION_CREATOR.createListCollection( panel.getSelectedModels() )\n .done( function(){ panel.model.refresh(); });\n }\n },\n // TODO: Only show quick pair if two things selected.\n { html: _l( 'Build Dataset Pair' ), func: function() {\n PAIR_COLLECTION_CREATOR.createPairCollection( panel.getSelectedModels() )\n .done( function(){ panel.model.refresh(); });\n }\n },\n { html: _l( 'Build List of Dataset Pairs' ), func: function() {\n LIST_OF_PAIRS_COLLECTION_CREATOR.createListOfPairsCollection( panel.getSelectedModels() )\n .done( function(){ panel.model.refresh(); });\n }\n },\n ];\n },\n\n // ------------------------------------------------------------------------ sub-views\n /** In this override, add purgeAllowed and whether tags/annotation editors should be shown */\n _getItemViewOptions : function( model ){\n var options = _super.prototype._getItemViewOptions.call( this, model );\n _.extend( options, {\n purgeAllowed : this.purgeAllowed,\n tagsEditorShown : ( this.tagsEditor && !this.tagsEditor.hidden ),\n annotationEditorShown : ( this.annotationEditor && !this.annotationEditor.hidden )\n });\n return options;\n },\n\n /** If this item is deleted and we're not showing deleted items, remove the view\n * @param {Model} the item model to check\n */\n _handleItemDeletedChange : function( itemModel ){\n if( itemModel.get( 'deleted' ) ){\n this._handleItemDeletion( itemModel );\n } else {\n this._handleItemUndeletion( itemModel );\n }\n this._renderCounts();\n },\n\n _handleItemDeletion : function( itemModel ){\n var contentsShown = this.model.get( 'contents_active' );\n contentsShown.deleted += 1;\n contentsShown.active -= 1;\n if( !this.model.contents.includeDeleted ){\n this.removeItemView( itemModel );\n }\n this.model.set( 'contents_active', contentsShown );\n },\n\n _handleItemUndeletion : function( itemModel ){\n var contentsShown = this.model.get( 'contents_active' );\n contentsShown.deleted -= 1;\n if( !this.model.contents.includeDeleted ){\n contentsShown.active -= 1;\n }\n this.model.set( 'contents_active', contentsShown );\n },\n\n /** If this item is hidden and we're not showing hidden items, remove the view\n * @param {Model} the item model to check\n */\n _handleItemVisibleChange : function( itemModel ){\n if( itemModel.hidden() ){\n this._handleItemHidden( itemModel );\n } else {\n this._handleItemUnhidden( itemModel );\n }\n this._renderCounts();\n },\n\n _handleItemHidden : function( itemModel ){\n var contentsShown = this.model.get( 'contents_active' );\n contentsShown.hidden += 1;\n contentsShown.active -= 1;\n if( !this.model.contents.includeHidden ){\n this.removeItemView( itemModel );\n }\n this.model.set( 'contents_active', contentsShown );\n },\n\n _handleItemUnhidden : function( itemModel ){\n var contentsShown = this.model.get( 'contents_active' );\n contentsShown.hidden -= 1;\n if( !this.model.contents.includeHidden ){\n contentsShown.active -= 1;\n }\n this.model.set( 'contents_active', contentsShown );\n },\n\n /** toggle the visibility of each content's tagsEditor applying all the args sent to this function */\n toggleHDATagEditors : function( showOrHide, speed ){\n _.each( this.views, function( view ){\n if( view.tagsEditor ){\n view.tagsEditor.toggle( showOrHide, speed );\n }\n });\n },\n\n /** toggle the visibility of each content's annotationEditor applying all the args sent to this function */\n toggleHDAAnnotationEditors : function( showOrHide, speed ){\n _.each( this.views, function( view ){\n if( view.annotationEditor ){\n view.annotationEditor.toggle( showOrHide, speed );\n }\n });\n },\n\n // ------------------------------------------------------------------------ panel events\n /** event map */\n events : _.extend( _.clone( _super.prototype.events ), {\n 'click .show-selectors-btn' : 'toggleSelectors',\n 'click .toggle-deleted-link' : function( ev ){ this.toggleShowDeleted(); },\n 'click .toggle-hidden-link' : function( ev ){ this.toggleShowHidden(); }\n }),\n\n // ------------------------------------------------------------------------ search\n _renderSearchProgress : function( limit, offset ){\n var stop = limit + offset;\n return this.$( '> .controls .subtitle' ).html([\n '',\n _l( 'Searching ' ), stop, '/', this.model.contentsShown(),\n ''\n ].join(''));\n },\n\n /** override to display number found in subtitle */\n _renderSearchFindings : function(){\n this.$( '> .controls .subtitle' ).html([\n _l( 'Found' ), this.views.length\n ].join(' '));\n return this;\n },\n\n // ------------------------------------------------------------------------ as drop target\n /** turn all the drag and drop handlers on and add some help text above the drop area */\n dropTargetOn : function(){\n if( this.dropTarget ){ return this; }\n this.dropTarget = true;\n\n //TODO: to init\n var dropHandlers = {\n 'dragenter' : _.bind( this.dragenter, this ),\n 'dragover' : _.bind( this.dragover, this ),\n 'dragleave' : _.bind( this.dragleave, this ),\n 'drop' : _.bind( this.drop, this )\n };\n\n var $dropTarget = this._renderDropTarget();\n this.$list().before([ this._renderDropTargetHelp(), $dropTarget ]);\n for( var evName in dropHandlers ){\n if( dropHandlers.hasOwnProperty( evName ) ){\n //console.debug( evName, dropHandlers[ evName ] );\n $dropTarget.on( evName, dropHandlers[ evName ] );\n }\n }\n return this;\n },\n\n /** render a box to serve as a 'drop here' area on the history */\n _renderDropTarget : function(){\n this.$( '.history-drop-target' ).remove();\n return $( '
                      ' ).addClass( 'history-drop-target' );\n },\n\n /** tell the user how it works */\n _renderDropTargetHelp : function(){\n this.$( '.history-drop-target-help' ).remove();\n return $( '
                      ' ).addClass( 'history-drop-target-help' )\n .text( _l( 'Drag datasets here to copy them to the current history' ) );\n },\n\n /** shut down drag and drop event handlers and remove drop target */\n dropTargetOff : function(){\n if( !this.dropTarget ){ return this; }\n //this.log( 'dropTargetOff' );\n this.dropTarget = false;\n var dropTarget = this.$( '.history-drop-target' ).get(0);\n for( var evName in this._dropHandlers ){\n if( this._dropHandlers.hasOwnProperty( evName ) ){\n dropTarget.off( evName, this._dropHandlers[ evName ] );\n }\n }\n this.$( '.history-drop-target' ).remove();\n this.$( '.history-drop-target-help' ).remove();\n return this;\n },\n /** toggle the target on/off */\n dropTargetToggle : function(){\n if( this.dropTarget ){\n this.dropTargetOff();\n } else {\n this.dropTargetOn();\n }\n return this;\n },\n\n dragenter : function( ev ){\n //console.debug( 'dragenter:', this, ev );\n ev.preventDefault();\n ev.stopPropagation();\n this.$( '.history-drop-target' ).css( 'border', '2px solid black' );\n },\n dragover : function( ev ){\n ev.preventDefault();\n ev.stopPropagation();\n },\n dragleave : function( ev ){\n //console.debug( 'dragleave:', this, ev );\n ev.preventDefault();\n ev.stopPropagation();\n this.$( '.history-drop-target' ).css( 'border', '1px dashed black' );\n },\n /** when (text) is dropped try to parse as json and trigger an event */\n drop : function( ev ){\n ev.preventDefault();\n //ev.stopPropagation();\n\n var self = this;\n var dataTransfer = ev.originalEvent.dataTransfer;\n var data = dataTransfer.getData( \"text\" );\n\n dataTransfer.dropEffect = 'move';\n try {\n data = JSON.parse( data );\n } catch( err ){\n self.warn( 'error parsing JSON from drop:', data );\n }\n\n self.trigger( 'droptarget:drop', ev, data, self );\n return false;\n },\n\n /** handler that copies data into the contents */\n dataDropped : function( data ){\n var self = this;\n // HDA: dropping will copy it to the history\n if( _.isObject( data ) && data.model_class === 'HistoryDatasetAssociation' && data.id ){\n if( self.contents.currentPage !== 0 ){\n return self.contents.fetchPage( 0 )\n .then( function(){\n return self.model.contents.copy( data.id );\n });\n }\n return self.model.contents.copy( data.id );\n }\n return jQuery.when();\n },\n\n // ........................................................................ misc\n /** Return a string rep of the history */\n toString : function(){\n return 'HistoryViewEdit(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';\n }\n});\n\n//------------------------------------------------------------------------------ TEMPLATES\nHistoryViewEdit.prototype.templates = (function(){\n\n var countsTemplate = BASE_MVC.wrapTemplate([\n '<% var shown = Math.max( view.views.length, history.contents_active.active ) %>',\n '<% if( shown ){ %>',\n '',\n '<%- shown %> ', _l( 'shown' ),\n '',\n '<% } %>',\n\n '<% if( history.contents_active.deleted ){ %>',\n '',\n '<% if( view.model.contents.includeDeleted ){ %>',\n '',\n _l( 'hide deleted' ),\n '',\n '<% } else { %>',\n '<%- history.contents_active.deleted %> ',\n '',\n _l( 'deleted' ),\n '',\n '<% } %>',\n '',\n '<% } %>',\n\n '<% if( history.contents_active.hidden ){ %>',\n '',\n '<% if( view.model.contents.includeHidden ){ %>',\n '',\n _l( 'hide hidden' ),\n '',\n '<% } else { %>',\n '<%- history.contents_active.hidden %> ',\n '',\n _l( 'hidden' ),\n '',\n '<% } %>',\n '',\n '<% } %>',\n ], 'history' );\n\n return _.extend( _.clone( _super.prototype.templates ), {\n counts : countsTemplate\n });\n}());\n\n\n//==============================================================================\n return {\n HistoryViewEdit : HistoryViewEdit\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/history/history-view-edit.js\n ** module id = 114\n ** module chunks = 3\n **/","define([\n \"mvc/list/list-view\",\n \"mvc/history/history-model\",\n \"mvc/history/history-contents\",\n \"mvc/history/history-preferences\",\n \"mvc/history/hda-li\",\n \"mvc/history/hdca-li\",\n \"mvc/user/user-model\",\n \"mvc/ui/error-modal\",\n \"ui/fa-icon-button\",\n \"mvc/base-mvc\",\n \"utils/localization\",\n \"ui/search-input\"\n], function(\n LIST_VIEW,\n HISTORY_MODEL,\n HISTORY_CONTENTS,\n HISTORY_PREFS,\n HDA_LI,\n HDCA_LI,\n USER,\n ERROR_MODAL,\n faIconButton,\n BASE_MVC,\n _l\n){\n'use strict';\n\n/* =============================================================================\nTODO:\n\n============================================================================= */\n/** @class non-editable, read-only View/Controller for a history model.\n * Allows:\n * changing the loaded history\n * displaying data, info, and download\n * tracking history attrs: size, tags, annotations, name, etc.\n * Does not allow:\n * changing the name\n */\nvar _super = LIST_VIEW.ModelListPanel;\nvar HistoryView = _super.extend(\n/** @lends HistoryView.prototype */{\n _logNamespace : 'history',\n\n /** class to use for constructing the HDA views */\n HDAViewClass : HDA_LI.HDAListItemView,\n /** class to use for constructing the HDCA views */\n HDCAViewClass : HDCA_LI.HDCAListItemView,\n /** class to used for constructing collection of sub-view models */\n collectionClass : HISTORY_CONTENTS.HistoryContents,\n /** key of attribute in model to assign to this.collection */\n modelCollectionKey : 'contents',\n\n tagName : 'div',\n className : _super.prototype.className + ' history-panel',\n\n /** string to display when the collection is empty */\n emptyMsg : _l( 'This history is empty' ),\n /** displayed when no items match the search terms */\n noneFoundMsg : _l( 'No matching datasets found' ),\n /** string used for search placeholder */\n searchPlaceholder : _l( 'search datasets' ),\n\n /** @type {Number} ms to wait after history load to fetch/decorate hdcas with element_count */\n FETCH_COLLECTION_COUNTS_DELAY : 2000,\n\n // ......................................................................... SET UP\n /** Set up the view, bind listeners.\n * @param {Object} attributes optional settings for the panel\n */\n initialize : function( attributes ){\n _super.prototype.initialize.call( this, attributes );\n // ---- instance vars\n // control contents/behavior based on where (and in what context) the panel is being used\n /** where should pages from links be displayed? (default to new tab/window) */\n this.linkTarget = attributes.linkTarget || '_blank';\n },\n\n /** create and return a collection for when none is initially passed */\n _createDefaultCollection : function(){\n // override\n return new this.collectionClass([], { history: this.model });\n },\n\n /** In this override, clear the update timer on the model */\n freeModel : function(){\n _super.prototype.freeModel.call( this );\n if( this.model ){\n this.model.clearUpdateTimeout();\n }\n return this;\n },\n\n /** create any event listeners for the panel\n * @fires: rendered:initial on the first render\n * @fires: empty-history when switching to a history with no contents or creating a new history\n */\n _setUpListeners : function(){\n _super.prototype._setUpListeners.call( this );\n this.on({\n error : function( model, xhr, options, msg, details ){\n this.errorHandler( model, xhr, options, msg, details );\n },\n 'loading-done' : function(){\n var self = this;\n // after the initial load, decorate with more time consuming fields (like HDCA element_counts)\n _.delay( function(){\n self.model.contents.fetchCollectionCounts();\n }, self.FETCH_COLLECTION_COUNTS_DELAY );\n },\n 'views:ready view:attached view:removed' : function( view ){\n this._renderSelectButton();\n },\n 'view:attached' : function( view ){\n this.scrollTo(0);\n },\n });\n // this.on( 'all', function(){ console.debug( arguments ); });\n },\n\n // ------------------------------------------------------------------------ loading history/hda models\n /** load the history with the given id then it's contents, sending ajax options to both */\n loadHistory : function( historyId, options, contentsOptions ){\n contentsOptions = _.extend( contentsOptions || { silent: true });\n this.info( 'loadHistory:', historyId, options, contentsOptions );\n var self = this;\n self.setModel( new HISTORY_MODEL.History({ id : historyId }) );\n\n contentsOptions.silent = true;\n self.trigger( 'loading' );\n return self.model\n .fetchWithContents( options, contentsOptions )\n .always( function(){\n self.render();\n self.trigger( 'loading-done' );\n });\n },\n\n /** convenience alias to the model. Updates the item list only (not the history) */\n refreshContents : function( options ){\n if( this.model ){\n return this.model.refresh( options );\n }\n // may have callbacks - so return an empty promise\n return $.when();\n },\n\n /** Override to reset web storage when the id changes (since it needs the id) */\n _setUpCollectionListeners : function(){\n _super.prototype._setUpCollectionListeners.call( this );\n return this.listenTo( this.collection, {\n // 'all' : function(){ console.log( this.collection + ':', arguments ); },\n 'fetching-more' : function(){\n this._toggleContentsLoadingIndicator( true );\n this.$emptyMessage().hide();\n },\n 'fetching-more-done': function(){ this._toggleContentsLoadingIndicator( false ); },\n });\n },\n\n // ------------------------------------------------------------------------ panel rendering\n /** hide the $el and display a loading indicator (in the $el's parent) when loading new data */\n _showLoadingIndicator : function( msg, speed, callback ){\n var $indicator = $( '
                      ' );\n this.$el.html( $indicator.text( msg ).slideDown( !_.isUndefined( speed )? speed : this.fxSpeed ) );\n },\n\n /** hide the loading indicator */\n _hideLoadingIndicator : function( speed ){\n // make speed a bit slower to compensate for slow rendering of up to 500 contents\n this.$( '.loading-indicator' ).slideUp( !_.isUndefined( speed )? speed : ( this.fxSpeed + 200 ), function(){\n $( this ).remove();\n });\n },\n\n /** In this override, add a btn to toggle the selectors */\n _buildNewRender : function(){\n var $newRender = _super.prototype._buildNewRender.call( this );\n this._renderSelectButton( $newRender );\n return $newRender;\n },\n\n /** button for starting select mode */\n _renderSelectButton : function( $where ){\n $where = $where || this.$el;\n // do not render selector option if no actions\n if( !this.multiselectActions().length ){\n return null;\n }\n // do not render (and remove even) if nothing to select\n if( !this.views.length ){\n this.hideSelectors();\n $where.find( '.controls .actions .show-selectors-btn' ).remove();\n return null;\n }\n // don't bother rendering if there's one already\n var $existing = $where.find( '.controls .actions .show-selectors-btn' );\n if( $existing.length ){\n return $existing;\n }\n\n return faIconButton({\n title : _l( 'Operations on multiple datasets' ),\n classes : 'show-selectors-btn',\n faIcon : 'fa-check-square-o'\n }).prependTo( $where.find( '.controls .actions' ) );\n },\n\n /** override to avoid showing intial empty message using contents_active */\n _renderEmptyMessage : function( $whereTo ){\n var self = this;\n var $emptyMsg = self.$emptyMessage( $whereTo );\n\n var empty = self.model.get( 'contents_active' ).active <= 0;\n if( empty ){\n return $emptyMsg.empty().append( self.emptyMsg ).show();\n\n } else if( self.searchFor && self.model.contents.haveSearchDetails() && !self.views.length ){\n return $emptyMsg.empty().append( self.noneFoundMsg ).show();\n }\n $emptyMsg.hide();\n return $();\n },\n\n /** the scroll container for this panel - can be $el, $el.parent(), or grandparent depending on context */\n $scrollContainer : function( $where ){\n // override or set via attributes.$scrollContainer\n return this.$list( $where );\n },\n\n // ------------------------------------------------------------------------ subviews\n _toggleContentsLoadingIndicator : function( show ){\n if( !show ){\n this.$list().find( '.contents-loading-indicator' ).remove();\n } else {\n this.$list().html( '
                      '\n + '
                      ' );\n }\n },\n\n /** override to render pagination also */\n renderItems: function( $whereTo ){\n // console.log( this + '.renderItems-----------------', new Date() );\n $whereTo = $whereTo || this.$el;\n var self = this;\n var $list = self.$list( $whereTo );\n\n // TODO: bootstrap hack to remove orphaned tooltips\n $( '.tooltip' ).remove();\n\n $list.empty();\n self.views = [];\n\n var models = self._filterCollection();\n if( models.length ){\n self._renderPagination( $whereTo );\n self.views = self._renderSomeItems( models, $list );\n } else {\n // TODO: consolidate with _renderPagination above by (???) passing in models/length?\n $whereTo.find( '> .controls .list-pagination' ).empty();\n }\n self._renderEmptyMessage( $whereTo ).toggle( !models.length );\n\n self.trigger( 'views:ready', self.views );\n return self.views;\n },\n\n /** render pagination controls if not searching and contents says we're paginating */\n _renderPagination: function( $whereTo ){\n var $paginationControls = $whereTo.find( '> .controls .list-pagination' );\n if( this.searchFor || !this.model.contents.shouldPaginate() ) return $paginationControls.empty();\n\n $paginationControls.html( this.templates.pagination({\n // pagination is 1-based for the user\n current : this.model.contents.currentPage + 1,\n last : this.model.contents.getLastPage() + 1,\n }, this ));\n $paginationControls.find( 'select.pages' ).tooltip();\n return $paginationControls;\n },\n\n /** render a subset of the entire collection (client-side pagination) */\n _renderSomeItems: function( models, $list ){\n var self = this;\n var views = [];\n $list.append( models.map( function( m ){\n var view = self._createItemView( m );\n views.push( view );\n return self._renderItemView$el( view );\n }));\n return views;\n },\n\n // ------------------------------------------------------------------------ sub-views\n /** in this override, check if the contents would also display based on includeDeleted/hidden */\n _filterItem : function( model ){\n var self = this;\n var contents = self.model.contents;\n return ( contents.includeHidden || !model.hidden() )\n && ( contents.includeDeleted || !model.isDeletedOrPurged() )\n && ( _super.prototype._filterItem.call( self, model ) );\n },\n\n /** In this override, since history contents are mixed,\n * get the appropo view class based on history_content_type\n */\n _getItemViewClass : function( model ){\n var contentType = model.get( \"history_content_type\" );\n switch( contentType ){\n case 'dataset':\n return this.HDAViewClass;\n case 'dataset_collection':\n return this.HDCAViewClass;\n }\n throw new TypeError( 'Unknown history_content_type: ' + contentType );\n },\n\n /** in this override, add a linktarget, and expand if id is in web storage */\n _getItemViewOptions : function( model ){\n var options = _super.prototype._getItemViewOptions.call( this, model );\n return _.extend( options, {\n linkTarget : this.linkTarget,\n expanded : this.model.contents.storage.isExpanded( model.id ),\n hasUser : this.model.ownedByCurrUser()\n });\n },\n\n /** In this override, add/remove expanded/collapsed model ids to/from web storage */\n _setUpItemViewListeners : function( view ){\n var panel = this;\n _super.prototype._setUpItemViewListeners.call( panel, view );\n //TODO: send from content view: this.model.collection.storage.addExpanded\n // maintain a list of items whose bodies are expanded\n return panel.listenTo( view, {\n 'expanded': function( v ){\n panel.model.contents.storage.addExpanded( v.model );\n },\n 'collapsed': function( v ){\n panel.model.contents.storage.removeExpanded( v.model );\n }\n });\n },\n\n /** override to remove expandedIds from webstorage */\n collapseAll : function(){\n this.model.contents.storage.clearExpanded();\n _super.prototype.collapseAll.call( this );\n },\n\n // ------------------------------------------------------------------------ selection\n /** Override to correctly set the historyId of the new collection */\n getSelectedModels : function(){\n var collection = _super.prototype.getSelectedModels.call( this );\n collection.historyId = this.collection.historyId;\n return collection;\n },\n\n\n // ------------------------------------------------------------------------ panel events\n /** event map */\n events : _.extend( _.clone( _super.prototype.events ), {\n 'click .show-selectors-btn' : 'toggleSelectors',\n 'click > .controls .prev' : '_clickPrevPage',\n 'click > .controls .next' : '_clickNextPage',\n 'change > .controls .pages' : '_changePageSelect',\n // allow (error) messages to be clicked away\n 'click .messages [class$=message]' : 'clearMessages',\n }),\n\n _clickPrevPage : function( ev ){\n this.model.contents.fetchPrevPage();\n },\n\n _clickNextPage : function( ev ){\n this.model.contents.fetchNextPage();\n },\n\n _changePageSelect : function( ev ){\n var page = $( ev.currentTarget ).val();\n this.model.contents.fetchPage( page );\n },\n\n /** Toggle and store the deleted visibility and re-render items\n * @returns {Boolean} new setting\n */\n toggleShowDeleted : function( show, options ){\n show = ( show !== undefined )?( show ):( !this.model.contents.includeDeleted );\n var self = this;\n var contents = self.model.contents;\n contents.setIncludeDeleted( show, options );\n self.trigger( 'show-deleted', show );\n\n contents.fetchCurrentPage({ renderAll: true });\n return show;\n },\n\n /** Toggle and store whether to render explicity hidden contents\n * @returns {Boolean} new setting\n */\n toggleShowHidden : function( show, store, options ){\n // console.log( 'toggleShowHidden', show, store );\n show = ( show !== undefined )?( show ):( !this.model.contents.includeHidden );\n var self = this;\n var contents = self.model.contents;\n contents.setIncludeHidden( show, options );\n self.trigger( 'show-hidden', show );\n\n contents.fetchCurrentPage({ renderAll: true });\n return show;\n },\n\n /** On the first search, if there are no details - load them, then search */\n _firstSearch : function( searchFor ){\n var self = this;\n var inputSelector = '> .controls .search-input';\n this.log( 'onFirstSearch', searchFor );\n\n // if the contents already have enough details to search, search and return now\n if( self.model.contents.haveSearchDetails() ){\n self.searchItems( searchFor );\n return;\n }\n\n // otherwise, load the details progressively here\n self.$( inputSelector ).searchInput( 'toggle-loading' );\n // set this now so that only results will show during progress\n self.searchFor = searchFor;\n var xhr = self.model.contents.progressivelyFetchDetails({ silent: true })\n .progress( function( response, limit, offset ){\n self.renderItems();\n self.trigger( 'search:loading-progress', limit, offset );\n })\n .always( function(){\n self.$el.find( inputSelector ).searchInput( 'toggle-loading' );\n })\n .done( function(){\n self.searchItems( searchFor, 'force' );\n });\n },\n\n /** clear the search filters and show all views that are normally shown */\n clearSearch : function( searchFor ){\n var self = this;\n if( !self.searchFor ) return self;\n //self.log( 'onSearchClear', self );\n self.searchFor = '';\n self.trigger( 'search:clear', self );\n self.$( '> .controls .search-query' ).val( '' );\n // NOTE: silent + render prevents collection update event with merge only\n // - which causes an empty page due to event handler above\n self.model.contents.fetchCurrentPage({ silent: true })\n .done( function(){\n self.renderItems();\n });\n return self;\n },\n\n // ........................................................................ error handling\n /** Event handler for errors (from the panel, the history, or the history's contents)\n * Alternately use two strings for model and xhr to use custom message and title (respectively)\n * @param {Model or View} model the (Backbone) source of the error\n * @param {XMLHTTPRequest} xhr any ajax obj. assoc. with the error\n * @param {Object} options the options map commonly used with bbone ajax\n */\n errorHandler : function( model, xhr, options ){\n //TODO: to mixin or base model\n // interrupted ajax or no connection\n if( xhr && xhr.status === 0 && xhr.readyState === 0 ){\n // return ERROR_MODAL.offlineErrorModal();\n // fail silently\n return;\n }\n // otherwise, leave something to report in the console\n this.error( model, xhr, options );\n // and feedback to a modal\n // if sent two strings (and possibly details as 'options'), use those as message and title\n if( _.isString( model ) && _.isString( xhr ) ){\n var message = model;\n var title = xhr;\n return ERROR_MODAL.errorModal( message, title, options );\n }\n // bad gateway\n // TODO: possibly to global handler\n if( xhr && xhr.status === 502 ){\n return ERROR_MODAL.badGatewayErrorModal();\n }\n return ERROR_MODAL.ajaxErrorModal( model, xhr, options );\n },\n\n /** Remove all messages from the panel. */\n clearMessages : function( ev ){\n var $target = !_.isUndefined( ev )?\n $( ev.currentTarget )\n :this.$messages().children( '[class$=\"message\"]' );\n $target.fadeOut( this.fxSpeed, function(){\n $( this ).remove();\n });\n return this;\n },\n\n // ........................................................................ scrolling\n /** Scrolls the panel to show the content sub-view with the given hid.\n * @param {Integer} hid the hid of item to scroll into view\n * @returns {HistoryView} the panel\n */\n scrollToHid : function( hid ){\n return this.scrollToItem( _.first( this.viewsWhereModel({ hid: hid }) ) );\n },\n\n // ........................................................................ misc\n /** utility for adding -st, -nd, -rd, -th to numbers */\n ordinalIndicator : function( number ){\n var numStr = number + '';\n switch( numStr.charAt( numStr.length - 1 )){\n case '1': return numStr + 'st';\n case '2': return numStr + 'nd';\n case '3': return numStr + 'rd';\n default : return numStr + 'th';\n }\n },\n\n /** Return a string rep of the history */\n toString : function(){\n return 'HistoryView(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';\n }\n});\n\n\n//------------------------------------------------------------------------------ TEMPLATES\nHistoryView.prototype.templates = (function(){\n\n var mainTemplate = BASE_MVC.wrapTemplate([\n // temp container\n '
                      ',\n '
                      ',\n '
                        ',\n '
                        ',\n '
                        '\n ]);\n\n var controlsTemplate = BASE_MVC.wrapTemplate([\n '
                        ',\n '
                        ',\n '
                        <%- history.name %>
                        ',\n '
                        ',\n '
                        ',\n '
                        <%- history.nice_size %>
                        ',\n\n '
                        ',\n\n '
                        ',\n '<% if( history.deleted && history.purged ){ %>',\n '
                        ',\n _l( 'This history has been purged and deleted' ),\n '
                        ',\n '<% } else if( history.deleted ){ %>',\n '
                        ',\n _l( 'This history has been deleted' ),\n '
                        ',\n '<% } else if( history.purged ){ %>',\n '
                        ',\n _l( 'This history has been purged' ),\n '
                        ',\n '<% } %>',\n\n '<% if( history.message ){ %>',\n // should already be localized\n '
                        messagesmall\">',\n '<%= history.message.text %>',\n '
                        ',\n '<% } %>',\n '
                        ',\n\n // add tags and annotations\n '
                        ',\n '
                        ',\n\n '
                        ',\n '
                        ',\n '
                        ',\n\n '
                        ',\n '
                        ',\n '',\n '',\n '
                        ',\n '
                        ',\n '
                        ',\n '
                        ',\n '
                        ',\n '
                        '\n ], 'history' );\n\n var paginationTemplate = BASE_MVC.wrapTemplate([\n '',\n '',\n '',\n ], 'pages' );\n\n return _.extend( _.clone( _super.prototype.templates ), {\n el : mainTemplate,\n controls : controlsTemplate,\n pagination : paginationTemplate,\n });\n}());\n\n\n//==============================================================================\n return {\n HistoryView: HistoryView\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/history/history-view.js\n ** module id = 115\n ** module chunks = 3\n **/","define([\n \"mvc/ui/popup-menu\",\n \"mvc/history/copy-dialog\",\n \"mvc/base-mvc\",\n \"utils/localization\"\n], function( PopupMenu, historyCopyDialog, BASE_MVC, _l ){\n\n'use strict';\n\n// ============================================================================\nvar menu = [\n {\n html : _l( 'History Lists' ),\n header : true\n },\n {\n html : _l( 'Saved Histories' ),\n href : 'history/list',\n },\n {\n html : _l( 'Histories Shared with Me' ),\n href : 'history/list_shared'\n },\n\n {\n html : _l( 'History Actions' ),\n header : true,\n anon : true\n },\n {\n html : _l( 'Create New' ),\n func : function(){ Galaxy.currHistoryPanel.createNewHistory(); }\n },\n {\n html : _l( 'Copy History' ),\n func : function(){\n historyCopyDialog( Galaxy.currHistoryPanel.model )\n .done( function(){\n Galaxy.currHistoryPanel.loadCurrentHistory();\n });\n },\n },\n {\n html : _l( 'Share or Publish' ),\n href : 'history/sharing',\n },\n {\n html : _l( 'Show Structure' ),\n href : 'history/display_structured',\n anon : true,\n },\n {\n html : _l( 'Extract Workflow' ),\n href : 'workflow/build_from_current_history',\n },\n {\n html : _l( 'Delete' ),\n anon : true,\n func : function() {\n if( Galaxy && Galaxy.currHistoryPanel && confirm( _l( 'Really delete the current history?' ) ) ){\n galaxy_main.window.location.href = 'history/delete?id=' + Galaxy.currHistoryPanel.model.id;\n }\n },\n },\n {\n html : _l( 'Delete Permanently' ),\n purge : true,\n anon : true,\n func : function() {\n if( Galaxy && Galaxy.currHistoryPanel\n && confirm( _l( 'Really delete the current history permanently? This cannot be undone.' ) ) ){\n galaxy_main.window.location.href = 'history/delete?purge=True&id=' + Galaxy.currHistoryPanel.model.id;\n }\n },\n },\n\n\n {\n html : _l( 'Dataset Actions' ),\n header : true,\n anon : true\n },\n {\n html : _l( 'Copy Datasets' ),\n href : 'dataset/copy_datasets',\n },\n {\n html : _l( 'Dataset Security' ),\n href : 'root/history_set_default_permissions',\n },\n {\n html : _l( 'Resume Paused Jobs' ),\n href : 'history/resume_paused_jobs?current=True',\n anon : true,\n },\n {\n html : _l( 'Collapse Expanded Datasets' ),\n func : function(){ Galaxy.currHistoryPanel.collapseAll(); }\n },\n {\n html : _l( 'Unhide Hidden Datasets' ),\n anon : true,\n func : function(){ Galaxy.currHistoryPanel.unhideHidden(); }\n },\n {\n html : _l( 'Delete Hidden Datasets' ),\n anon : true,\n func : function(){ Galaxy.currHistoryPanel.deleteHidden(); }\n },\n {\n html : _l( 'Purge Deleted Datasets' ),\n confirm : _l( 'Really delete all deleted datasets permanently? This cannot be undone.' ),\n href : 'history/purge_deleted_datasets',\n purge : true,\n anon : true,\n },\n\n {\n html : _l( 'Downloads' ),\n header : true\n },\n {\n html : _l( 'Export Tool Citations' ),\n href : 'history/citations',\n anon : true,\n },\n {\n html : _l( 'Export History to File' ),\n href : 'history/export_archive?preview=True',\n anon : true,\n },\n\n {\n html : _l( 'Other Actions' ),\n header : true\n },\n {\n html : _l( 'Import from File' ),\n href : 'history/import_archive',\n }\n];\n\nfunction buildMenu( isAnon, purgeAllowed, urlRoot ){\n return _.clone( menu ).filter( function( menuOption ){\n if( isAnon && !menuOption.anon ){\n return false;\n }\n if( !purgeAllowed && menuOption.purge ){\n return false;\n }\n\n //TODO:?? hard-coded galaxy_main\n if( menuOption.href ){\n menuOption.href = urlRoot + menuOption.href;\n menuOption.target = 'galaxy_main';\n }\n\n if( menuOption.confirm ){\n menuOption.func = function(){\n if( confirm( menuOption.confirm ) ){\n galaxy_main.location = menuOption.href;\n }\n };\n }\n return true;\n });\n}\n\nvar create = function( $button, options ){\n options = options || {};\n var isAnon = options.anonymous === undefined? true : options.anonymous,\n purgeAllowed = options.purgeAllowed || false,\n menu = buildMenu( isAnon, purgeAllowed, Galaxy.root );\n //console.debug( 'menu:', menu );\n return new PopupMenu( $button, menu );\n};\n\n\n// ============================================================================\n return create;\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/history/options-menu.js\n ** module id = 116\n ** module chunks = 3\n **/","/**\n * Renders tabs e.g. used in the Charts editor, behaves similar to repeat and section rendering\n */\ndefine( [ 'utils/utils' ], function( Utils ) {\nvar View = Backbone.View.extend({\n initialize : function( options ) {\n var self = this;\n this.visible = false;\n this.$nav = null;\n this.$content = null;\n this.first_tab = null;\n this.current_id = null;\n this.list = {};\n this.options = Utils.merge( options, {\n title_new : '',\n operations : null,\n onnew : null,\n max : null,\n onchange : null\n });\n this.setElement( $( this._template( this.options ) ) );\n this.$nav = this.$( '.tab-navigation' );\n this.$content = this.$( '.tab-content' );\n this.$operations = this.$nav.find( '.tab-operations' );\n\n // Renders tab operations\n if ( this.options.operations ) {\n $.each( this.options.operations, function( name, item ) {\n item.$el.prop( 'id', name );\n self.$operations.append( item.$el );\n });\n }\n\n // Allows user to add new tabs\n this.options.onnew && this.$nav.append( $( this._template_tab_new( this.options ) )\n .tooltip( { title: 'Add a new tab', placement: 'bottom', container: self.$el } )\n .on( 'click', function( e ) { self.options.onnew() } )\n );\n this.$tabnew = this.$nav.find( '.tab-new' );\n\n // Remove all tooltips on click\n this.$el.on( 'click', function() { $( '.tooltip' ).hide() } );\n },\n\n /** Returns current number of tabs */\n size: function() {\n return _.size( this.list );\n },\n\n /** Returns tab id for currently shown tab */\n current: function() {\n return this.$el.find( '.tab-pane.active' ).attr( 'id' );\n },\n\n /** Adds a new tab */\n add: function( options ) {\n var self = this;\n var id = options.id;\n var $tab_title = $( this._template_tab( options ) );\n var $tab_content = $( '
                        ' ).attr( 'id', options.id ).addClass( 'tab-pane' );\n\n // hide new tab if maximum number of tabs has been reached\n this.list[ id ] = true;\n if ( this.options.max && this.size() >= this.options.max ) {\n this.$tabnew.hide();\n }\n\n // insert tab before new tab or as last tab\n if ( this.options.onnew ) {\n this.$tabnew.before( $tab_title );\n } else {\n this.$nav.append( $tab_title );\n }\n\n // assing delete callback if provided\n if ( options.ondel ) {\n $tab_title.find( '.tab-delete' ).tooltip( { title: 'Delete this tab', placement: 'bottom', container: self.$el } )\n .on( 'click', function() { options.ondel() } );\n } else {\n $tab_title.tooltip( { title: options.tooltip, placement: 'bottom', container: self.$el } );\n }\n $tab_title.on( 'click', function( e ) {\n e.preventDefault();\n options.onclick ? options.onclick() : self.show( id );\n });\n this.$content.append( $tab_content.append( options.$el ) );\n\n // assign current/first tab\n if ( this.size() == 1 ) {\n $tab_title.addClass( 'active' );\n $tab_content.addClass( 'active' );\n this.first_tab = id;\n }\n if ( !this.current_id ) {\n this.current_id = id;\n }\n },\n\n /** Delete tab */\n del: function( id ) {\n this.$( '#tab-' + id ).remove();\n this.$( '#' + id ).remove();\n this.first_tab = this.first_tab == id ? null : this.first_tab;\n this.first_tab != null && this.show( this.first_tab );\n this.list[ id ] && delete this.list[ id ];\n if ( this.size() < this.options.max ) {\n this.$el.find( '.ui-tabs-new' ).show();\n }\n },\n\n /** Delete all tabs */\n delAll: function() {\n for ( var id in this.list ) {\n this.del( id );\n }\n },\n\n /** Show tab view and highlight a tab by id */\n show: function( id ){\n this.$el.fadeIn( 'fast' );\n this.visible = true;\n if ( id ) {\n this.$( '#tab-' + this.current_id ).removeClass('active' );\n this.$( '#' + this.current_id ).removeClass('active' );\n this.$( '#tab-' + id ).addClass( 'active' );\n this.$( '#' + id ).addClass( 'active' );\n this.current_id = id;\n }\n this.options.onchange && this.options.onchange( id );\n },\n \n /** Hide tab view */\n hide: function(){\n this.$el.fadeOut( 'fast' );\n this.visible = false;\n },\n\n /** Show tab */\n showTab: function( id ) {\n this.$( '#tab-' + id ).show();\n },\n\n /** hide tab */\n hideTab: function( id ) {\n this.$( '#tab-' + id ).hide();\n },\n\n /** Hide operation by id */\n hideOperation: function( id ) {\n this.$nav.find( '#' + id ).hide();\n },\n\n /** Show operation by id */\n showOperation: function( id ) {\n this.$nav.find( '#' + id ).show();\n },\n\n /** Reassign an operation to a new callback */\n setOperation: function( id, callback ) {\n this.$nav.find( '#' + id ).off('click').on( 'click', callback );\n },\n\n /** Set/Get title */\n title: function( id, new_title ) {\n var $el = this.$( '#tab-title-text-' + id );\n new_title && $el.html( new_title );\n return $el.html();\n },\n\n /** Enumerate titles */\n retitle: function( new_title ) {\n var index = 0;\n for ( var id in this.list ) {\n this.title( id, ++index + ': ' + new_title );\n }\n },\n\n /** Main template */\n _template: function( options ) {\n return $( '
                        ' ).addClass( 'ui-tabs tabbable tabs-left' )\n .append( $( '
                      • ' +\n '
                        ' +\n '
                        ' +\n '
                        ' +\n '
                        You can tell Galaxy to download data from web by entering URL in this box (one per line). You can also directly paste the contents of a file.
                        ' +\n '',\n\t '
                        ',\n\t '
                        '\n\t ].join( '' );\n\t }\n\t});\n\t\n\t//==============================================================================\n\treturn {\n\t CitationView : CitationView,\n\t CitationListView : CitationListView\n\t};\n\t\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3)))\n\n/***/ },\n/* 29 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(_, $, jQuery) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(42),\n\t __webpack_require__(32),\n\t __webpack_require__(6),\n\t __webpack_require__(5)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( LIST_ITEM, DATASET_LI, BASE_MVC, _l ){\n\t\n\t'use strict';\n\t//==============================================================================\n\tvar FoldoutListItemView = LIST_ITEM.FoldoutListItemView,\n\t ListItemView = LIST_ITEM.ListItemView;\n\t/** @class Read only view for DatasetCollection.\n\t */\n\tvar DCListItemView = FoldoutListItemView.extend(\n\t/** @lends DCListItemView.prototype */{\n\t\n\t className : FoldoutListItemView.prototype.className + \" dataset-collection\",\n\t id : function(){\n\t return [ 'dataset_collection', this.model.get( 'id' ) ].join( '-' );\n\t },\n\t\n\t /** override to add linkTarget */\n\t initialize : function( attributes ){\n\t this.linkTarget = attributes.linkTarget || '_blank';\n\t this.hasUser = attributes.hasUser;\n\t FoldoutListItemView.prototype.initialize.call( this, attributes );\n\t },\n\t\n\t /** event listeners */\n\t _setUpListeners : function(){\n\t FoldoutListItemView.prototype._setUpListeners.call( this );\n\t this.listenTo( this.model, 'change', function( model, options ){\n\t // if the model has changed deletion status render it entirely\n\t if( _.has( model.changed, 'deleted' ) ){\n\t this.render();\n\t\n\t // if the model has been decorated after the fact with the element count,\n\t // render the subtitle where the count is displayed\n\t } else if( _.has( model.changed, 'element_count' ) ){\n\t this.$( '> .title-bar .subtitle' ).replaceWith( this._renderSubtitle() );\n\t }\n\t });\n\t },\n\t\n\t // ......................................................................... rendering\n\t /** render a subtitle to show the user what sort of collection this is */\n\t _renderSubtitle : function(){\n\t return $( this.templates.subtitle( this.model.toJSON(), this ) );\n\t },\n\t\n\t // ......................................................................... foldout\n\t /** override to add linktarget to sub-panel */\n\t _getFoldoutPanelOptions : function(){\n\t var options = FoldoutListItemView.prototype._getFoldoutPanelOptions.call( this );\n\t return _.extend( options, {\n\t linkTarget : this.linkTarget,\n\t hasUser : this.hasUser\n\t });\n\t },\n\t\n\t /** override to not catch sub-panel selectors */\n\t $selector : function(){\n\t return this.$( '> .selector' );\n\t },\n\t\n\t // ......................................................................... misc\n\t /** String representation */\n\t toString : function(){\n\t var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n\t return 'DCListItemView(' + modelString + ')';\n\t }\n\t});\n\t\n\t// ............................................................................ TEMPLATES\n\t/** underscore templates */\n\tDCListItemView.prototype.templates = (function(){\n\t\n\t var warnings = _.extend( {}, FoldoutListItemView.prototype.templates.warnings, {\n\t error : BASE_MVC.wrapTemplate([\n\t // error during index fetch - show error on dataset\n\t '<% if( model.error ){ %>',\n\t '
                        ',\n\t _l( 'There was an error getting the data for this collection' ), ': <%- model.error %>',\n\t '
                        ',\n\t '<% } %>'\n\t ]),\n\t purged : BASE_MVC.wrapTemplate([\n\t '<% if( model.purged ){ %>',\n\t '
                        ',\n\t _l( 'This collection has been deleted and removed from disk' ),\n\t '
                        ',\n\t '<% } %>'\n\t ]),\n\t deleted : BASE_MVC.wrapTemplate([\n\t // deleted not purged\n\t '<% if( model.deleted && !model.purged ){ %>',\n\t '
                        ',\n\t _l( 'This collection has been deleted' ),\n\t '
                        ',\n\t '<% } %>'\n\t ])\n\t });\n\t\n\t // use element identifier\n\t var titleBarTemplate = BASE_MVC.wrapTemplate([\n\t '
                        ',\n\t '
                        ',\n\t '<%- collection.element_identifier || collection.name %>',\n\t '
                        ',\n\t '
                        ',\n\t '
                        '\n\t ], 'collection' );\n\t\n\t // use element identifier\n\t var subtitleTemplate = BASE_MVC.wrapTemplate([\n\t '
                        ',\n\t '<% var countText = collection.element_count? ( collection.element_count + \" \" ) : \"\"; %>',\n\t '<% if( collection.collection_type === \"list\" ){ %>',\n\t _l( 'a list of <%- countText %>datasets' ),\n\t '<% } else if( collection.collection_type === \"paired\" ){ %>',\n\t _l( 'a pair of datasets' ),\n\t '<% } else if( collection.collection_type === \"list:paired\" ){ %>',\n\t _l( 'a list of <%- countText %>dataset pairs' ),\n\t '<% } else if( collection.collection_type === \"list:list\" ){ %>',\n\t _l( 'a list of <%- countText %>dataset lists' ),\n\t '<% } %>',\n\t '
                        '\n\t ], 'collection' );\n\t\n\t return _.extend( {}, FoldoutListItemView.prototype.templates, {\n\t warnings : warnings,\n\t titleBar : titleBarTemplate,\n\t subtitle : subtitleTemplate\n\t });\n\t}());\n\t\n\t\n\t//==============================================================================\n\t/** @class Read only view for DatasetCollectionElement.\n\t */\n\tvar DCEListItemView = ListItemView.extend(\n\t/** @lends DCEListItemView.prototype */{\n\t\n\t /** add the DCE class to the list item */\n\t className : ListItemView.prototype.className + \" dataset-collection-element\",\n\t\n\t /** set up */\n\t initialize : function( attributes ){\n\t if( attributes.logger ){ this.logger = this.model.logger = attributes.logger; }\n\t this.log( 'DCEListItemView.initialize:', attributes );\n\t ListItemView.prototype.initialize.call( this, attributes );\n\t },\n\t\n\t // ......................................................................... misc\n\t /** String representation */\n\t toString : function(){\n\t var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n\t return 'DCEListItemView(' + modelString + ')';\n\t }\n\t});\n\t\n\t// ............................................................................ TEMPLATES\n\t/** underscore templates */\n\tDCEListItemView.prototype.templates = (function(){\n\t\n\t // use the element identifier here - since that will persist and the user will need it\n\t var titleBarTemplate = BASE_MVC.wrapTemplate([\n\t '
                        ',\n\t '
                        ',\n\t '<%- element.element_identifier %>',\n\t '
                        ',\n\t '
                        ',\n\t '
                        '\n\t ], 'element' );\n\t\n\t return _.extend( {}, ListItemView.prototype.templates, {\n\t titleBar : titleBarTemplate\n\t });\n\t}());\n\t\n\t\n\t//==============================================================================\n\t/** @class Read only view for a DatasetCollectionElement that is also an DatasetAssociation\n\t * (a dataset contained in a dataset collection).\n\t */\n\tvar DatasetDCEListItemView = DATASET_LI.DatasetListItemView.extend(\n\t/** @lends DatasetDCEListItemView.prototype */{\n\t\n\t className : DATASET_LI.DatasetListItemView.prototype.className + \" dataset-collection-element\",\n\t\n\t /** set up */\n\t initialize : function( attributes ){\n\t if( attributes.logger ){ this.logger = this.model.logger = attributes.logger; }\n\t this.log( 'DatasetDCEListItemView.initialize:', attributes );\n\t DATASET_LI.DatasetListItemView.prototype.initialize.call( this, attributes );\n\t },\n\t\n\t /** In this override, only get details if in the ready state.\n\t * Note: fetch with no 'change' event triggering to prevent automatic rendering.\n\t */\n\t _fetchModelDetails : function(){\n\t var view = this;\n\t if( view.model.inReadyState() && !view.model.hasDetails() ){\n\t return view.model.fetch({ silent: true });\n\t }\n\t return jQuery.when();\n\t },\n\t\n\t // ......................................................................... misc\n\t /** String representation */\n\t toString : function(){\n\t var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n\t return 'DatasetDCEListItemView(' + modelString + ')';\n\t }\n\t});\n\t\n\t// ............................................................................ TEMPLATES\n\t/** underscore templates */\n\tDatasetDCEListItemView.prototype.templates = (function(){\n\t\n\t // use the element identifier here and not the dataset name\n\t //TODO:?? can we steal the DCE titlebar?\n\t var titleBarTemplate = BASE_MVC.wrapTemplate([\n\t '
                        ',\n\t '',\n\t '
                        ',\n\t '<%- element.element_identifier %>',\n\t '
                        ',\n\t '
                        '\n\t ], 'element' );\n\t\n\t return _.extend( {}, DATASET_LI.DatasetListItemView.prototype.templates, {\n\t titleBar : titleBarTemplate\n\t });\n\t}());\n\t\n\t\n\t//==============================================================================\n\t/** @class Read only view for a DatasetCollectionElement that is also a DatasetCollection\n\t * (a nested DC).\n\t */\n\tvar NestedDCDCEListItemView = DCListItemView.extend(\n\t/** @lends NestedDCDCEListItemView.prototype */{\n\t\n\t className : DCListItemView.prototype.className + \" dataset-collection-element\",\n\t\n\t /** In this override, add the state as a class for use with state-based CSS */\n\t _swapNewRender : function( $newRender ){\n\t DCListItemView.prototype._swapNewRender.call( this, $newRender );\n\t var state = this.model.get( 'state' ) || 'ok';\n\t this.$el.addClass( 'state-' + state );\n\t return this.$el;\n\t },\n\t\n\t // ......................................................................... misc\n\t /** String representation */\n\t toString : function(){\n\t var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n\t return 'NestedDCDCEListItemView(' + modelString + ')';\n\t }\n\t});\n\t\n\t\n\t//==============================================================================\n\t return {\n\t DCListItemView : DCListItemView,\n\t DCEListItemView : DCEListItemView,\n\t DatasetDCEListItemView : DatasetDCEListItemView,\n\t NestedDCDCEListItemView : NestedDCDCEListItemView\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(2), __webpack_require__(1), __webpack_require__(1)))\n\n/***/ },\n/* 30 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(_, Backbone, jQuery) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(70),\n\t __webpack_require__(6),\n\t __webpack_require__(5)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( DATASET_MODEL, BASE_MVC, _l ){\n\t'use strict';\n\t\n\t//==============================================================================\n\t/*\n\tNotes:\n\t\n\tTerminology:\n\t DatasetCollection/DC : a container of datasets or nested DatasetCollections\n\t Element/DatasetCollectionElement/DCE : an item contained in a DatasetCollection\n\t HistoryDatasetCollectionAssociation/HDCA: a DatasetCollection contained in a history\n\t\n\t\n\tThis all seems too complex unfortunately:\n\t\n\t- Terminology collision between DatasetCollections (DCs) and Backbone Collections.\n\t- In the DatasetCollections API JSON, DC Elements use a 'Has A' stucture to *contain*\n\t either a dataset or a nested DC. This would make the hierarchy much taller. I've\n\t decided to merge the contained JSON with the DC element json - making the 'has a'\n\t relation into an 'is a' relation. This seems simpler to me and allowed a lot of\n\t DRY in both models and views, but may make tracking or tracing within these models\n\t more difficult (since DatasetCollectionElements are now *also* DatasetAssociations\n\t or DatasetCollections (nested)). This also violates the rule of thumb about\n\t favoring aggregation over inheritance.\n\t- Currently, there are three DatasetCollection subclasses: List, Pair, and ListPaired.\n\t These each should a) be usable on their own, b) be usable in the context of\n\t nesting within a collection model (at least in the case of ListPaired), and\n\t c) be usable within the context of other container models (like History or\n\t LibraryFolder, etc.). I've tried to separate/extract classes in order to\n\t handle those three situations, but it's proven difficult to do in a simple,\n\t readable manner.\n\t- Ideally, histories and libraries would inherit from the same server models as\n\t dataset collections do since they are (in essence) dataset collections themselves -\n\t making the whole nested structure simpler. This would be a large, error-prone\n\t refactoring and migration.\n\t\n\tMany of the classes and heirarchy are meant as extension points so, while the\n\trelations and flow may be difficult to understand initially, they'll allow us to\n\thandle the growth or flux dataset collection in the future (w/o actually implementing\n\tany YAGNI).\n\t\n\t*/\n\t//_________________________________________________________________________________________________ ELEMENTS\n\t/** @class mixin for Dataset collection elements.\n\t * When collection elements are passed from the API, the underlying element is\n\t * in a sub-object 'object' (IOW, a DCE representing an HDA will have HDA json in element.object).\n\t * This mixin uses the constructor and parse methods to merge that JSON with the DCE attribtues\n\t * effectively changing a DCE from a container to a subclass (has a --> is a).\n\t */\n\tvar DatasetCollectionElementMixin = {\n\t\n\t /** default attributes used by elements in a dataset collection */\n\t defaults : {\n\t model_class : 'DatasetCollectionElement',\n\t element_identifier : null,\n\t element_index : null,\n\t element_type : null\n\t },\n\t\n\t /** merge the attributes of the sub-object 'object' into this model */\n\t _mergeObject : function( attributes ){\n\t // if we don't preserve and correct ids here, the element id becomes the object id\n\t // and collision in backbone's _byId will occur and only\n\t _.extend( attributes, attributes.object, { element_id: attributes.id });\n\t delete attributes.object;\n\t return attributes;\n\t },\n\t\n\t /** override to merge this.object into this */\n\t constructor : function( attributes, options ){\n\t // console.debug( '\\t DatasetCollectionElement.constructor:', attributes, options );\n\t attributes = this._mergeObject( attributes );\n\t this.idAttribute = 'element_id';\n\t Backbone.Model.apply( this, arguments );\n\t },\n\t\n\t /** when the model is fetched, merge this.object into this */\n\t parse : function( response, options ){\n\t var attributes = response;\n\t attributes = this._mergeObject( attributes );\n\t return attributes;\n\t }\n\t};\n\t\n\t/** @class Concrete class of Generic DatasetCollectionElement */\n\tvar DatasetCollectionElement = Backbone.Model\n\t .extend( BASE_MVC.LoggableMixin )\n\t .extend( DatasetCollectionElementMixin )\n\t .extend({ _logNamespace : 'collections' });\n\t\n\t\n\t//==============================================================================\n\t/** @class Base/Abstract Backbone collection for Generic DCEs. */\n\tvar DCECollection = Backbone.Collection.extend( BASE_MVC.LoggableMixin ).extend(\n\t/** @lends DCECollection.prototype */{\n\t _logNamespace : 'collections',\n\t\n\t model: DatasetCollectionElement,\n\t\n\t /** String representation. */\n\t toString : function(){\n\t return ([ 'DatasetCollectionElementCollection(', this.length, ')' ].join( '' ));\n\t }\n\t});\n\t\n\t\n\t//==============================================================================\n\t/** @class Backbone model for a dataset collection element that is a dataset (HDA).\n\t */\n\tvar DatasetDCE = DATASET_MODEL.DatasetAssociation.extend( BASE_MVC.mixin( DatasetCollectionElementMixin,\n\t/** @lends DatasetDCE.prototype */{\n\t\n\t /** url fn */\n\t url : function(){\n\t // won't always be an hda\n\t if( !this.has( 'history_id' ) ){\n\t console.warn( 'no endpoint for non-hdas within a collection yet' );\n\t // (a little silly since this api endpoint *also* points at hdas)\n\t return Galaxy.root + 'api/datasets';\n\t }\n\t return Galaxy.root + 'api/histories/' + this.get( 'history_id' ) + '/contents/' + this.get( 'id' );\n\t },\n\t\n\t defaults : _.extend( {},\n\t DATASET_MODEL.DatasetAssociation.prototype.defaults,\n\t DatasetCollectionElementMixin.defaults\n\t ),\n\t\n\t // because all objects have constructors (as this hashmap would even if this next line wasn't present)\n\t // the constructor in hcontentMixin won't be attached by BASE_MVC.mixin to this model\n\t // - re-apply manually for now\n\t /** call the mixin constructor */\n\t constructor : function( attributes, options ){\n\t this.debug( '\\t DatasetDCE.constructor:', attributes, options );\n\t //DATASET_MODEL.DatasetAssociation.prototype.constructor.call( this, attributes, options );\n\t DatasetCollectionElementMixin.constructor.call( this, attributes, options );\n\t },\n\t\n\t /** Does this model already contain detailed data (as opposed to just summary level data)? */\n\t hasDetails : function(){\n\t return this.elements && this.elements.length;\n\t },\n\t\n\t /** String representation. */\n\t toString : function(){\n\t var objStr = this.get( 'element_identifier' );\n\t return ([ 'DatasetDCE(', objStr, ')' ].join( '' ));\n\t }\n\t}));\n\t\n\t\n\t//==============================================================================\n\t/** @class DCECollection of DatasetDCE's (a list of datasets, a pair of datasets).\n\t */\n\tvar DatasetDCECollection = DCECollection.extend(\n\t/** @lends DatasetDCECollection.prototype */{\n\t model: DatasetDCE,\n\t\n\t /** String representation. */\n\t toString : function(){\n\t return ([ 'DatasetDCECollection(', this.length, ')' ].join( '' ));\n\t }\n\t});\n\t\n\t\n\t//_________________________________________________________________________________________________ COLLECTIONS\n\t/** @class Backbone model for Dataset Collections.\n\t * The DC API returns an array of JSON objects under the attribute elements.\n\t * This model:\n\t * - removes that array/attribute ('elements') from the model,\n\t * - creates a bbone collection (of the class defined in the 'collectionClass' attribute),\n\t * - passes that json onto the bbone collection\n\t * - caches the bbone collection in this.elements\n\t */\n\tvar DatasetCollection = Backbone.Model\n\t .extend( BASE_MVC.LoggableMixin )\n\t .extend( BASE_MVC.SearchableModelMixin )\n\t .extend(/** @lends DatasetCollection.prototype */{\n\t _logNamespace : 'collections',\n\t\n\t /** default attributes for a model */\n\t defaults : {\n\t /* 'list', 'paired', or 'list:paired' */\n\t collection_type : null,\n\t //??\n\t deleted : false\n\t },\n\t\n\t /** Which class to use for elements */\n\t collectionClass : DCECollection,\n\t\n\t /** set up: create elements instance var and (on changes to elements) update them */\n\t initialize : function( model, options ){\n\t this.debug( this + '(DatasetCollection).initialize:', model, options, this );\n\t this.elements = this._createElementsModel();\n\t this.on( 'change:elements', function(){\n\t this.log( 'change:elements' );\n\t //TODO: prob. better to update the collection instead of re-creating it\n\t this.elements = this._createElementsModel();\n\t });\n\t },\n\t\n\t /** move elements model attribute to full collection */\n\t _createElementsModel : function(){\n\t this.debug( this + '._createElementsModel', this.collectionClass, this.get( 'elements' ), this.elements );\n\t //TODO: same patterns as DatasetCollectionElement _createObjectModel - refactor to BASE_MVC.hasSubModel?\n\t var elements = this.get( 'elements' ) || [];\n\t this.unset( 'elements', { silent: true });\n\t this.elements = new this.collectionClass( elements );\n\t //this.debug( 'collectionClass:', this.collectionClass + '', this.elements );\n\t return this.elements;\n\t },\n\t\n\t // ........................................................................ common queries\n\t /** pass the elements back within the model json when this is serialized */\n\t toJSON : function(){\n\t var json = Backbone.Model.prototype.toJSON.call( this );\n\t if( this.elements ){\n\t json.elements = this.elements.toJSON();\n\t }\n\t return json;\n\t },\n\t\n\t /** Is this collection in a 'ready' state no processing (for the collection) is left\n\t * to do on the server.\n\t */\n\t inReadyState : function(){\n\t var populated = this.get( 'populated' );\n\t return ( this.isDeletedOrPurged() || populated );\n\t },\n\t\n\t //TODO:?? the following are the same interface as DatasetAssociation - can we combine?\n\t /** Does the DC contain any elements yet? Is a fetch() required? */\n\t hasDetails : function(){\n\t return this.elements.length !== 0;\n\t },\n\t\n\t /** Given the filters, what models in this.elements would be returned? */\n\t getVisibleContents : function( filters ){\n\t // filters unused for now\n\t return this.elements;\n\t },\n\t\n\t // ........................................................................ ajax\n\t /** override to use actual Dates objects for create/update times */\n\t parse : function( response, options ){\n\t var parsed = Backbone.Model.prototype.parse.call( this, response, options );\n\t if( parsed.create_time ){\n\t parsed.create_time = new Date( parsed.create_time );\n\t }\n\t if( parsed.update_time ){\n\t parsed.update_time = new Date( parsed.update_time );\n\t }\n\t return parsed;\n\t },\n\t\n\t /** save this dataset, _Mark_ing it as deleted (just a flag) */\n\t 'delete' : function( options ){\n\t if( this.get( 'deleted' ) ){ return jQuery.when(); }\n\t return this.save( { deleted: true }, options );\n\t },\n\t /** save this dataset, _Mark_ing it as undeleted */\n\t undelete : function( options ){\n\t if( !this.get( 'deleted' ) || this.get( 'purged' ) ){ return jQuery.when(); }\n\t return this.save( { deleted: false }, options );\n\t },\n\t\n\t /** Is this collection deleted or purged? */\n\t isDeletedOrPurged : function(){\n\t return ( this.get( 'deleted' ) || this.get( 'purged' ) );\n\t },\n\t\n\t // ........................................................................ searchable\n\t /** searchable attributes for collections */\n\t searchAttributes : [\n\t 'name'\n\t ],\n\t\n\t // ........................................................................ misc\n\t /** String representation */\n\t toString : function(){\n\t var idAndName = [ this.get( 'id' ), this.get( 'name' ) || this.get( 'element_identifier' ) ];\n\t return 'DatasetCollection(' + ( idAndName.join(',') ) + ')';\n\t }\n\t});\n\t\n\t\n\t//==============================================================================\n\t/** Model for a DatasetCollection containing datasets (non-nested).\n\t */\n\tvar ListDatasetCollection = DatasetCollection.extend(\n\t/** @lends ListDatasetCollection.prototype */{\n\t\n\t /** override since we know the collection will only contain datasets */\n\t collectionClass : DatasetDCECollection,\n\t\n\t /** String representation. */\n\t toString : function(){ return 'List' + DatasetCollection.prototype.toString.call( this ); }\n\t});\n\t\n\t\n\t//==============================================================================\n\t/** Model for a DatasetCollection containing fwd/rev datasets (a list of 2).\n\t */\n\tvar PairDatasetCollection = ListDatasetCollection.extend(\n\t/** @lends PairDatasetCollection.prototype */{\n\t\n\t /** String representation. */\n\t toString : function(){ return 'Pair' + DatasetCollection.prototype.toString.call( this ); }\n\t});\n\t\n\t\n\t//_________________________________________________________________________________________________ NESTED COLLECTIONS\n\t// this is where things get weird, man. Weird.\n\t//TODO: it might be possible to compact all the following...I think.\n\t//==============================================================================\n\t/** @class Backbone model for a Generic DatasetCollectionElement that is also a DatasetCollection\n\t * (a nested collection). Currently only list:paired.\n\t */\n\tvar NestedDCDCE = DatasetCollection.extend( BASE_MVC.mixin( DatasetCollectionElementMixin,\n\t/** @lends NestedDCDCE.prototype */{\n\t\n\t // because all objects have constructors (as this hashmap would even if this next line wasn't present)\n\t // the constructor in hcontentMixin won't be attached by BASE_MVC.mixin to this model\n\t // - re-apply manually it now\n\t /** call the mixin constructor */\n\t constructor : function( attributes, options ){\n\t this.debug( '\\t NestedDCDCE.constructor:', attributes, options );\n\t DatasetCollectionElementMixin.constructor.call( this, attributes, options );\n\t },\n\t\n\t /** String representation. */\n\t toString : function(){\n\t var objStr = ( this.object )?( '' + this.object ):( this.get( 'element_identifier' ) );\n\t return ([ 'NestedDCDCE(', objStr, ')' ].join( '' ));\n\t }\n\t}));\n\t\n\t\n\t//==============================================================================\n\t/** @class Backbone collection containing Generic NestedDCDCE's (nested dataset collections).\n\t */\n\tvar NestedDCDCECollection = DCECollection.extend(\n\t/** @lends NestedDCDCECollection.prototype */{\n\t\n\t /** This is a collection of nested collections */\n\t model: NestedDCDCE,\n\t\n\t /** String representation. */\n\t toString : function(){\n\t return ([ 'NestedDCDCECollection(', this.length, ')' ].join( '' ));\n\t }\n\t});\n\t\n\t\n\t//==============================================================================\n\t/** @class Backbone model for a paired dataset collection within a list:paired dataset collection.\n\t */\n\tvar NestedPairDCDCE = PairDatasetCollection.extend( BASE_MVC.mixin( DatasetCollectionElementMixin,\n\t/** @lends NestedPairDCDCE.prototype */{\n\t//TODO:?? possibly rename to NestedDatasetCollection?\n\t\n\t // because all objects have constructors (as this hashmap would even if this next line wasn't present)\n\t // the constructor in hcontentMixin won't be attached by BASE_MVC.mixin to this model\n\t // - re-apply manually it now\n\t /** This is both a collection and a collection element - call the constructor */\n\t constructor : function( attributes, options ){\n\t this.debug( '\\t NestedPairDCDCE.constructor:', attributes, options );\n\t //DatasetCollection.constructor.call( this, attributes, options );\n\t DatasetCollectionElementMixin.constructor.call( this, attributes, options );\n\t },\n\t\n\t /** String representation. */\n\t toString : function(){\n\t var objStr = ( this.object )?( '' + this.object ):( this.get( 'element_identifier' ) );\n\t return ([ 'NestedPairDCDCE(', objStr, ')' ].join( '' ));\n\t }\n\t}));\n\t\n\t\n\t//==============================================================================\n\t/** @class Backbone collection for a backbone collection containing paired dataset collections.\n\t */\n\tvar NestedPairDCDCECollection = NestedDCDCECollection.extend(\n\t/** @lends PairDCDCECollection.prototype */{\n\t\n\t /** We know this collection is composed of only nested pair collections */\n\t model: NestedPairDCDCE,\n\t\n\t /** String representation. */\n\t toString : function(){\n\t return ([ 'NestedPairDCDCECollection(', this.length, ')' ].join( '' ));\n\t }\n\t});\n\t\n\t\n\t//==============================================================================\n\t/** @class Backbone Model for a DatasetCollection (list) that contains DatasetCollections (pairs).\n\t */\n\tvar ListPairedDatasetCollection = DatasetCollection.extend(\n\t/** @lends ListPairedDatasetCollection.prototype */{\n\t\n\t /** list:paired is the only collection that itself contains collections */\n\t collectionClass : NestedPairDCDCECollection,\n\t\n\t /** String representation. */\n\t toString : function(){\n\t return ([ 'ListPairedDatasetCollection(', this.get( 'name' ), ')' ].join( '' ));\n\t }\n\t});\n\t\n\t\n\t//==============================================================================\n\t/** @class Backbone model for a list dataset collection within a list:list dataset collection. */\n\tvar NestedListDCDCE = ListDatasetCollection.extend( BASE_MVC.mixin( DatasetCollectionElementMixin,\n\t/** @lends NestedListDCDCE.prototype */{\n\t\n\t /** This is both a collection and a collection element - call the constructor */\n\t constructor : function( attributes, options ){\n\t this.debug( '\\t NestedListDCDCE.constructor:', attributes, options );\n\t DatasetCollectionElementMixin.constructor.call( this, attributes, options );\n\t },\n\t\n\t /** String representation. */\n\t toString : function(){\n\t var objStr = ( this.object )?( '' + this.object ):( this.get( 'element_identifier' ) );\n\t return ([ 'NestedListDCDCE(', objStr, ')' ].join( '' ));\n\t }\n\t}));\n\t\n\t\n\t//==============================================================================\n\t/** @class Backbone collection containing list dataset collections. */\n\tvar NestedListDCDCECollection = NestedDCDCECollection.extend({\n\t\n\t /** We know this collection is composed of only nested pair collections */\n\t model: NestedListDCDCE,\n\t\n\t /** String representation. */\n\t toString : function(){\n\t return ([ 'NestedListDCDCECollection(', this.length, ')' ].join( '' ));\n\t }\n\t});\n\t\n\t\n\t//==============================================================================\n\t/** @class Backbone Model for a DatasetCollection (list) that contains other lists. */\n\tvar ListOfListsDatasetCollection = DatasetCollection.extend({\n\t\n\t /** list:paired is the only collection that itself contains collections */\n\t collectionClass : NestedListDCDCECollection,\n\t\n\t /** String representation. */\n\t toString : function(){\n\t return ([ 'ListOfListsDatasetCollection(', this.get( 'name' ), ')' ].join( '' ));\n\t }\n\t});\n\t\n\t\n\t//==============================================================================\n\t return {\n\t ListDatasetCollection : ListDatasetCollection,\n\t PairDatasetCollection : PairDatasetCollection,\n\t ListPairedDatasetCollection : ListPairedDatasetCollection,\n\t ListOfListsDatasetCollection: ListOfListsDatasetCollection\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(2), __webpack_require__(3), __webpack_require__(1)))\n\n/***/ },\n/* 31 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, _, $, jQuery) {\n\t!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(39),\n\t __webpack_require__(12),\n\t __webpack_require__(6),\n\t __webpack_require__(8),\n\t __webpack_require__(87),\n\t __webpack_require__(5),\n\t __webpack_require__(84)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( HDCA, STATES, BASE_MVC, UI_MODAL, naturalSort, _l ){\n\t\n\t'use strict';\n\t\n\tvar logNamespace = 'collections';\n\t/*==============================================================================\n\tTODO:\n\t use proper Element model and not just json\n\t straighten out createFn, collection.createHDCA\n\t possibly stop using modals for this\n\t It would be neat to do a drag and drop\n\t\n\t==============================================================================*/\n\t/** A view for both DatasetDCEs and NestedDCDCEs\n\t * (things that implement collection-model:DatasetCollectionElementMixin)\n\t */\n\tvar DatasetCollectionElementView = Backbone.View.extend( BASE_MVC.LoggableMixin ).extend({\n\t _logNamespace : logNamespace,\n\t\n\t//TODO: use proper class (DatasetDCE or NestedDCDCE (or the union of both))\n\t tagName : 'li',\n\t className : 'collection-element',\n\t\n\t initialize : function( attributes ){\n\t this.element = attributes.element || {};\n\t this.selected = attributes.selected || false;\n\t },\n\t\n\t render : function(){\n\t this.$el\n\t .attr( 'data-element-id', this.element.id )\n\t .attr( 'draggable', true )\n\t .html( this.template({ element: this.element }) );\n\t if( this.selected ){\n\t this.$el.addClass( 'selected' );\n\t }\n\t return this;\n\t },\n\t\n\t //TODO: lots of unused space in the element - possibly load details and display them horiz.\n\t template : _.template([\n\t '',\n\t '<%- element.name %>',\n\t '',\n\t '',\n\t ].join('')),\n\t\n\t /** select this element and pub */\n\t select : function( toggle ){\n\t this.$el.toggleClass( 'selected', toggle );\n\t this.trigger( 'select', {\n\t source : this,\n\t selected : this.$el.hasClass( 'selected' )\n\t });\n\t },\n\t\n\t /** animate the removal of this element and pub */\n\t discard : function(){\n\t var view = this,\n\t parentWidth = this.$el.parent().width();\n\t this.$el.animate({ 'margin-right' : parentWidth }, 'fast', function(){\n\t view.trigger( 'discard', {\n\t source : view\n\t });\n\t view.destroy();\n\t });\n\t },\n\t\n\t /** remove the DOM and any listeners */\n\t destroy : function(){\n\t this.off();\n\t this.$el.remove();\n\t },\n\t\n\t events : {\n\t 'click' : '_click',\n\t 'click .name' : '_clickName',\n\t 'click .discard': '_clickDiscard',\n\t\n\t 'dragstart' : '_dragstart',\n\t 'dragend' : '_dragend',\n\t 'dragover' : '_sendToParent',\n\t 'drop' : '_sendToParent'\n\t },\n\t\n\t /** select when the li is clicked */\n\t _click : function( ev ){\n\t ev.stopPropagation();\n\t this.select( ev );\n\t },\n\t\n\t /** rename a pair when the name is clicked */\n\t _clickName : function( ev ){\n\t ev.stopPropagation();\n\t ev.preventDefault();\n\t var promptString = [ _l( 'Enter a new name for the element' ), ':\\n(',\n\t _l( 'Note that changing the name here will not rename the dataset' ), ')' ].join( '' ),\n\t response = prompt( _l( 'Enter a new name for the element' ) + ':', this.element.name );\n\t if( response ){\n\t this.element.name = response;\n\t this.render();\n\t }\n\t //TODO: cancelling with ESC leads to closure of the creator...\n\t },\n\t\n\t /** discard when the discard button is clicked */\n\t _clickDiscard : function( ev ){\n\t ev.stopPropagation();\n\t this.discard();\n\t },\n\t\n\t /** dragging pairs for re-ordering */\n\t _dragstart : function( ev ){\n\t if( ev.originalEvent ){ ev = ev.originalEvent; }\n\t ev.dataTransfer.effectAllowed = 'move';\n\t ev.dataTransfer.setData( 'text/plain', JSON.stringify( this.element ) );\n\t\n\t this.$el.addClass( 'dragging' );\n\t this.$el.parent().trigger( 'collection-element.dragstart', [ this ] );\n\t },\n\t\n\t /** dragging for re-ordering */\n\t _dragend : function( ev ){\n\t this.$el.removeClass( 'dragging' );\n\t this.$el.parent().trigger( 'collection-element.dragend', [ this ] );\n\t },\n\t\n\t /** manually bubble up an event to the parent/container */\n\t _sendToParent : function( ev ){\n\t this.$el.parent().trigger( ev );\n\t },\n\t\n\t /** string rep */\n\t toString : function(){\n\t return 'DatasetCollectionElementView()';\n\t }\n\t});\n\t\n\t\n\t// ============================================================================\n\t/** An interface for building collections.\n\t */\n\tvar ListCollectionCreator = Backbone.View.extend( BASE_MVC.LoggableMixin ).extend({\n\t _logNamespace : logNamespace,\n\t\n\t /** the class used to display individual elements */\n\t elementViewClass : DatasetCollectionElementView,\n\t /** the class this creator will create and save */\n\t collectionClass : HDCA.HistoryListDatasetCollection,\n\t className : 'list-collection-creator collection-creator flex-row-container',\n\t\n\t /** minimum number of valid elements to start with in order to build a collection of this type */\n\t minElements : 1,\n\t\n\t defaultAttributes : {\n\t//TODO: remove - use new collectionClass().save()\n\t /** takes elements and creates the proper collection - returns a promise */\n\t creationFn : function(){ throw new TypeError( 'no creation fn for creator' ); },\n\t /** fn to call when the collection is created (scoped to this) */\n\t oncreate : function(){},\n\t /** fn to call when the cancel button is clicked (scoped to this) - if falsy, no btn is displayed */\n\t oncancel : function(){},\n\t /** distance from list edge to begin autoscrolling list */\n\t autoscrollDist : 24,\n\t /** Color passed to hoverhighlight */\n\t highlightClr : 'rgba( 64, 255, 255, 1.0 )'\n\t },\n\t\n\t /** set up initial options, instance vars, behaviors */\n\t initialize : function( attributes ){\n\t this.metric( 'ListCollectionCreator.initialize', attributes );\n\t var creator = this;\n\t _.each( this.defaultAttributes, function( value, key ){\n\t value = attributes[ key ] || value;\n\t creator[ key ] = value;\n\t });\n\t\n\t /** unordered, original list - cache to allow reversal */\n\t creator.initialElements = attributes.elements || [];\n\t\n\t this._instanceSetUp();\n\t this._elementsSetUp();\n\t this._setUpBehaviors();\n\t },\n\t\n\t /** set up instance vars */\n\t _instanceSetUp : function(){\n\t /** Ids of elements that have been selected by the user - to preserve over renders */\n\t this.selectedIds = {};\n\t /** DOM elements currently being dragged */\n\t this.$dragging = null;\n\t /** Used for blocking UI events during ajax/operations (don't post twice) */\n\t this.blocking = false;\n\t },\n\t\n\t // ------------------------------------------------------------------------ process raw list\n\t /** set up main data */\n\t _elementsSetUp : function(){\n\t //this.debug( '-- _dataSetUp' );\n\t /** a list of invalid elements and the reasons they aren't valid */\n\t this.invalidElements = [];\n\t//TODO: handle fundamental problem of syncing DOM, views, and list here\n\t /** data for list in progress */\n\t this.workingElements = [];\n\t /** views for workingElements */\n\t this.elementViews = [];\n\t\n\t // copy initial list, sort, add ids if needed\n\t this.workingElements = this.initialElements.slice( 0 );\n\t this._ensureElementIds();\n\t this._validateElements();\n\t this._mangleDuplicateNames();\n\t this._sortElements();\n\t },\n\t\n\t /** add ids to dataset objs in initial list if none */\n\t _ensureElementIds : function(){\n\t this.workingElements.forEach( function( element ){\n\t if( !element.hasOwnProperty( 'id' ) ){\n\t element.id = _.uniqueId();\n\t }\n\t });\n\t return this.workingElements;\n\t },\n\t\n\t /** separate working list into valid and invalid elements for this collection */\n\t _validateElements : function(){\n\t var creator = this,\n\t existingNames = {};\n\t creator.invalidElements = [];\n\t\n\t this.workingElements = this.workingElements.filter( function( element ){\n\t var problem = creator._isElementInvalid( element );\n\t if( problem ){\n\t creator.invalidElements.push({\n\t element : element,\n\t text : problem\n\t });\n\t }\n\t return !problem;\n\t });\n\t return this.workingElements;\n\t },\n\t\n\t /** describe what is wrong with a particular element if anything */\n\t _isElementInvalid : function( element ){\n\t if( element.history_content_type !== 'dataset' ){\n\t return _l( \"is not a dataset\" );\n\t }\n\t if( element.state !== STATES.OK ){\n\t if( _.contains( STATES.NOT_READY_STATES, element.state ) ){\n\t return _l( \"hasn't finished running yet\" );\n\t }\n\t return _l( \"has errored, is paused, or is not accessible\" );\n\t }\n\t if( element.deleted || element.purged ){\n\t return _l( \"has been deleted or purged\" );\n\t }\n\t return null;\n\t },\n\t\n\t /** mangle duplicate names using a mac-like '(counter)' addition to any duplicates */\n\t _mangleDuplicateNames : function(){\n\t var SAFETY = 900,\n\t counter = 1,\n\t existingNames = {};\n\t this.workingElements.forEach( function( element ){\n\t var currName = element.name;\n\t while( existingNames.hasOwnProperty( currName ) ){\n\t currName = element.name + ' (' + counter + ')';\n\t counter += 1;\n\t if( counter >= SAFETY ){\n\t throw new Error( 'Safety hit in while loop - thats impressive' );\n\t }\n\t }\n\t element.name = currName;\n\t existingNames[ element.name ] = true;\n\t });\n\t },\n\t\n\t /** sort a list of elements */\n\t _sortElements : function( list ){\n\t // // currently only natural sort by name\n\t // this.workingElements.sort( function( a, b ){ return naturalSort( a.name, b.name ); });\n\t // return this.workingElements;\n\t },\n\t\n\t // ------------------------------------------------------------------------ rendering\n\t // templates : ListCollectionCreator.templates,\n\t /** render the entire interface */\n\t render : function( speed, callback ){\n\t //this.debug( '-- _render' );\n\t if( this.workingElements.length < this.minElements ){\n\t return this._renderInvalid( speed, callback );\n\t }\n\t\n\t this.$el.empty().html( this.templates.main() );\n\t this._renderHeader( speed );\n\t this._renderMiddle( speed );\n\t this._renderFooter( speed );\n\t this._addPluginComponents();\n\t this.$( '.collection-name' ).focus();\n\t this.trigger( 'rendered', this );\n\t return this;\n\t },\n\t\n\t\n\t /** render a simplified interface aimed at telling the user why they can't move forward */\n\t _renderInvalid : function( speed, callback ){\n\t //this.debug( '-- _render' );\n\t this.$el.empty().html( this.templates.invalidInitial({\n\t problems: this.invalidElements,\n\t elements: this.workingElements,\n\t }));\n\t if( typeof this.oncancel === 'function' ){\n\t this.$( '.cancel-create.btn' ).show();\n\t }\n\t this.trigger( 'rendered', this );\n\t return this;\n\t },\n\t\n\t /** render the header section */\n\t _renderHeader : function( speed, callback ){\n\t var $header = this.$( '.header' ).empty().html( this.templates.header() )\n\t .find( '.help-content' ).prepend( $( this.templates.helpContent() ) );\n\t //TODO: should only show once despite calling _renderHeader again\n\t if( this.invalidElements.length ){\n\t this._invalidElementsAlert();\n\t }\n\t return $header;\n\t },\n\t\n\t /** render the middle including the elements */\n\t _renderMiddle : function( speed, callback ){\n\t var $middle = this.$( '.middle' ).empty().html( this.templates.middle() );\n\t this._renderList( speed );\n\t return $middle;\n\t },\n\t\n\t /** render the footer, completion controls, and cancel controls */\n\t _renderFooter : function( speed, callback ){\n\t var $footer = this.$( '.footer' ).empty().html( this.templates.footer() );\n\t if( typeof this.oncancel === 'function' ){\n\t this.$( '.cancel-create.btn' ).show();\n\t }\n\t return $footer;\n\t },\n\t\n\t /** add any jQuery/bootstrap/custom plugins to elements rendered */\n\t _addPluginComponents : function(){\n\t this.$( '.help-content i' ).hoverhighlight( '.collection-creator', this.highlightClr );\n\t },\n\t\n\t /** build and show an alert describing any elements that could not be included due to problems */\n\t _invalidElementsAlert : function(){\n\t this._showAlert( this.templates.invalidElements({ problems: this.invalidElements }), 'alert-warning' );\n\t },\n\t\n\t /** add (or clear if clear is truthy) a validation warning to the DOM element described in what */\n\t _validationWarning : function( what, clear ){\n\t var VALIDATION_CLASS = 'validation-warning';\n\t if( what === 'name' ){\n\t what = this.$( '.collection-name' ).add( this.$( '.collection-name-prompt' ) );\n\t this.$( '.collection-name' ).focus().select();\n\t }\n\t if( clear ){\n\t what = what || this.$( '.' + VALIDATION_CLASS );\n\t what.removeClass( VALIDATION_CLASS );\n\t } else {\n\t what.addClass( VALIDATION_CLASS );\n\t }\n\t },\n\t\n\t _disableNameAndCreate : function( disable ){\n\t disable = !_.isUndefined( disable )? disable : true;\n\t if( disable ){\n\t this.$( '.collection-name' ).prop( 'disabled', true );\n\t this.$( '.create-collection' ).toggleClass( 'disabled', true );\n\t // } else {\n\t // this.$( '.collection-name' ).prop( 'disabled', false );\n\t // this.$( '.create-collection' ).removeClass( 'disable' );\n\t }\n\t },\n\t\n\t // ------------------------------------------------------------------------ rendering elements\n\t /** conv. to the main list display DOM */\n\t $list : function(){\n\t return this.$( '.collection-elements' );\n\t },\n\t\n\t /** show or hide the clear selected control based on the num of selected elements */\n\t _renderClearSelected : function(){\n\t if( _.size( this.selectedIds ) ){\n\t this.$( '.collection-elements-controls > .clear-selected' ).show();\n\t } else {\n\t this.$( '.collection-elements-controls > .clear-selected' ).hide();\n\t }\n\t },\n\t\n\t /** render the elements in order (or a warning if no elements found) */\n\t _renderList : function( speed, callback ){\n\t //this.debug( '-- _renderList' );\n\t var creator = this,\n\t $tmp = jQuery( '
                        ' ),\n\t $list = creator.$list();\n\t\n\t _.each( this.elementViews, function( view ){\n\t view.destroy();\n\t creator.removeElementView( view );\n\t });\n\t\n\t // if( !this.workingElements.length ){\n\t // this._renderNoValidElements();\n\t // return;\n\t // }\n\t\n\t creator.workingElements.forEach( function( element ){\n\t var elementView = creator._createElementView( element );\n\t $tmp.append( elementView.$el );\n\t });\n\t\n\t creator._renderClearSelected();\n\t $list.empty().append( $tmp.children() );\n\t _.invoke( creator.elementViews, 'render' );\n\t\n\t if( $list.height() > $list.css( 'max-height' ) ){\n\t $list.css( 'border-width', '1px 0px 1px 0px' );\n\t } else {\n\t $list.css( 'border-width', '0px' );\n\t }\n\t },\n\t\n\t /** create an element view, cache in elementViews, set up listeners, and return */\n\t _createElementView : function( element ){\n\t var elementView = new this.elementViewClass({\n\t//TODO: use non-generic class or not all\n\t // model : COLLECTION.DatasetDCE( element )\n\t element : element,\n\t selected: _.has( this.selectedIds, element.id )\n\t });\n\t this.elementViews.push( elementView );\n\t this._listenToElementView( elementView );\n\t return elementView;\n\t },\n\t\n\t /** listen to any element events */\n\t _listenToElementView : function( view ){\n\t var creator = this;\n\t creator.listenTo( view, {\n\t select : function( data ){\n\t var element = data.source.element;\n\t if( data.selected ){\n\t creator.selectedIds[ element.id ] = true;\n\t } else {\n\t delete creator.selectedIds[ element.id ];\n\t }\n\t creator.trigger( 'elements:select', data );\n\t },\n\t discard : function( data ){\n\t creator.trigger( 'elements:discard', data );\n\t }\n\t });\n\t },\n\t\n\t /** add a new element view based on the json in element */\n\t addElementView : function( element ){\n\t//TODO: workingElements is sorted, add element in appropo index\n\t // add element, sort elements, find element index\n\t // var view = this._createElementView( element );\n\t // return view;\n\t },\n\t\n\t /** stop listening to view and remove from caches */\n\t removeElementView : function( view ){\n\t delete this.selectedIds[ view.element.id ];\n\t this._renderClearSelected();\n\t\n\t this.elementViews = _.without( this.elementViews, view );\n\t this.stopListening( view );\n\t },\n\t\n\t /** render a message in the list that no elements remain to create a collection */\n\t _renderNoElementsLeft : function(){\n\t this._disableNameAndCreate( true );\n\t this.$( '.collection-elements' ).append( this.templates.noElementsLeft() );\n\t },\n\t\n\t // /** render a message in the list that no valid elements were found to create a collection */\n\t // _renderNoValidElements : function(){\n\t // this._disableNameAndCreate( true );\n\t // this.$( '.collection-elements' ).append( this.templates.noValidElements() );\n\t // },\n\t\n\t // ------------------------------------------------------------------------ API\n\t /** convert element into JSON compatible with the collections API */\n\t _elementToJSON : function( element ){\n\t // return element.toJSON();\n\t return element;\n\t },\n\t\n\t /** create the collection via the API\n\t * @returns {jQuery.xhr Object} the jquery ajax request\n\t */\n\t createList : function( name ){\n\t if( !this.workingElements.length ){\n\t var message = _l( 'No valid elements for final list' ) + '. ';\n\t message += '' + _l( 'Cancel' ) + ' ';\n\t message += _l( 'or' );\n\t message += ' ' + _l( 'start over' ) + '.';\n\t this._showAlert( message );\n\t return;\n\t }\n\t\n\t var creator = this,\n\t elements = this.workingElements.map( function( element ){\n\t return creator._elementToJSON( element );\n\t });\n\t\n\t creator.blocking = true;\n\t return creator.creationFn( elements, name )\n\t .always( function(){\n\t creator.blocking = false;\n\t })\n\t .fail( function( xhr, status, message ){\n\t creator.trigger( 'error', {\n\t xhr : xhr,\n\t status : status,\n\t message : _l( 'An error occurred while creating this collection' )\n\t });\n\t })\n\t .done( function( response, message, xhr ){\n\t creator.trigger( 'collection:created', response, message, xhr );\n\t creator.metric( 'collection:created', response );\n\t if( typeof creator.oncreate === 'function' ){\n\t creator.oncreate.call( this, response, message, xhr );\n\t }\n\t });\n\t },\n\t\n\t // ------------------------------------------------------------------------ events\n\t /** set up event handlers on self */\n\t _setUpBehaviors : function(){\n\t this.on( 'error', this._errorHandler );\n\t\n\t this.once( 'rendered', function(){\n\t this.trigger( 'rendered:initial', this );\n\t });\n\t\n\t this.on( 'elements:select', function( data ){\n\t this._renderClearSelected();\n\t });\n\t\n\t this.on( 'elements:discard', function( data ){\n\t var element = data.source.element;\n\t this.removeElementView( data.source );\n\t\n\t this.workingElements = _.without( this.workingElements, element );\n\t if( !this.workingElements.length ){\n\t this._renderNoElementsLeft();\n\t }\n\t });\n\t\n\t //this.on( 'all', function(){\n\t // this.info( arguments );\n\t //});\n\t return this;\n\t },\n\t\n\t /** handle errors with feedback and details to the user (if available) */\n\t _errorHandler : function( data ){\n\t this.error( data );\n\t\n\t var creator = this;\n\t content = data.message || _l( 'An error occurred' );\n\t if( data.xhr ){\n\t var xhr = data.xhr,\n\t message = data.message;\n\t if( xhr.readyState === 0 && xhr.status === 0 ){\n\t content += ': ' + _l( 'Galaxy could not be reached and may be updating.' ) +\n\t _l( ' Try again in a few minutes.' );\n\t } else if( xhr.responseJSON ){\n\t content += ':
                        ' + JSON.stringify( xhr.responseJSON ) + '
                        ';\n\t } else {\n\t content += ': ' + message;\n\t }\n\t }\n\t creator._showAlert( content, 'alert-danger' );\n\t },\n\t\n\t events : {\n\t // header\n\t 'click .more-help' : '_clickMoreHelp',\n\t 'click .less-help' : '_clickLessHelp',\n\t 'click .main-help' : '_toggleHelp',\n\t 'click .header .alert button' : '_hideAlert',\n\t\n\t 'click .reset' : 'reset',\n\t 'click .clear-selected' : 'clearSelectedElements',\n\t\n\t // elements - selection\n\t 'click .collection-elements' : 'clearSelectedElements',\n\t\n\t // elements - drop target\n\t // 'dragenter .collection-elements': '_dragenterElements',\n\t // 'dragleave .collection-elements': '_dragleaveElements',\n\t 'dragover .collection-elements' : '_dragoverElements',\n\t 'drop .collection-elements' : '_dropElements',\n\t\n\t // these bubble up from the elements as custom events\n\t 'collection-element.dragstart .collection-elements' : '_elementDragstart',\n\t 'collection-element.dragend .collection-elements' : '_elementDragend',\n\t\n\t // footer\n\t 'change .collection-name' : '_changeName',\n\t 'keydown .collection-name' : '_nameCheckForEnter',\n\t 'click .cancel-create' : function( ev ){\n\t if( typeof this.oncancel === 'function' ){\n\t this.oncancel.call( this );\n\t }\n\t },\n\t 'click .create-collection' : '_clickCreate'//,\n\t },\n\t\n\t // ........................................................................ header\n\t /** expand help */\n\t _clickMoreHelp : function( ev ){\n\t ev.stopPropagation();\n\t this.$( '.main-help' ).addClass( 'expanded' );\n\t this.$( '.more-help' ).hide();\n\t },\n\t /** collapse help */\n\t _clickLessHelp : function( ev ){\n\t ev.stopPropagation();\n\t this.$( '.main-help' ).removeClass( 'expanded' );\n\t this.$( '.more-help' ).show();\n\t },\n\t /** toggle help */\n\t _toggleHelp : function( ev ){\n\t ev.stopPropagation();\n\t this.$( '.main-help' ).toggleClass( 'expanded' );\n\t this.$( '.more-help' ).toggle();\n\t },\n\t\n\t /** show an alert on the top of the interface containing message (alertClass is bootstrap's alert-*) */\n\t _showAlert : function( message, alertClass ){\n\t alertClass = alertClass || 'alert-danger';\n\t this.$( '.main-help' ).hide();\n\t this.$( '.header .alert' )\n\t .attr( 'class', 'alert alert-dismissable' ).addClass( alertClass ).show()\n\t .find( '.alert-message' ).html( message );\n\t },\n\t /** hide the alerts at the top */\n\t _hideAlert : function( message ){\n\t this.$( '.main-help' ).show();\n\t this.$( '.header .alert' ).hide();\n\t },\n\t\n\t // ........................................................................ elements\n\t /** reset all data to the initial state */\n\t reset : function(){\n\t this._instanceSetUp();\n\t this._elementsSetUp();\n\t this.render();\n\t },\n\t\n\t /** deselect all elements */\n\t clearSelectedElements : function( ev ){\n\t this.$( '.collection-elements .collection-element' ).removeClass( 'selected' );\n\t this.$( '.collection-elements-controls > .clear-selected' ).hide();\n\t },\n\t\n\t //_dragenterElements : function( ev ){\n\t // //this.debug( '_dragenterElements:', ev );\n\t //},\n\t//TODO: if selected are dragged out of the list area - remove the placeholder - cuz it won't work anyway\n\t // _dragleaveElements : function( ev ){\n\t // //this.debug( '_dragleaveElements:', ev );\n\t // },\n\t\n\t /** track the mouse drag over the list adding a placeholder to show where the drop would occur */\n\t _dragoverElements : function( ev ){\n\t //this.debug( '_dragoverElements:', ev );\n\t ev.preventDefault();\n\t\n\t var $list = this.$list();\n\t this._checkForAutoscroll( $list, ev.originalEvent.clientY );\n\t var $nearest = this._getNearestElement( ev.originalEvent.clientY );\n\t\n\t //TODO: no need to re-create - move instead\n\t this.$( '.element-drop-placeholder' ).remove();\n\t var $placeholder = $( '
                        ' );\n\t if( !$nearest.length ){\n\t $list.append( $placeholder );\n\t } else {\n\t $nearest.before( $placeholder );\n\t }\n\t },\n\t\n\t /** If the mouse is near enough to the list's top or bottom, scroll the list */\n\t _checkForAutoscroll : function( $element, y ){\n\t var AUTOSCROLL_SPEED = 2,\n\t offset = $element.offset(),\n\t scrollTop = $element.scrollTop(),\n\t upperDist = y - offset.top,\n\t lowerDist = ( offset.top + $element.outerHeight() ) - y;\n\t if( upperDist >= 0 && upperDist < this.autoscrollDist ){\n\t $element.scrollTop( scrollTop - AUTOSCROLL_SPEED );\n\t } else if( lowerDist >= 0 && lowerDist < this.autoscrollDist ){\n\t $element.scrollTop( scrollTop + AUTOSCROLL_SPEED );\n\t }\n\t },\n\t\n\t /** get the nearest element based on the mouse's Y coordinate.\n\t * If the y is at the end of the list, return an empty jQuery object.\n\t */\n\t _getNearestElement : function( y ){\n\t var WIGGLE = 4,\n\t lis = this.$( '.collection-elements li.collection-element' ).toArray();\n\t for( var i=0; i y && top - halfHeight < y ){\n\t return $li;\n\t }\n\t }\n\t return $();\n\t },\n\t\n\t /** drop (dragged/selected elements) onto the list, re-ordering the internal list */\n\t _dropElements : function( ev ){\n\t if( ev.originalEvent ){ ev = ev.originalEvent; }\n\t // both required for firefox\n\t ev.preventDefault();\n\t ev.dataTransfer.dropEffect = 'move';\n\t\n\t // insert before the nearest element or after the last.\n\t var $nearest = this._getNearestElement( ev.clientY );\n\t if( $nearest.length ){\n\t this.$dragging.insertBefore( $nearest );\n\t } else {\n\t // no nearest before - insert after last element\n\t this.$dragging.insertAfter( this.$( '.collection-elements .collection-element' ).last() );\n\t }\n\t // resync the creator's list based on the new DOM order\n\t this._syncOrderToDom();\n\t return false;\n\t },\n\t\n\t /** resync the creator's list of elements based on the DOM order */\n\t _syncOrderToDom : function(){\n\t var creator = this,\n\t newElements = [];\n\t //TODO: doesn't seem wise to use the dom to store these - can't we sync another way?\n\t this.$( '.collection-elements .collection-element' ).each( function(){\n\t var id = $( this ).attr( 'data-element-id' ),\n\t element = _.findWhere( creator.workingElements, { id: id });\n\t if( element ){\n\t newElements.push( element );\n\t } else {\n\t console.error( 'missing element: ', id );\n\t }\n\t });\n\t this.workingElements = newElements;\n\t this._renderList();\n\t },\n\t\n\t /** drag communication with element sub-views: dragstart */\n\t _elementDragstart : function( ev, element ){\n\t // auto select the element causing the event and move all selected\n\t element.select( true );\n\t this.$dragging = this.$( '.collection-elements .collection-element.selected' );\n\t },\n\t\n\t /** drag communication with element sub-views: dragend - remove the placeholder */\n\t _elementDragend : function( ev, element ){\n\t $( '.element-drop-placeholder' ).remove();\n\t this.$dragging = null;\n\t },\n\t\n\t // ........................................................................ footer\n\t /** handle a collection name change */\n\t _changeName : function( ev ){\n\t this._validationWarning( 'name', !!this._getName() );\n\t },\n\t\n\t /** check for enter key press when in the collection name and submit */\n\t _nameCheckForEnter : function( ev ){\n\t if( ev.keyCode === 13 && !this.blocking ){\n\t this._clickCreate();\n\t }\n\t },\n\t\n\t /** get the current collection name */\n\t _getName : function(){\n\t return _.escape( this.$( '.collection-name' ).val() );\n\t },\n\t\n\t /** attempt to create the current collection */\n\t _clickCreate : function( ev ){\n\t var name = this._getName();\n\t if( !name ){\n\t this._validationWarning( 'name' );\n\t } else if( !this.blocking ){\n\t this.createList( name );\n\t }\n\t },\n\t\n\t // ------------------------------------------------------------------------ templates\n\t //TODO: move to require text plugin and load these as text\n\t //TODO: underscore currently unnecc. bc no vars are used\n\t //TODO: better way of localizing text-nodes in long strings\n\t /** underscore template fns attached to class */\n\t templates : {\n\t /** the skeleton */\n\t main : _.template([\n\t '
                        ',\n\t '
                        ',\n\t '
                        '\n\t ].join('')),\n\t\n\t /** the header (not including help text) */\n\t header : _.template([\n\t '
                        ',\n\t '', _l( 'More help' ), '',\n\t '
                        ',\n\t '', _l( 'Less' ), '',\n\t '
                        ',\n\t '
                        ',\n\t '
                        ',\n\t '',\n\t '',\n\t '
                        ',\n\t ].join('')),\n\t\n\t /** the middle: element list */\n\t middle : _.template([\n\t '',\n\t '
                        ',\n\t '
                        '\n\t ].join('')),\n\t\n\t /** creation and cancel controls */\n\t footer : _.template([\n\t '
                        ',\n\t '
                        ',\n\t '',\n\t '
                        ', _l( 'Name' ), ':
                        ',\n\t '
                        ',\n\t '
                        ',\n\t\n\t '
                        ',\n\t '
                        ',\n\t '',\n\t '
                        ',\n\t '',\n\t '',\n\t '
                        ',\n\t '
                        ',\n\t\n\t '
                        ',\n\t '',\n\t '
                        ',\n\t '
                        '\n\t ].join('')),\n\t\n\t /** help content */\n\t helpContent : _.template([\n\t '

                        ', _l([\n\t 'Collections of datasets are permanent, ordered lists of datasets that can be passed to tools and ',\n\t 'workflows in order to have analyses done on each member of the entire group. This interface allows ',\n\t 'you to create a collection and re-order the final collection.'\n\t ].join( '' )), '

                        ',\n\t '
                          ',\n\t '
                        • ', _l([\n\t 'Rename elements in the list by clicking on ',\n\t 'the existing name.'\n\t ].join( '' )), '
                        • ',\n\t '
                        • ', _l([\n\t 'Discard elements from the final created list by clicking on the ',\n\t '\"Discard\" button.'\n\t ].join( '' )), '
                        • ',\n\t '
                        • ', _l([\n\t 'Reorder the list by clicking and dragging elements. Select multiple elements by clicking on ',\n\t 'them and you can then move those selected by dragging the ',\n\t 'entire group. Deselect them by clicking them again or by clicking the ',\n\t 'the \"Clear selected\" link.'\n\t ].join( '' )), '
                        • ',\n\t '
                        • ', _l([\n\t 'Click the \"Start over\" link to begin again as if you had just opened ',\n\t 'the interface.'\n\t ].join( '' )), '
                        • ',\n\t '
                        • ', _l([\n\t 'Click the \"Cancel\" button to exit the interface.'\n\t ].join( '' )), '
                        • ',\n\t '

                        ',\n\t '

                        ', _l([\n\t 'Once your collection is complete, enter a name and ',\n\t 'click \"Create list\".'\n\t ].join( '' )), '

                        '\n\t ].join('')),\n\t\n\t /** shown in list when all elements are discarded */\n\t invalidElements : _.template([\n\t _l( 'The following selections could not be included due to problems:' ),\n\t '
                          <% _.each( problems, function( problem ){ %>',\n\t '
                        • <%- problem.element.name %>: <%- problem.text %>
                        • ',\n\t '<% }); %>
                        '\n\t ].join('')),\n\t\n\t /** shown in list when all elements are discarded */\n\t noElementsLeft : _.template([\n\t '
                      • ',\n\t _l( 'No elements left! ' ),\n\t _l( 'Would you like to ' ), '', _l( 'start over' ), '?',\n\t '
                      • '\n\t ].join('')),\n\t\n\t /** a simplified page communicating what went wrong and why the user needs to reselect something else */\n\t invalidInitial : _.template([\n\t '
                        ',\n\t '
                        ',\n\t '',\n\t '<% if( _.size( problems ) ){ %>',\n\t _l( 'The following selections could not be included due to problems' ), ':',\n\t '
                          <% _.each( problems, function( problem ){ %>',\n\t '
                        • <%- problem.element.name %>: <%- problem.text %>
                        • ',\n\t '<% }); %>
                        ',\n\t '<% } else if( _.size( elements ) < 1 ){ %>',\n\t _l( 'No datasets were selected' ), '.',\n\t '<% } %>',\n\t '
                        ',\n\t _l( 'At least one element is needed for the collection' ), '. ',\n\t _l( 'You may need to ' ),\n\t '', _l( 'cancel' ), ' ',\n\t _l( 'and reselect new elements' ), '.',\n\t '
                        ',\n\t '
                        ',\n\t '
                        ',\n\t '
                        ',\n\t '
                        ',\n\t '
                        ',\n\t '',\n\t // _l( 'Create a different kind of collection' ),\n\t '
                        ',\n\t '
                        ',\n\t '
                        '\n\t ].join('')),\n\t },\n\t\n\t // ------------------------------------------------------------------------ misc\n\t /** string rep */\n\t toString : function(){ return 'ListCollectionCreator'; }\n\t});\n\t\n\t\n\t\n\t//=============================================================================\n\t/** Create a modal and load its body with the given CreatorClass creator type\n\t * @returns {Deferred} resolved when creator has built a collection.\n\t */\n\tvar collectionCreatorModal = function _collectionCreatorModal( elements, options, CreatorClass ){\n\t\n\t var deferred = jQuery.Deferred(),\n\t modal = Galaxy.modal || ( new UI_MODAL.View() ),\n\t creator;\n\t\n\t options = _.defaults( options || {}, {\n\t elements : elements,\n\t oncancel : function(){\n\t modal.hide();\n\t deferred.reject( 'cancelled' );\n\t },\n\t oncreate : function( creator, response ){\n\t modal.hide();\n\t deferred.resolve( response );\n\t }\n\t });\n\t\n\t creator = new CreatorClass( options );\n\t modal.show({\n\t title : options.title || _l( 'Create a collection' ),\n\t body : creator.$el,\n\t width : '80%',\n\t height : '100%',\n\t closing_events: true\n\t });\n\t creator.render();\n\t window._collectionCreator = creator;\n\t\n\t //TODO: remove modal header\n\t return deferred;\n\t};\n\t\n\t/** List collection flavor of collectionCreatorModal. */\n\tvar listCollectionCreatorModal = function _listCollectionCreatorModal( elements, options ){\n\t options = options || {};\n\t options.title = _l( 'Create a collection from a list of datasets' );\n\t return collectionCreatorModal( elements, options, ListCollectionCreator );\n\t};\n\t\n\t\n\t//==============================================================================\n\t/** Use a modal to create a list collection, then add it to the given history contents.\n\t * @returns {Deferred} resolved when the collection is added to the history.\n\t */\n\tfunction createListCollection( contents ){\n\t var elements = contents.toJSON(),\n\t promise = listCollectionCreatorModal( elements, {\n\t creationFn : function( elements, name ){\n\t elements = elements.map( function( element ){\n\t return {\n\t id : element.id,\n\t name : element.name,\n\t //TODO: this allows for list:list even if the filter above does not - reconcile\n\t src : ( element.history_content_type === 'dataset'? 'hda' : 'hdca' )\n\t };\n\t });\n\t return contents.createHDCA( elements, 'list', name );\n\t }\n\t });\n\t return promise;\n\t}\n\t\n\t//==============================================================================\n\t return {\n\t DatasetCollectionElementView: DatasetCollectionElementView,\n\t ListCollectionCreator : ListCollectionCreator,\n\t\n\t collectionCreatorModal : collectionCreatorModal,\n\t listCollectionCreatorModal : listCollectionCreatorModal,\n\t createListCollection : createListCollection\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(2), __webpack_require__(1), __webpack_require__(1)))\n\n/***/ },\n/* 32 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(jQuery, Backbone, $, _) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(42),\n\t __webpack_require__(12),\n\t __webpack_require__(22),\n\t __webpack_require__(6),\n\t __webpack_require__(5)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( LIST_ITEM, STATES, faIconButton, BASE_MVC, _l ){\n\t'use strict';\n\t\n\tvar logNamespace = 'dataset';\n\t/*==============================================================================\n\tTODO:\n\t straighten out state rendering and templates used\n\t inaccessible/STATES.NOT_VIEWABLE is a special case\n\t simplify button rendering\n\t\n\t==============================================================================*/\n\tvar _super = LIST_ITEM.ListItemView;\n\t/** @class Read only list view for either LDDAs, HDAs, or HDADCEs.\n\t * Roughly, any DatasetInstance (and not a raw Dataset).\n\t */\n\tvar DatasetListItemView = _super.extend(\n\t/** @lends DatasetListItemView.prototype */{\n\t _logNamespace : logNamespace,\n\t\n\t className : _super.prototype.className + \" dataset\",\n\t //TODO:?? doesn't exactly match an hda's type_id\n\t id : function(){\n\t return [ 'dataset', this.model.get( 'id' ) ].join( '-' );\n\t },\n\t\n\t /** Set up: instance vars, options, and event handlers */\n\t initialize : function( attributes ){\n\t if( attributes.logger ){ this.logger = this.model.logger = attributes.logger; }\n\t this.log( this + '.initialize:', attributes );\n\t _super.prototype.initialize.call( this, attributes );\n\t\n\t /** where should pages from links be displayed? (default to new tab/window) */\n\t this.linkTarget = attributes.linkTarget || '_blank';\n\t },\n\t\n\t /** event listeners */\n\t _setUpListeners : function(){\n\t _super.prototype._setUpListeners.call( this );\n\t var self = this;\n\t\n\t // re-rendering on any model changes\n\t return self.listenTo( self.model, {\n\t 'change': function( model, options ){\n\t // if the model moved into the ready state and is expanded without details, fetch those details now\n\t if( self.model.changedAttributes().state\n\t && self.model.inReadyState()\n\t && self.expanded\n\t && !self.model.hasDetails() ){\n\t // normally, will render automatically (due to fetch -> change),\n\t // but! setting_metadata sometimes doesn't cause any other changes besides state\n\t // so, not rendering causes it to seem frozen in setting_metadata state\n\t self.model.fetch({ silent : true })\n\t .done( function(){ self.render(); });\n\t\n\t } else {\n\t self.render();\n\t }\n\t }\n\t });\n\t },\n\t\n\t // ......................................................................... expandable\n\t /** In this override, only get details if in the ready state, get rerunnable if in other states.\n\t * Note: fetch with no 'change' event triggering to prevent automatic rendering.\n\t */\n\t _fetchModelDetails : function(){\n\t var view = this;\n\t if( view.model.inReadyState() && !view.model.hasDetails() ){\n\t return view.model.fetch({ silent: true });\n\t }\n\t return jQuery.when();\n\t },\n\t\n\t // ......................................................................... removal\n\t /** Remove this view's html from the DOM and remove all event listeners.\n\t * @param {Number or String} speed jq effect speed\n\t * @param {Function} callback an optional function called when removal is done (scoped to this view)\n\t */\n\t remove : function( speed, callback ){\n\t var view = this;\n\t speed = speed || this.fxSpeed;\n\t this.$el.fadeOut( speed, function(){\n\t Backbone.View.prototype.remove.call( view );\n\t if( callback ){ callback.call( view ); }\n\t });\n\t },\n\t\n\t // ......................................................................... rendering\n\t /* TODO:\n\t dataset states are the issue primarily making dataset rendering complex\n\t each state should have it's own way of displaying/set of details\n\t often with different actions that can be applied\n\t throw in deleted/purged/visible and things get complicated easily\n\t I've considered (a couple of times) - creating a view for each state\n\t - but recreating the view during an update...seems wrong\n\t */\n\t /** In this override, add the dataset state as a class for use with state-based CSS */\n\t _swapNewRender : function( $newRender ){\n\t _super.prototype._swapNewRender.call( this, $newRender );\n\t if( this.model.has( 'state' ) ){\n\t this.$el.addClass( 'state-' + this.model.get( 'state' ) );\n\t }\n\t return this.$el;\n\t },\n\t\n\t // ................................................................................ titlebar\n\t /** In this override, add the dataset display button. */\n\t _renderPrimaryActions : function(){\n\t // render just the display for read-only\n\t return [ this._renderDisplayButton() ];\n\t },\n\t\n\t /** Render icon-button to display dataset data */\n\t _renderDisplayButton : function(){\n\t // don't show display if not viewable or not accessible\n\t var state = this.model.get( 'state' );\n\t if( ( state === STATES.NOT_VIEWABLE )\n\t || ( state === STATES.DISCARDED )\n\t || ( !this.model.get( 'accessible' ) ) ){\n\t return null;\n\t }\n\t\n\t var displayBtnData = {\n\t target : this.linkTarget,\n\t classes : 'display-btn'\n\t };\n\t\n\t // show a disabled display if the data's been purged\n\t if( this.model.get( 'purged' ) ){\n\t displayBtnData.disabled = true;\n\t displayBtnData.title = _l( 'Cannot display datasets removed from disk' );\n\t\n\t // disable if still uploading\n\t } else if( state === STATES.UPLOAD ){\n\t displayBtnData.disabled = true;\n\t displayBtnData.title = _l( 'This dataset must finish uploading before it can be viewed' );\n\t\n\t // disable if still new\n\t } else if( state === STATES.NEW ){\n\t displayBtnData.disabled = true;\n\t displayBtnData.title = _l( 'This dataset is not yet viewable' );\n\t\n\t } else {\n\t displayBtnData.title = _l( 'View data' );\n\t\n\t // default link for dataset\n\t displayBtnData.href = this.model.urls.display;\n\t\n\t // add frame manager option onclick event\n\t var self = this;\n\t displayBtnData.onclick = function( ev ){\n\t if (Galaxy.frame && Galaxy.frame.active) {\n\t // Add dataset to frames.\n\t Galaxy.frame.addDataset(self.model.get('id'));\n\t ev.preventDefault();\n\t }\n\t };\n\t }\n\t displayBtnData.faIcon = 'fa-eye';\n\t return faIconButton( displayBtnData );\n\t },\n\t\n\t // ......................................................................... rendering details\n\t /** Render the enclosing div of the hda body and, if expanded, the html in the body\n\t * @returns {jQuery} rendered DOM\n\t */\n\t _renderDetails : function(){\n\t //TODO: generalize to be allow different details for each state\n\t\n\t // no access - render nothing but a message\n\t if( this.model.get( 'state' ) === STATES.NOT_VIEWABLE ){\n\t return $( this.templates.noAccess( this.model.toJSON(), this ) );\n\t }\n\t\n\t var $details = _super.prototype._renderDetails.call( this );\n\t $details.find( '.actions .left' ).empty().append( this._renderSecondaryActions() );\n\t $details.find( '.summary' ).html( this._renderSummary() )\n\t .prepend( this._renderDetailMessages() );\n\t $details.find( '.display-applications' ).html( this._renderDisplayApplications() );\n\t\n\t this._setUpBehaviors( $details );\n\t return $details;\n\t },\n\t\n\t /** Defer to the appropo summary rendering fn based on state */\n\t _renderSummary : function(){\n\t var json = this.model.toJSON(),\n\t summaryRenderFn = this.templates.summaries[ json.state ];\n\t summaryRenderFn = summaryRenderFn || this.templates.summaries.unknown;\n\t return summaryRenderFn( json, this );\n\t },\n\t\n\t /** Render messages to be displayed only when the details are shown */\n\t _renderDetailMessages : function(){\n\t var view = this,\n\t $warnings = $( '
                        ' ),\n\t json = view.model.toJSON();\n\t //TODO:! unordered (map)\n\t _.each( view.templates.detailMessages, function( templateFn ){\n\t $warnings.append( $( templateFn( json, view ) ) );\n\t });\n\t return $warnings;\n\t },\n\t\n\t /** Render the external display application links */\n\t _renderDisplayApplications : function(){\n\t if( this.model.isDeletedOrPurged() ){ return ''; }\n\t // render both old and new display apps using the same template\n\t return [\n\t this.templates.displayApplications( this.model.get( 'display_apps' ), this ),\n\t this.templates.displayApplications( this.model.get( 'display_types' ), this )\n\t ].join( '' );\n\t },\n\t\n\t // ......................................................................... secondary/details actions\n\t /** A series of links/buttons for less commonly used actions: re-run, info, etc. */\n\t _renderSecondaryActions : function(){\n\t this.debug( '_renderSecondaryActions' );\n\t switch( this.model.get( 'state' ) ){\n\t case STATES.NOT_VIEWABLE:\n\t return [];\n\t case STATES.OK:\n\t case STATES.FAILED_METADATA:\n\t case STATES.ERROR:\n\t return [ this._renderDownloadButton(), this._renderShowParamsButton() ];\n\t }\n\t return [ this._renderShowParamsButton() ];\n\t },\n\t\n\t /** Render icon-button to show the input and output (stdout/err) for the job that created this.\n\t * @returns {jQuery} rendered DOM\n\t */\n\t _renderShowParamsButton : function(){\n\t // gen. safe to show in all cases\n\t return faIconButton({\n\t title : _l( 'View details' ),\n\t classes : 'params-btn',\n\t href : this.model.urls.show_params,\n\t target : this.linkTarget,\n\t faIcon : 'fa-info-circle',\n\t onclick : function( ev ) {\n\t if ( Galaxy.frame && Galaxy.frame.active ) {\n\t Galaxy.frame.add( { title: 'Dataset details', url: this.href } );\n\t ev.preventDefault();\n\t ev.stopPropagation();\n\t }\n\t }\n\t });\n\t },\n\t\n\t /** Render icon-button/popupmenu to download the data (and/or the associated meta files (bai, etc.)) for this.\n\t * @returns {jQuery} rendered DOM\n\t */\n\t _renderDownloadButton : function(){\n\t // don't show anything if the data's been purged\n\t if( this.model.get( 'purged' ) || !this.model.hasData() ){ return null; }\n\t\n\t // return either: a popupmenu with links to download assoc. meta files (if there are meta files)\n\t // or a single download icon-button (if there are no meta files)\n\t if( !_.isEmpty( this.model.get( 'meta_files' ) ) ){\n\t return this._renderMetaFileDownloadButton();\n\t }\n\t\n\t return $([\n\t '',\n\t '',\n\t ''\n\t ].join( '' ));\n\t },\n\t\n\t /** Render the download button which opens a dropdown with links to download assoc. meta files (indeces, etc.) */\n\t _renderMetaFileDownloadButton : function(){\n\t var urls = this.model.urls;\n\t return $([\n\t '
                        ',\n\t '',\n\t '',\n\t '',\n\t '',\n\t '
                        '\n\t ].join( '\\n' ));\n\t },\n\t\n\t // ......................................................................... misc\n\t events : _.extend( _.clone( _super.prototype.events ), {\n\t 'click .display-btn' : function( ev ){ this.trigger( 'display', this, ev ); },\n\t 'click .params-btn' : function( ev ){ this.trigger( 'params', this, ev ); },\n\t 'click .download-btn' : function( ev ){ this.trigger( 'download', this, ev ); }\n\t }),\n\t\n\t // ......................................................................... misc\n\t /** String representation */\n\t toString : function(){\n\t var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n\t return 'DatasetListItemView(' + modelString + ')';\n\t }\n\t});\n\t\n\t// ............................................................................ TEMPLATES\n\t/** underscore templates */\n\tDatasetListItemView.prototype.templates = (function(){\n\t//TODO: move to require text! plugin\n\t\n\t var warnings = _.extend( {}, _super.prototype.templates.warnings, {\n\t failed_metadata : BASE_MVC.wrapTemplate([\n\t // failed metadata is rendered as a warning on an otherwise ok dataset view\n\t '<% if( model.state === \"failed_metadata\" ){ %>',\n\t '
                        ',\n\t _l( 'An error occurred setting the metadata for this dataset' ),\n\t '
                        ',\n\t '<% } %>'\n\t ]),\n\t error : BASE_MVC.wrapTemplate([\n\t // error during index fetch - show error on dataset\n\t '<% if( model.error ){ %>',\n\t '
                        ',\n\t _l( 'There was an error getting the data for this dataset' ), ': <%- model.error %>',\n\t '
                        ',\n\t '<% } %>'\n\t ]),\n\t purged : BASE_MVC.wrapTemplate([\n\t '<% if( model.purged ){ %>',\n\t '
                        ',\n\t _l( 'This dataset has been deleted and removed from disk' ),\n\t '
                        ',\n\t '<% } %>'\n\t ]),\n\t deleted : BASE_MVC.wrapTemplate([\n\t // deleted not purged\n\t '<% if( model.deleted && !model.purged ){ %>',\n\t '
                        ',\n\t _l( 'This dataset has been deleted' ),\n\t '
                        ',\n\t '<% } %>'\n\t ])\n\t\n\t //NOTE: hidden warning is only needed for HDAs\n\t });\n\t\n\t var detailsTemplate = BASE_MVC.wrapTemplate([\n\t '
                        ',\n\t '
                        ',\n\t\n\t '
                        ',\n\t '
                        ',\n\t '
                        ',\n\t '
                        ',\n\t\n\t // do not display tags, annotation, display apps, or peek when deleted\n\t '<% if( !dataset.deleted && !dataset.purged ){ %>',\n\t '
                        ',\n\t '
                        ',\n\t\n\t '
                        ',\n\t\n\t '<% if( dataset.peek ){ %>',\n\t '
                        <%= dataset.peek %>
                        ',\n\t '<% } %>',\n\t '<% } %>',\n\t '
                        '\n\t ], 'dataset' );\n\t\n\t var noAccessTemplate = BASE_MVC.wrapTemplate([\n\t '
                        ',\n\t '
                        ',\n\t _l( 'You do not have permission to view this dataset' ),\n\t '
                        ',\n\t '
                        '\n\t ], 'dataset' );\n\t\n\t//TODO: still toooooooooooooo complex - rework\n\t var summaryTemplates = {};\n\t summaryTemplates[ STATES.OK ] = summaryTemplates[ STATES.FAILED_METADATA ] = BASE_MVC.wrapTemplate([\n\t '<% if( dataset.misc_blurb ){ %>',\n\t '
                        ',\n\t '<%- dataset.misc_blurb %>',\n\t '
                        ',\n\t '<% } %>',\n\t\n\t '<% if( dataset.file_ext ){ %>',\n\t '
                        ',\n\t '',\n\t '<%- dataset.file_ext %>',\n\t '
                        ',\n\t '<% } %>',\n\t\n\t '<% if( dataset.metadata_dbkey ){ %>',\n\t '
                        ',\n\t '',\n\t '',\n\t '<%- dataset.metadata_dbkey %>',\n\t '',\n\t '
                        ',\n\t '<% } %>',\n\t\n\t '<% if( dataset.misc_info ){ %>',\n\t '
                        ',\n\t '<%- dataset.misc_info %>',\n\t '
                        ',\n\t '<% } %>'\n\t ], 'dataset' );\n\t summaryTemplates[ STATES.NEW ] = BASE_MVC.wrapTemplate([\n\t '
                        ', _l( 'This is a new dataset and not all of its data are available yet' ), '
                        '\n\t ], 'dataset' );\n\t summaryTemplates[ STATES.NOT_VIEWABLE ] = BASE_MVC.wrapTemplate([\n\t '
                        ', _l( 'You do not have permission to view this dataset' ), '
                        '\n\t ], 'dataset' );\n\t summaryTemplates[ STATES.DISCARDED ] = BASE_MVC.wrapTemplate([\n\t '
                        ', _l( 'The job creating this dataset was cancelled before completion' ), '
                        '\n\t ], 'dataset' );\n\t summaryTemplates[ STATES.QUEUED ] = BASE_MVC.wrapTemplate([\n\t '
                        ', _l( 'This job is waiting to run' ), '
                        '\n\t ], 'dataset' );\n\t summaryTemplates[ STATES.RUNNING ] = BASE_MVC.wrapTemplate([\n\t '
                        ', _l( 'This job is currently running' ), '
                        '\n\t ], 'dataset' );\n\t summaryTemplates[ STATES.UPLOAD ] = BASE_MVC.wrapTemplate([\n\t '
                        ', _l( 'This dataset is currently uploading' ), '
                        '\n\t ], 'dataset' );\n\t summaryTemplates[ STATES.SETTING_METADATA ] = BASE_MVC.wrapTemplate([\n\t '
                        ', _l( 'Metadata is being auto-detected' ), '
                        '\n\t ], 'dataset' );\n\t summaryTemplates[ STATES.PAUSED ] = BASE_MVC.wrapTemplate([\n\t '
                        ', _l( 'This job is paused. Use the \"Resume Paused Jobs\" in the history menu to resume' ), '
                        '\n\t ], 'dataset' );\n\t summaryTemplates[ STATES.ERROR ] = BASE_MVC.wrapTemplate([\n\t '<% if( !dataset.purged ){ %>',\n\t '
                        <%- dataset.misc_blurb %>
                        ',\n\t '<% } %>',\n\t '', _l( 'An error occurred with this dataset' ), ':',\n\t '
                        <%- dataset.misc_info %>
                        '\n\t ], 'dataset' );\n\t summaryTemplates[ STATES.EMPTY ] = BASE_MVC.wrapTemplate([\n\t '
                        ', _l( 'No data' ), ': <%- dataset.misc_blurb %>
                        '\n\t ], 'dataset' );\n\t summaryTemplates.unknown = BASE_MVC.wrapTemplate([\n\t '
                        Error: unknown dataset state: \"<%- dataset.state %>\"
                        '\n\t ], 'dataset' );\n\t\n\t // messages to be displayed only within the details section ('below the fold')\n\t var detailMessageTemplates = {\n\t resubmitted : BASE_MVC.wrapTemplate([\n\t // deleted not purged\n\t '<% if( model.resubmitted ){ %>',\n\t '
                        ',\n\t _l( 'The job creating this dataset has been resubmitted' ),\n\t '
                        ',\n\t '<% } %>'\n\t ])\n\t };\n\t\n\t // this is applied to both old and new style display apps\n\t var displayApplicationsTemplate = BASE_MVC.wrapTemplate([\n\t '<% _.each( apps, function( app ){ %>',\n\t '
                        ',\n\t '<%- app.label %> ',\n\t '',\n\t '<% _.each( app.links, function( link ){ %>',\n\t '\" href=\"<%- link.href %>\">',\n\t '<% print( _l( link.text ) ); %>',\n\t ' ',\n\t '<% }); %>',\n\t '',\n\t '
                        ',\n\t '<% }); %>'\n\t ], 'apps' );\n\t\n\t return _.extend( {}, _super.prototype.templates, {\n\t warnings : warnings,\n\t details : detailsTemplate,\n\t noAccess : noAccessTemplate,\n\t summaries : summaryTemplates,\n\t detailMessages : detailMessageTemplates,\n\t displayApplications : displayApplicationsTemplate\n\t });\n\t}());\n\t\n\t\n\t// ============================================================================\n\t return {\n\t DatasetListItemView : DatasetListItemView\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(1), __webpack_require__(3), __webpack_require__(1), __webpack_require__(2)))\n\n/***/ },\n/* 33 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, $, _) {/* This class maps the form dom to an api compatible javascript dictionary. */\n\t!(__WEBPACK_AMD_DEFINE_ARRAY__ = [ __webpack_require__(4) ], __WEBPACK_AMD_DEFINE_RESULT__ = function( Utils ) {\n\t var Manager = Backbone.Model.extend({\n\t initialize: function( app ) {\n\t this.app = app;\n\t },\n\t\n\t /** Creates a checksum. */\n\t checksum: function() {\n\t var sum = '';\n\t var self = this;\n\t this.app.section.$el.find( '.section-row' ).each( function() {\n\t var id = $(this).attr( 'id' );\n\t var field = self.app.field_list[ id ];\n\t if ( field ) {\n\t sum += id + ':' + JSON.stringify( field.value && field.value() ) + ':' + field.collapsed + ';';\n\t }\n\t });\n\t return sum;\n\t },\n\t\n\t /** Convert dom into a dictionary of flat id/value pairs used e.g. on job submission. */\n\t create: function() {\n\t var self = this;\n\t\n\t // get raw dictionary from dom\n\t var dict = {};\n\t this._iterate( this.app.section.$el, dict );\n\t\n\t // add to result dictionary, label elements\n\t var result_dict = {};\n\t this.flat_dict = {};\n\t function add( flat_id, input_id, input_value ) {\n\t self.flat_dict[ flat_id ] = input_id;\n\t result_dict[ flat_id ] = input_value;\n\t self.app.element_list[ input_id ] && self.app.element_list[ input_id ].$el.attr( 'tour_id', flat_id );\n\t }\n\t // converter between raw dictionary and job dictionary\n\t function convert( identifier, head ) {\n\t for ( var index in head ) {\n\t var node = head[ index ];\n\t if ( node.input ) {\n\t var input = node.input;\n\t var flat_id = identifier;\n\t if ( identifier != '' ) {\n\t flat_id += '|';\n\t }\n\t flat_id += input.name;\n\t switch ( input.type ) {\n\t case 'repeat':\n\t var section_label = 'section-';\n\t var block_indices = [];\n\t var block_prefix = null;\n\t for ( var block_label in node ) {\n\t var pos = block_label.indexOf( section_label );\n\t if ( pos != -1 ) {\n\t pos += section_label.length;\n\t block_indices.push( parseInt( block_label.substr( pos ) ));\n\t if ( !block_prefix ) {\n\t block_prefix = block_label.substr( 0, pos );\n\t }\n\t }\n\t }\n\t block_indices.sort( function( a, b ) { return a - b; });\n\t var index = 0;\n\t for ( var i in block_indices ) {\n\t convert( flat_id + '_' + index++, node[ block_prefix + block_indices[ i ] ]);\n\t }\n\t break;\n\t case 'conditional':\n\t var value = self.app.field_list[ input.id ].value();\n\t add( flat_id + '|' + input.test_param.name, input.id, value );\n\t var selectedCase = matchCase( input, value );\n\t if ( selectedCase != -1 ) {\n\t convert( flat_id, head[ input.id + '-section-' + selectedCase ] );\n\t }\n\t break;\n\t case 'section':\n\t convert( !input.flat && flat_id || '', node );\n\t break;\n\t default:\n\t var field = self.app.field_list[ input.id ];\n\t if ( field && field.value ) {\n\t var value = field.value();\n\t if ( input.ignore === undefined || input.ignore != value ) {\n\t if ( field.collapsed && input.collapsible_value ) {\n\t value = input.collapsible_value;\n\t }\n\t add( flat_id, input.id, value );\n\t if ( input.payload ) {\n\t for ( var p_id in input.payload ) {\n\t add( p_id, input.id, input.payload[ p_id ] );\n\t }\n\t }\n\t }\n\t }\n\t }\n\t }\n\t }\n\t }\n\t convert( '', dict );\n\t return result_dict;\n\t },\n\t\n\t /** Matches flat ids to corresponding input element\n\t * @param{string} flat_id - Flat input id to be looked up.\n\t */\n\t match: function ( flat_id ) {\n\t return this.flat_dict && this.flat_dict[ flat_id ];\n\t },\n\t\n\t /** Match conditional values to selected cases\n\t */\n\t matchCase: function( input, value ) {\n\t return matchCase( input, value );\n\t },\n\t\n\t /** Matches a new tool model to the current input elements e.g. used to update dynamic options\n\t */\n\t matchModel: function( model, callback ) {\n\t var self = this;\n\t visitInputs( model.inputs, function( input, name ) {\n\t self.flat_dict[ name ] && callback ( input, self.flat_dict[ name ] );\n\t });\n\t },\n\t\n\t /** Matches identifier from api response to input elements e.g. used to display validation errors\n\t */\n\t matchResponse: function( response ) {\n\t var result = {};\n\t var self = this;\n\t function search ( id, head ) {\n\t if ( typeof head === 'string' ) {\n\t var input_id = self.flat_dict[ id ];\n\t input_id && ( result[ input_id ] = head );\n\t } else {\n\t for ( var i in head ) {\n\t var new_id = i;\n\t if ( id !== '' ) {\n\t var separator = '|';\n\t if ( head instanceof Array ) {\n\t separator = '_';\n\t }\n\t new_id = id + separator + new_id;\n\t }\n\t search ( new_id, head[ i ] );\n\t }\n\t }\n\t }\n\t search( '', response );\n\t return result;\n\t },\n\t\n\t /** Map dom tree to dictionary tree with input elements.\n\t */\n\t _iterate: function( parent, dict ) {\n\t var self = this;\n\t var children = $( parent ).children();\n\t children.each( function() {\n\t var child = this;\n\t var id = $( child ).attr( 'id' );\n\t if ( $( child ).hasClass( 'section-row' ) ) {\n\t var input = self.app.input_list[ id ];\n\t dict[ id ] = ( input && { input : input } ) || {};\n\t self._iterate( child, dict[ id ] );\n\t } else {\n\t self._iterate( child, dict );\n\t }\n\t });\n\t }\n\t });\n\t\n\t /** Match conditional values to selected cases\n\t * @param{dict} input - Definition of conditional input parameter\n\t * @param{dict} value - Current value\n\t */\n\t var matchCase = function( input, value ) {\n\t if ( input.test_param.type == 'boolean' ) {\n\t if ( value == 'true' ) {\n\t value = input.test_param.truevalue || 'true';\n\t } else {\n\t value = input.test_param.falsevalue || 'false';\n\t }\n\t }\n\t for ( var i in input.cases ) {\n\t if ( input.cases[ i ].value == value ) {\n\t return i;\n\t }\n\t }\n\t return -1;\n\t };\n\t\n\t /** Visits tool inputs\n\t * @param{dict} inputs - Nested dictionary of input elements\n\t * @param{dict} callback - Called with the mapped dictionary object and corresponding model node\n\t */\n\t var visitInputs = function( inputs, callback, prefix, context ) {\n\t context = $.extend( true, {}, context );\n\t _.each( inputs, function ( input ) {\n\t if ( input && input.type && input.name ) {\n\t context[ input.name ] = input;\n\t }\n\t });\n\t for ( var key in inputs ) {\n\t var node = inputs[ key ];\n\t node.name = node.name || key;\n\t var name = prefix ? prefix + '|' + node.name : node.name;\n\t switch ( node.type ) {\n\t case 'repeat':\n\t _.each( node.cache, function( cache, j ) {\n\t visitInputs( cache, callback, name + '_' + j, context );\n\t });\n\t break;\n\t case 'conditional':\n\t if ( node.test_param ) {\n\t callback( node.test_param, name + '|' + node.test_param.name, context );\n\t var selectedCase = matchCase( node, node.test_param.value );\n\t if ( selectedCase != -1 ) {\n\t visitInputs( node.cases[ selectedCase ].inputs, callback, name, context );\n\t } else {\n\t Galaxy.emit.debug( 'form-data::visitInputs() - Invalid case for ' + name + '.' );\n\t }\n\t } else {\n\t Galaxy.emit.debug( 'form-data::visitInputs() - Conditional test parameter missing for ' + name + '.' );\n\t }\n\t break;\n\t case 'section':\n\t visitInputs( node.inputs, callback, name, context )\n\t break;\n\t default:\n\t callback( node, name, context );\n\t }\n\t }\n\t };\n\t\n\t return {\n\t Manager : Manager,\n\t visitInputs : visitInputs\n\t }\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(1), __webpack_require__(2)))\n\n/***/ },\n/* 34 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, $, _) {/**\n\t This class creates a form input element wrapper\n\t*/\n\t!(__WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_RESULT__ = function() {\n\t return Backbone.View.extend({\n\t initialize: function( app, options ) {\n\t this.app = app;\n\t this.app_options = app.options || {};\n\t this.field = options && options.field || new Backbone.View();\n\t this.model = options && options.model || new Backbone.Model({\n\t text_enable : this.app_options.text_enable || 'Enable',\n\t text_disable : this.app_options.text_disable || 'Disable',\n\t cls_enable : this.app_options.cls_enable || 'fa fa-caret-square-o-down',\n\t cls_disable : this.app_options.cls_disable || 'fa fa-caret-square-o-up'\n\t }).set( options );\n\t\n\t // set element and link components\n\t this.setElement( this._template() );\n\t this.$field = this.$( '.ui-form-field' );\n\t this.$info = this.$( '.ui-form-info' );\n\t this.$preview = this.$( '.ui-form-preview' );\n\t this.$collapsible = this.$( '.ui-form-collapsible' );\n\t this.$collapsible_text = this.$( '.ui-form-collapsible-text' );\n\t this.$collapsible_icon = this.$( '.ui-form-collapsible-icon' );\n\t this.$title = this.$( '.ui-form-title' );\n\t this.$title_text = this.$( '.ui-form-title-text' );\n\t this.$error_text = this.$( '.ui-form-error-text' );\n\t this.$error = this.$( '.ui-form-error' );\n\t this.$backdrop = this.$( '.ui-form-backdrop' );\n\t\n\t // add field element\n\t this.$field.prepend( this.field.$el );\n\t\n\t // decide wether to expand or collapse fields\n\t var collapsible_value = this.model.get( 'collapsible_value' );\n\t this.field.collapsed = collapsible_value !== undefined && JSON.stringify( this.model.get( 'value' ) ) == JSON.stringify( collapsible_value );\n\t this.listenTo( this.model, 'change', this.render, this );\n\t this.render();\n\t\n\t // add click handler\n\t var self = this;\n\t this.$collapsible.on( 'click', function() {\n\t self.field.collapsed = !self.field.collapsed;\n\t app.trigger && app.trigger( 'change' );\n\t self.render();\n\t });\n\t },\n\t\n\t /** Set backdrop for input element\n\t */\n\t backdrop: function() {\n\t this.model.set( 'backdrop', true );\n\t },\n\t\n\t /** Set error text\n\t */\n\t error: function( text ) {\n\t this.model.set( 'error_text', text );\n\t },\n\t\n\t /** Reset this view\n\t */\n\t reset: function() {\n\t this.model.set( 'error_text', null );\n\t },\n\t\n\t render: function() {\n\t // render help\n\t $( '.tooltip' ).hide();\n\t var help_text = this.model.get( 'help', '' );\n\t var help_argument = this.model.get( 'argument' );\n\t if ( help_argument && help_text.indexOf( '(' + help_argument + ')' ) == -1 ) {\n\t help_text += ' (' + help_argument + ')';\n\t }\n\t this.$info.html( help_text );\n\t // render visibility\n\t this.$el[ this.model.get( 'hidden' ) ? 'hide' : 'show' ]();\n\t // render preview view for collapsed fields\n\t this.$preview[ ( this.field.collapsed && this.model.get( 'collapsible_preview' ) || this.model.get( 'disabled' ) ) ? 'show' : 'hide' ]()\n\t .html( _.escape( this.model.get( 'text_value' ) ) );\n\t // render error messages\n\t var error_text = this.model.get( 'error_text' );\n\t this.$error[ error_text ? 'show' : 'hide' ]();\n\t this.$el[ error_text ? 'addClass' : 'removeClass' ]( 'ui-error' );\n\t this.$error_text.html( error_text );\n\t // render backdrop\n\t this.$backdrop[ this.model.get( 'backdrop' ) ? 'show' : 'hide' ]();\n\t // render input field\n\t this.field.collapsed || this.model.get( 'disabled' ) ? this.$field.hide() : this.$field.show();\n\t // render input field color and style\n\t this.field.model && this.field.model.set( { 'color': this.model.get( 'color' ), 'style': this.model.get( 'style' ) } );\n\t // render collapsible options\n\t if ( !this.model.get( 'disabled' ) && this.model.get( 'collapsible_value' ) !== undefined ) {\n\t var collapsible_state = this.field.collapsed ? 'enable' : 'disable';\n\t this.$title_text.hide();\n\t this.$collapsible.show();\n\t this.$collapsible_text.text( this.model.get( 'label' ) );\n\t this.$collapsible_icon.removeClass().addClass( 'icon' )\n\t .addClass( this.model.get( 'cls_' + collapsible_state ) )\n\t .attr( 'data-original-title', this.model.get( 'text_' + collapsible_state ) )\n\t .tooltip( { placement: 'bottom' } );\n\t } else {\n\t this.$title_text.show().text( this.model.get( 'label' ) );\n\t this.$collapsible.hide();\n\t }\n\t },\n\t\n\t _template: function() {\n\t return $( '
                        ' ).addClass( 'ui-form-element' )\n\t .append( $( '
                        ' ).addClass( 'ui-form-error ui-error' )\n\t .append( $( '' ).addClass( 'fa fa-arrow-down' ) )\n\t .append( $( '' ).addClass( 'ui-form-error-text' ) )\n\t )\n\t .append( $( '
                        ' ).addClass( 'ui-form-title' )\n\t .append( $( '
                        ' ).addClass( 'ui-form-collapsible' )\n\t .append( $( '' ).addClass( 'ui-form-collapsible-icon' ) )\n\t .append( $( '' ).addClass( 'ui-form-collapsible-text' ) )\n\t )\n\t .append( $( '' ).addClass( 'ui-form-title-text' ) )\n\t )\n\t .append( $( '
                        ' ).addClass( 'ui-form-field' )\n\t .append( $( '' ).addClass( 'ui-form-info' ) )\n\t .append( $( '
                        ' ).addClass( 'ui-form-backdrop' ) )\n\t )\n\t .append( $( '
                        ' ).addClass( 'ui-form-preview' ) );\n\t }\n\t });\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(1), __webpack_require__(2)))\n\n/***/ },\n/* 35 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, _, $) {/**\n\t This class creates input elements. New input parameter types should be added to the types dictionary.\n\t*/\n\t!(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(4),\n\t __webpack_require__(7),\n\t __webpack_require__(48),\n\t __webpack_require__(50),\n\t __webpack_require__(49),\n\t __webpack_require__(46)], __WEBPACK_AMD_DEFINE_RESULT__ = function( Utils, Ui, SelectContent, SelectLibrary, SelectFtp, ColorPicker ) {\n\t\n\t // create form view\n\t return Backbone.Model.extend({\n\t /** Available parameter types */\n\t types: {\n\t 'text' : '_fieldText',\n\t 'select' : '_fieldSelect',\n\t 'data_column' : '_fieldSelect',\n\t 'genomebuild' : '_fieldSelect',\n\t 'data' : '_fieldData',\n\t 'data_collection' : '_fieldData',\n\t 'integer' : '_fieldSlider',\n\t 'float' : '_fieldSlider',\n\t 'boolean' : '_fieldBoolean',\n\t 'drill_down' : '_fieldDrilldown',\n\t 'color' : '_fieldColor',\n\t 'hidden' : '_fieldHidden',\n\t 'hidden_data' : '_fieldHidden',\n\t 'baseurl' : '_fieldHidden',\n\t 'library_data' : '_fieldLibrary',\n\t 'ftpfile' : '_fieldFtp'\n\t },\n\t\n\t /** Returns an input field for a given field type */\n\t create: function( input_def ) {\n\t var fieldClass = this.types[ input_def.type ];\n\t var field = typeof( this[ fieldClass ] ) === 'function' ? this[ fieldClass ].call( this, input_def ) : null;\n\t if ( !field ) {\n\t field = input_def.options ? this._fieldSelect( input_def ) : this._fieldText( input_def );\n\t Galaxy.emit.debug('form-parameters::_addRow()', 'Auto matched field type (' + input_def.type + ').');\n\t }\n\t input_def.value === undefined && ( input_def.value = null );\n\t field.value( input_def.value );\n\t return field;\n\t },\n\t\n\t /** Data input field */\n\t _fieldData: function( input_def ) {\n\t return new SelectContent.View({\n\t id : 'field-' + input_def.id,\n\t extensions : input_def.extensions,\n\t optional : input_def.optional,\n\t multiple : input_def.multiple,\n\t type : input_def.type,\n\t flavor : input_def.flavor,\n\t data : input_def.options,\n\t onchange : input_def.onchange\n\t });\n\t },\n\t\n\t /** Select/Checkbox/Radio options field */\n\t _fieldSelect: function ( input_def ) {\n\t // show text field e.g. in workflow editor\n\t if( input_def.is_workflow ) {\n\t return this._fieldText( input_def );\n\t }\n\t\n\t // customize properties\n\t if ( input_def.type == 'data_column' ) {\n\t input_def.error_text = 'Missing columns in referenced dataset.'\n\t }\n\t\n\t // identify available options\n\t var data = input_def.data;\n\t if( !data ) {\n\t data = [];\n\t _.each( input_def.options, function( option ) {\n\t data.push( { label: option[ 0 ], value: option[ 1 ] } );\n\t });\n\t }\n\t\n\t // identify display type\n\t var SelectClass = Ui.Select;\n\t switch ( input_def.display ) {\n\t case 'checkboxes':\n\t SelectClass = Ui.Checkbox;\n\t break;\n\t case 'radio':\n\t SelectClass = Ui.Radio;\n\t break;\n\t case 'radiobutton':\n\t SelectClass = Ui.RadioButton;\n\t break;\n\t }\n\t\n\t // create select field\n\t return new SelectClass.View({\n\t id : 'field-' + input_def.id,\n\t data : data,\n\t error_text : input_def.error_text || 'No options available',\n\t multiple : input_def.multiple,\n\t optional : input_def.optional,\n\t onchange : input_def.onchange,\n\t searchable : input_def.flavor !== 'workflow'\n\t });\n\t },\n\t\n\t /** Drill down options field */\n\t _fieldDrilldown: function ( input_def ) {\n\t // show text field e.g. in workflow editor\n\t if( input_def.is_workflow ) {\n\t return this._fieldText( input_def );\n\t }\n\t\n\t // create drill down field\n\t return new Ui.Drilldown.View({\n\t id : 'field-' + input_def.id,\n\t data : input_def.options,\n\t display : input_def.display,\n\t optional : input_def.optional,\n\t onchange : input_def.onchange\n\t });\n\t },\n\t\n\t /** Text input field */\n\t _fieldText: function( input_def ) {\n\t // field replaces e.g. a select field\n\t if ( input_def.options && input_def.data ) {\n\t input_def.area = input_def.multiple;\n\t if ( Utils.isEmpty( input_def.value ) ) {\n\t input_def.value = null;\n\t } else {\n\t if ( $.isArray( input_def.value ) ) {\n\t var str_value = '';\n\t for ( var i in input_def.value ) {\n\t str_value += String( input_def.value[ i ] );\n\t if ( !input_def.multiple ) {\n\t break;\n\t }\n\t str_value += '\\n';\n\t }\n\t input_def.value = str_value;\n\t }\n\t }\n\t }\n\t // create input element\n\t return new Ui.Input({\n\t id : 'field-' + input_def.id,\n\t area : input_def.area,\n\t placeholder : input_def.placeholder,\n\t onchange : input_def.onchange\n\t });\n\t },\n\t\n\t /** Slider field */\n\t _fieldSlider: function( input_def ) {\n\t return new Ui.Slider.View({\n\t id : 'field-' + input_def.id,\n\t precise : input_def.type == 'float',\n\t is_workflow : input_def.is_workflow,\n\t min : input_def.min,\n\t max : input_def.max,\n\t onchange : input_def.onchange\n\t });\n\t },\n\t\n\t /** Hidden field */\n\t _fieldHidden: function( input_def ) {\n\t return new Ui.Hidden({\n\t id : 'field-' + input_def.id,\n\t info : input_def.info\n\t });\n\t },\n\t\n\t /** Boolean field */\n\t _fieldBoolean: function( input_def ) {\n\t return new Ui.RadioButton.View({\n\t id : 'field-' + input_def.id,\n\t data : [ { label : 'Yes', value : 'true' },\n\t { label : 'No', value : 'false' }],\n\t onchange : input_def.onchange\n\t });\n\t },\n\t\n\t /** Color picker field */\n\t _fieldColor: function( input_def ) {\n\t return new ColorPicker({\n\t id : 'field-' + input_def.id,\n\t onchange : input_def.onchange\n\t });\n\t },\n\t\n\t /** Library dataset field */\n\t _fieldLibrary: function( input_def ) {\n\t return new SelectLibrary.View({\n\t id : 'field-' + input_def.id,\n\t optional : input_def.optional,\n\t multiple : input_def.multiple,\n\t onchange : input_def.onchange\n\t });\n\t },\n\t\n\t /** FTP file field */\n\t _fieldFtp: function( input_def ) {\n\t return new SelectFtp.View({\n\t id : 'field-' + input_def.id,\n\t optional : input_def.optional,\n\t multiple : input_def.multiple,\n\t onchange : input_def.onchange\n\t });\n\t }\n\t });\n\t\n\t return {\n\t View: View\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(2), __webpack_require__(1)))\n\n/***/ },\n/* 36 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, $, _) {/** This class creates a ui component which enables the dynamic creation of portlets */\n\t!(__WEBPACK_AMD_DEFINE_ARRAY__ = [ __webpack_require__(4), __webpack_require__(9), __webpack_require__(7) ], __WEBPACK_AMD_DEFINE_RESULT__ = function( Utils, Portlet, Ui ) {\n\t var View = Backbone.View.extend({\n\t initialize: function( options ) {\n\t this.list = {};\n\t this.options = Utils.merge( options, {\n\t title : 'Repeat',\n\t empty_text : 'Not available.',\n\t max : null,\n\t min : null\n\t });\n\t this.button_new = new Ui.ButtonIcon({\n\t icon : 'fa-plus',\n\t title : 'Insert ' + this.options.title,\n\t tooltip : 'Add new ' + this.options.title + ' block',\n\t floating: 'clear',\n\t cls : 'ui-button-icon form-repeat-add',\n\t onclick : function() { options.onnew && options.onnew() }\n\t });\n\t this.setElement( $( '
                        ' ).append( this.$list = $( '
                        ' ) )\n\t .append( $( '
                        ' ).append( this.button_new.$el ) ) );\n\t },\n\t\n\t /** Number of repeat blocks */\n\t size: function() {\n\t return _.size( this.list );\n\t },\n\t\n\t /** Add new repeat block */\n\t add: function( options ) {\n\t if ( !options.id || this.list[ options.id ] ) {\n\t Galaxy.emit.debug( 'form-repeat::add()', 'Duplicate or invalid repeat block id.' );\n\t return;\n\t }\n\t var button_delete = new Ui.ButtonIcon({\n\t icon : 'fa-trash-o',\n\t tooltip : 'Delete this repeat block',\n\t cls : 'ui-button-icon-plain form-repeat-delete',\n\t onclick : function() { options.ondel && options.ondel() }\n\t });\n\t var portlet = new Portlet.View({\n\t id : options.id,\n\t title : 'placeholder',\n\t cls : options.cls || 'ui-portlet-repeat',\n\t operations : { button_delete: button_delete }\n\t });\n\t portlet.append( options.$el );\n\t portlet.$el.addClass( 'section-row' ).hide();\n\t this.list[ options.id ] = portlet;\n\t this.$list.append( portlet.$el.fadeIn( 'fast' ) );\n\t this.options.max > 0 && this.size() >= this.options.max && this.button_new.disable();\n\t this._refresh();\n\t },\n\t\n\t /** Delete repeat block */\n\t del: function( id ) {\n\t if ( !this.list[ id ] ) {\n\t Galaxy.emit.debug( 'form-repeat::del()', 'Invalid repeat block id.' );\n\t return;\n\t }\n\t this.$list.find( '#' + id ).remove();\n\t delete this.list[ id ];\n\t this.button_new.enable();\n\t this._refresh();\n\t },\n\t\n\t /** Remove all */\n\t delAll: function() {\n\t for( var id in this.list ) {\n\t this.del( id );\n\t }\n\t },\n\t\n\t /** Hides add/del options */\n\t hideOptions: function() {\n\t this.button_new.$el.hide();\n\t _.each( this.list, function( portlet ) { portlet.hideOperation( 'button_delete' ) } );\n\t _.isEmpty( this.list ) && this.$el.append( $( '
                        ' ).addClass( 'ui-form-info' ).html( this.options.empty_text ) );\n\t },\n\t\n\t /** Refresh view */\n\t _refresh: function() {\n\t var index = 0;\n\t for ( var id in this.list ) {\n\t var portlet = this.list[ id ];\n\t portlet.title( ++index + ': ' + this.options.title );\n\t portlet[ this.size() > this.options.min ? 'showOperation' : 'hideOperation' ]( 'button_delete' );\n\t }\n\t }\n\t });\n\t\n\t return {\n\t View : View\n\t }\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(1), __webpack_require__(2)))\n\n/***/ },\n/* 37 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, $, _, jQuery) {/**\n\t This class creates a form section and populates it with input elements. It also handles repeat blocks and conditionals by recursively creating new sub sections.\n\t*/\n\t!(__WEBPACK_AMD_DEFINE_ARRAY__ = [ __webpack_require__(4), __webpack_require__(7), __webpack_require__(9), __webpack_require__(36), __webpack_require__(34), __webpack_require__(35) ], __WEBPACK_AMD_DEFINE_RESULT__ = function( Utils, Ui, Portlet, Repeat, InputElement, Parameters ) {\n\t var View = Backbone.View.extend({\n\t initialize: function( app, options ) {\n\t this.app = app;\n\t this.inputs = options.inputs;\n\t this.parameters = new Parameters();\n\t this.setElement( $( '
                        ' ) );\n\t this.render();\n\t },\n\t\n\t /** Render section view */\n\t render: function() {\n\t var self = this;\n\t this.$el.empty();\n\t _.each( this.inputs, function( input ) { self.add( input ) } );\n\t },\n\t\n\t /** Add a new input element */\n\t add: function( input ) {\n\t var input_def = jQuery.extend( true, {}, input );\n\t input_def.id = input.id = Utils.uid();\n\t this.app.input_list[ input_def.id ] = input_def;\n\t switch( input_def.type ) {\n\t case 'conditional':\n\t this._addConditional( input_def );\n\t break;\n\t case 'repeat':\n\t this._addRepeat( input_def );\n\t break;\n\t case 'section':\n\t this._addSection( input_def );\n\t break;\n\t default:\n\t this._addRow( input_def );\n\t }\n\t },\n\t\n\t /** Add a conditional block */\n\t _addConditional: function( input_def ) {\n\t var self = this;\n\t input_def.test_param.id = input_def.id;\n\t this.app.options.sustain_conditionals && ( input_def.test_param.disabled = true );\n\t var field = this._addRow( input_def.test_param );\n\t\n\t // set onchange event for test parameter\n\t field.model && field.model.set( 'onchange', function( value ) {\n\t var selectedCase = self.app.data.matchCase( input_def, value );\n\t for ( var i in input_def.cases ) {\n\t var case_def = input_def.cases[ i ];\n\t var section_row = self.$( '#' + input_def.id + '-section-' + i );\n\t var nonhidden = false;\n\t for ( var j in case_def.inputs ) {\n\t if ( !case_def.inputs[ j ].hidden ) {\n\t nonhidden = true;\n\t break;\n\t }\n\t }\n\t if ( i == selectedCase && nonhidden ) {\n\t section_row.fadeIn( 'fast' );\n\t } else {\n\t section_row.hide();\n\t }\n\t }\n\t self.app.trigger( 'change' );\n\t });\n\t\n\t // add conditional sub sections\n\t for ( var i in input_def.cases ) {\n\t var sub_section = new View( this.app, { inputs: input_def.cases[ i ].inputs } );\n\t this._append( sub_section.$el.addClass( 'ui-form-section' ), input_def.id + '-section-' + i );\n\t }\n\t\n\t // trigger refresh on conditional input field after all input elements have been created\n\t field.trigger( 'change' );\n\t },\n\t\n\t /** Add a repeat block */\n\t _addRepeat: function( input_def ) {\n\t var self = this;\n\t var block_index = 0;\n\t\n\t // create repeat block element\n\t var repeat = new Repeat.View({\n\t title : input_def.title || 'Repeat',\n\t min : input_def.min,\n\t max : input_def.max,\n\t onnew : function() { create( input_def.inputs ); self.app.trigger( 'change' ); }\n\t });\n\t\n\t // helper function to create new repeat blocks\n\t function create ( inputs ) {\n\t var sub_section_id = input_def.id + '-section-' + ( block_index++ );\n\t var sub_section = new View( self.app, { inputs: inputs } );\n\t repeat.add( { id : sub_section_id,\n\t $el : sub_section.$el,\n\t ondel : function() { repeat.del( sub_section_id ); self.app.trigger( 'change' ); } } );\n\t }\n\t\n\t //\n\t // add parsed/minimum number of repeat blocks\n\t //\n\t var n_cache = _.size( input_def.cache );\n\t for ( var i = 0; i < Math.max( Math.max( n_cache, input_def.min ), input_def.default || 0 ); i++ ) {\n\t create( i < n_cache ? input_def.cache[ i ] : input_def.inputs );\n\t }\n\t\n\t // hide options\n\t this.app.options.sustain_repeats && repeat.hideOptions();\n\t\n\t // create input field wrapper\n\t var input_element = new InputElement( this.app, {\n\t label : input_def.title || input_def.name,\n\t help : input_def.help,\n\t field : repeat\n\t });\n\t this._append( input_element.$el, input_def.id );\n\t },\n\t\n\t /** Add a customized section */\n\t _addSection: function( input_def ) {\n\t var portlet = new Portlet.View({\n\t title : input_def.title || input_def.name,\n\t cls : 'ui-portlet-section',\n\t collapsible : true,\n\t collapsible_button : true,\n\t collapsed : !input_def.expanded\n\t });\n\t portlet.append( new View( this.app, { inputs: input_def.inputs } ).$el );\n\t portlet.append( $( '
                        ' ).addClass( 'ui-form-info' ).html( input_def.help ) );\n\t this.app.on( 'expand', function( input_id ) { ( portlet.$( '#' + input_id ).length > 0 ) && portlet.expand(); } );\n\t this._append( portlet.$el, input_def.id );\n\t },\n\t\n\t /** Add a single input field element */\n\t _addRow: function( input_def ) {\n\t var self = this;\n\t var id = input_def.id;\n\t input_def.onchange = function() { self.app.trigger( 'change', id ) };\n\t var field = this.parameters.create( input_def );\n\t this.app.field_list[ id ] = field;\n\t var input_element = new InputElement( this.app, {\n\t name : input_def.name,\n\t label : input_def.label || input_def.name,\n\t value : input_def.value,\n\t text_value : input_def.text_value,\n\t collapsible_value : input_def.collapsible_value,\n\t collapsible_preview : input_def.collapsible_preview,\n\t help : input_def.help,\n\t argument : input_def.argument,\n\t disabled : input_def.disabled,\n\t color : input_def.color,\n\t style : input_def.style,\n\t backdrop : input_def.backdrop,\n\t hidden : input_def.hidden,\n\t field : field\n\t });\n\t this.app.element_list[ id ] = input_element;\n\t this._append( input_element.$el, input_def.id );\n\t return field;\n\t },\n\t\n\t /** Append a new element to the form i.e. input element, repeat block, conditionals etc. */\n\t _append: function( $el, id ) {\n\t this.$el.append( $el.addClass( 'section-row' ).attr( 'id', id ) );\n\t }\n\t });\n\t\n\t return {\n\t View: View\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(1), __webpack_require__(2), __webpack_require__(1)))\n\n/***/ },\n/* 38 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, _, $) {/**\n\t This is the main class of the form plugin. It is referenced as 'app' in lower level modules.\n\t*/\n\t!(__WEBPACK_AMD_DEFINE_ARRAY__ = [ __webpack_require__(4), __webpack_require__(9), __webpack_require__(7), __webpack_require__(37), __webpack_require__(33) ], __WEBPACK_AMD_DEFINE_RESULT__ = function( Utils, Portlet, Ui, FormSection, FormData ) {\n\t return Backbone.View.extend({\n\t initialize: function( options ) {\n\t this.options = Utils.merge( options, {\n\t initial_errors : false,\n\t cls : 'ui-portlet-limited',\n\t icon : null,\n\t always_refresh : true\n\t });\n\t this.setElement( '
                        ' );\n\t this.render();\n\t },\n\t\n\t /** Update available options */\n\t update: function( new_model ){\n\t var self = this;\n\t this.data.matchModel( new_model, function( node, input_id ) {\n\t var input = self.input_list[ input_id ];\n\t if ( input && input.options ) {\n\t if ( !_.isEqual( input.options, node.options ) ) {\n\t input.options = node.options;\n\t var field = self.field_list[ input_id ];\n\t if ( field.update ) {\n\t var new_options = [];\n\t if ( ( [ 'data', 'data_collection', 'drill_down' ] ).indexOf( input.type ) != -1 ) {\n\t new_options = input.options;\n\t } else {\n\t for ( var i in node.options ) {\n\t var opt = node.options[ i ];\n\t if ( opt.length > 2 ) {\n\t new_options.push( { label: opt[ 0 ], value: opt[ 1 ] } );\n\t }\n\t }\n\t }\n\t field.update( new_options );\n\t field.trigger( 'change' );\n\t Galaxy.emit.debug( 'form-view::update()', 'Updating options for ' + input_id );\n\t }\n\t }\n\t }\n\t });\n\t },\n\t\n\t /** Set form into wait mode */\n\t wait: function( active ) {\n\t for ( var i in this.input_list ) {\n\t var field = this.field_list[ i ];\n\t var input = this.input_list[ i ];\n\t if ( input.is_dynamic && field.wait && field.unwait ) {\n\t field[ active ? 'wait' : 'unwait' ]();\n\t }\n\t }\n\t },\n\t\n\t /** Highlight and scroll to input element (currently only used for error notifications) */\n\t highlight: function ( input_id, message, silent ) {\n\t var input_element = this.element_list[ input_id ];\n\t if ( input_element ) {\n\t input_element.error( message || 'Please verify this parameter.' );\n\t this.portlet.expand();\n\t this.trigger( 'expand', input_id );\n\t if ( !silent ) {\n\t var $panel = this.$el.parents().filter(function() {\n\t return [ 'auto', 'scroll' ].indexOf( $( this ).css( 'overflow' ) ) != -1;\n\t }).first();\n\t $panel.animate( { scrollTop : $panel.scrollTop() + input_element.$el.offset().top - 120 }, 500 );\n\t }\n\t }\n\t },\n\t\n\t /** Highlights errors */\n\t errors: function( options ) {\n\t this.trigger( 'reset' );\n\t if ( options && options.errors ) {\n\t var error_messages = this.data.matchResponse( options.errors );\n\t for ( var input_id in this.element_list ) {\n\t var input = this.element_list[ input_id ];\n\t if ( error_messages[ input_id ] ) {\n\t this.highlight( input_id, error_messages[ input_id ], true );\n\t }\n\t }\n\t }\n\t },\n\t\n\t /** Render tool form */\n\t render: function() {\n\t var self = this;\n\t this.off('change');\n\t this.off('reset');\n\t // contains the dom field elements as created by the parameter factory i.e. form-parameters\n\t this.field_list = {};\n\t // contains input definitions/dictionaries as provided by the parameters to_dict() function through the api\n\t this.input_list = {};\n\t // contains the dom elements of each input element i.e. form-input which wraps the actual input field\n\t this.element_list = {};\n\t // converts the form into a json data structure\n\t this.data = new FormData.Manager( this );\n\t this._renderForm();\n\t this.data.create();\n\t this.options.initial_errors && this.errors( this.options );\n\t // add listener which triggers on checksum change, and reset the form input wrappers\n\t var current_check = this.data.checksum();\n\t this.on('change', function( input_id ) {\n\t var input = self.input_list[ input_id ];\n\t if ( !input || input.refresh_on_change || self.options.always_refresh ) {\n\t var new_check = self.data.checksum();\n\t if ( new_check != current_check ) {\n\t current_check = new_check;\n\t self.options.onchange && self.options.onchange();\n\t }\n\t }\n\t });\n\t this.on('reset', function() {\n\t _.each( self.element_list, function( input_element ) { input_element.reset() } );\n\t });\n\t return this;\n\t },\n\t\n\t /** Renders/appends dom elements of the form */\n\t _renderForm: function() {\n\t $( '.tooltip' ).remove();\n\t this.message = new Ui.Message();\n\t this.section = new FormSection.View( this, { inputs: this.options.inputs } );\n\t this.portlet = new Portlet.View({\n\t icon : this.options.icon,\n\t title : this.options.title,\n\t cls : this.options.cls,\n\t operations : this.options.operations,\n\t buttons : this.options.buttons,\n\t collapsible : this.options.collapsible,\n\t collapsed : this.options.collapsed\n\t });\n\t this.portlet.append( this.message.$el );\n\t this.portlet.append( this.section.$el );\n\t this.$el.empty();\n\t this.options.inputs && this.$el.append( this.portlet.$el );\n\t this.options.message && this.message.update( { persistent: true, status: 'warning', message: this.options.message } );\n\t Galaxy.emit.debug( 'form-view::initialize()', 'Completed' );\n\t }\n\t });\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(2), __webpack_require__(1)))\n\n/***/ },\n/* 39 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(_) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(30),\n\t __webpack_require__(74),\n\t __webpack_require__(5)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( DC_MODEL, HISTORY_CONTENT, _l ){\n\t\n\t'use strict';\n\t\n\t/*==============================================================================\n\t\n\tModels for DatasetCollections contained within a history.\n\t\n\tTODO:\n\t these might be compactable to one class if some duplication with\n\t collection-model is used.\n\t\n\t==============================================================================*/\n\tvar hcontentMixin = HISTORY_CONTENT.HistoryContentMixin,\n\t ListDC = DC_MODEL.ListDatasetCollection,\n\t PairDC = DC_MODEL.PairDatasetCollection,\n\t ListPairedDC = DC_MODEL.ListPairedDatasetCollection,\n\t ListOfListsDC = DC_MODEL.ListOfListsDatasetCollection;\n\t\n\t//==============================================================================\n\t/** Override to post to contents route w/o id. */\n\tfunction buildHDCASave( _super ){\n\t return function _save( attributes, options ){\n\t if( this.isNew() ){\n\t options = options || {};\n\t options.url = this.urlRoot + this.get( 'history_id' ) + '/contents';\n\t attributes = attributes || {};\n\t attributes.type = 'dataset_collection';\n\t }\n\t return _super.call( this, attributes, options );\n\t };\n\t}\n\t\n\t\n\t//==============================================================================\n\t/** @class Backbone model for List Dataset Collection within a History.\n\t */\n\tvar HistoryListDatasetCollection = ListDC.extend( hcontentMixin ).extend(\n\t/** @lends HistoryListDatasetCollection.prototype */{\n\t\n\t defaults : _.extend( _.clone( ListDC.prototype.defaults ), {\n\t history_content_type: 'dataset_collection',\n\t collection_type : 'list',\n\t model_class : 'HistoryDatasetCollectionAssociation'\n\t }),\n\t\n\t /** Override to post to contents route w/o id. */\n\t save : buildHDCASave( ListDC.prototype.save ),\n\t\n\t /** String representation. */\n\t toString : function(){\n\t return 'History' + ListDC.prototype.toString.call( this );\n\t }\n\t});\n\t\n\t\n\t//==============================================================================\n\t/** @class Backbone model for Pair Dataset Collection within a History.\n\t * @constructs\n\t */\n\tvar HistoryPairDatasetCollection = PairDC.extend( hcontentMixin ).extend(\n\t/** @lends HistoryPairDatasetCollection.prototype */{\n\t\n\t defaults : _.extend( _.clone( PairDC.prototype.defaults ), {\n\t history_content_type: 'dataset_collection',\n\t collection_type : 'paired',\n\t model_class : 'HistoryDatasetCollectionAssociation'\n\t }),\n\t\n\t /** Override to post to contents route w/o id. */\n\t save : buildHDCASave( PairDC.prototype.save ),\n\t\n\t /** String representation. */\n\t toString : function(){\n\t return 'History' + PairDC.prototype.toString.call( this );\n\t }\n\t});\n\t\n\t\n\t//==============================================================================\n\t/** @class Backbone model for List of Pairs Dataset Collection within a History. */\n\tvar HistoryListPairedDatasetCollection = ListPairedDC.extend( hcontentMixin ).extend({\n\t\n\t defaults : _.extend( _.clone( ListPairedDC.prototype.defaults ), {\n\t history_content_type: 'dataset_collection',\n\t collection_type : 'list:paired',\n\t model_class : 'HistoryDatasetCollectionAssociation'\n\t }),\n\t\n\t /** Override to post to contents route w/o id. */\n\t save : buildHDCASave( ListPairedDC.prototype.save ),\n\t\n\t /** String representation. */\n\t toString : function(){\n\t return 'History' + ListPairedDC.prototype.toString.call( this );\n\t }\n\t});\n\t\n\t\n\t//==============================================================================\n\t/** @class Backbone model for List of Lists Dataset Collection within a History. */\n\tvar HistoryListOfListsDatasetCollection = ListOfListsDC.extend( hcontentMixin ).extend({\n\t\n\t defaults : _.extend( _.clone( ListOfListsDC.prototype.defaults ), {\n\t history_content_type: 'dataset_collection',\n\t collection_type : 'list:list',\n\t model_class : 'HistoryDatasetCollectionAssociation'\n\t }),\n\t\n\t /** Override to post to contents route w/o id. */\n\t save : buildHDCASave( ListOfListsDC.prototype.save ),\n\t\n\t /** String representation. */\n\t toString : function(){\n\t return ([ 'HistoryListOfListsDatasetCollection(', this.get( 'name' ), ')' ].join( '' ));\n\t }\n\t});\n\t\n\t\n\t//==============================================================================\n\t return {\n\t HistoryListDatasetCollection : HistoryListDatasetCollection,\n\t HistoryPairDatasetCollection : HistoryPairDatasetCollection,\n\t HistoryListPairedDatasetCollection : HistoryListPairedDatasetCollection,\n\t HistoryListOfListsDatasetCollection : HistoryListOfListsDatasetCollection\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(2)))\n\n/***/ },\n/* 40 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(_, jQuery, Backbone) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(67),\n\t __webpack_require__(72),\n\t __webpack_require__(39),\n\t __webpack_require__(41),\n\t __webpack_require__(6),\n\t __webpack_require__(129)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( CONTROLLED_FETCH_COLLECTION, HDA_MODEL, HDCA_MODEL, HISTORY_PREFS, BASE_MVC, AJAX_QUEUE ){\n\t'use strict';\n\t\n\t//==============================================================================\n\tvar _super = CONTROLLED_FETCH_COLLECTION.PaginatedCollection;\n\t/** @class Backbone collection for history content.\n\t * NOTE: history content seems like a dataset collection, but differs in that it is mixed:\n\t * each element can be either an HDA (dataset) or a DatasetCollection and co-exist on\n\t * the same level.\n\t * Dataset collections on the other hand are not mixed and (so far) can only contain either\n\t * HDAs or child dataset collections on one level.\n\t * This is why this does not inherit from any of the DatasetCollections (currently).\n\t */\n\tvar HistoryContents = _super.extend( BASE_MVC.LoggableMixin ).extend({\n\t _logNamespace : 'history',\n\t\n\t // ........................................................................ composite collection\n\t /** since history content is a mix, override model fn into a factory, creating based on history_content_type */\n\t model : function( attrs, options ) {\n\t if( attrs.history_content_type === \"dataset\" ) {\n\t return new HDA_MODEL.HistoryDatasetAssociation( attrs, options );\n\t\n\t } else if( attrs.history_content_type === \"dataset_collection\" ) {\n\t switch( attrs.collection_type ){\n\t case 'list':\n\t return new HDCA_MODEL.HistoryListDatasetCollection( attrs, options );\n\t case 'paired':\n\t return new HDCA_MODEL.HistoryPairDatasetCollection( attrs, options );\n\t case 'list:paired':\n\t return new HDCA_MODEL.HistoryListPairedDatasetCollection( attrs, options );\n\t case 'list:list':\n\t return new HDCA_MODEL.HistoryListOfListsDatasetCollection( attrs, options );\n\t }\n\t // This is a hack inside a hack:\n\t // Raise a plain object with validationError to fake a model.validationError\n\t // (since we don't have a model to use validate with)\n\t // (the outer hack being the mixed content/model function in this collection)\n\t var msg = 'Unknown collection_type: ' + attrs.collection_type;\n\t console.warn( msg, attrs );\n\t return { validationError : msg };\n\t }\n\t return { validationError : 'Unknown history_content_type: ' + attrs.history_content_type };\n\t },\n\t\n\t // ........................................................................ set up\n\t limitPerPage : 500,\n\t\n\t /** @type {Integer} how many contents per call to fetch when using progressivelyFetchDetails */\n\t limitPerProgressiveFetch : 500,\n\t\n\t /** @type {String} order used here and when fetching from server */\n\t order : 'hid',\n\t\n\t /** root api url */\n\t urlRoot : Galaxy.root + 'api/histories',\n\t\n\t /** complete api url */\n\t url : function(){\n\t return this.urlRoot + '/' + this.historyId + '/contents';\n\t },\n\t\n\t /** Set up */\n\t initialize : function( models, options ){\n\t options = options || {};\n\t _super.prototype.initialize.call( this, models, options );\n\t\n\t this.history = options.history || null;\n\t this.setHistoryId( options.historyId || null );\n\t /** @type {Boolean} does this collection contain and fetch deleted elements */\n\t this.includeDeleted = options.includeDeleted || this.includeDeleted;\n\t /** @type {Boolean} does this collection contain and fetch non-visible elements */\n\t this.includeHidden = options.includeHidden || this.includeHidden;\n\t\n\t // backbonejs uses collection.model.prototype.idAttribute to determine if a model is *already* in a collection\n\t // and either merged or replaced. In this case, our 'model' is a function so we need to add idAttribute\n\t // manually here - if we don't, contents will not merge but be replaced/swapped.\n\t this.model.prototype.idAttribute = 'type_id';\n\t },\n\t\n\t setHistoryId : function( newId ){\n\t this.historyId = newId;\n\t this._setUpWebStorage();\n\t },\n\t\n\t /** Set up client side storage. Currently PersistanStorage keyed under 'history:' */\n\t _setUpWebStorage : function( initialSettings ){\n\t // TODO: use initialSettings\n\t if( !this.historyId ){ return; }\n\t this.storage = new HISTORY_PREFS.HistoryPrefs({\n\t id: HISTORY_PREFS.HistoryPrefs.historyStorageKey( this.historyId )\n\t });\n\t this.trigger( 'new-storage', this.storage, this );\n\t\n\t this.on({\n\t 'include-deleted' : function( newVal ){\n\t this.storage.includeDeleted( newVal );\n\t },\n\t 'include-hidden' : function( newVal ){\n\t this.storage.includeHidden( newVal );\n\t }\n\t });\n\t\n\t this.includeDeleted = this.storage.includeDeleted() || false;\n\t this.includeHidden = this.storage.includeHidden() || false;\n\t return this;\n\t },\n\t\n\t // ........................................................................ common queries\n\t /** @type {Object} map of collection available sorting orders containing comparator fns */\n\t comparators : _.extend( _.clone( _super.prototype.comparators ), {\n\t 'name' : BASE_MVC.buildComparator( 'name', { ascending: true }),\n\t 'name-dsc' : BASE_MVC.buildComparator( 'name', { ascending: false }),\n\t 'hid' : BASE_MVC.buildComparator( 'hid', { ascending: false }),\n\t 'hid-asc' : BASE_MVC.buildComparator( 'hid', { ascending: true }),\n\t }),\n\t\n\t /** Get every model in this collection not in a 'ready' state (running). */\n\t running : function(){\n\t return this.filter( function( c ){ return !c.inReadyState(); });\n\t },\n\t\n\t /** return contents that are not ready and not deleted/hidden */\n\t runningAndActive : function(){\n\t return this.filter( function( c ){\n\t return ( !c.inReadyState() )\n\t && ( c.get( 'visible' ) )\n\t // TODO: deletedOrPurged?\n\t && ( !c.get( 'deleted' ) );\n\t });\n\t },\n\t\n\t /** Get the model with the given hid\n\t * @param {Int} hid the hid to search for\n\t * @returns {HistoryDatasetAssociation} the model with the given hid or undefined if not found\n\t */\n\t getByHid : function( hid ){\n\t // note: there *can* be more than one content with a given hid, this finds the first based on order\n\t return this.findWhere({ hid: hid });\n\t },\n\t\n\t /** return true if all contents have details */\n\t haveDetails : function(){\n\t return this.all( function( c ){ return c.hasDetails(); });\n\t },\n\t\n\t // ........................................................................ hidden / deleted\n\t /** return a new contents collection of only hidden items */\n\t hidden : function(){\n\t return this.filter( function( c ){ return c.hidden(); });\n\t },\n\t\n\t /** return a new contents collection of only hidden items */\n\t deleted : function(){\n\t return this.filter( function( c ){ return c.get( 'deleted' ); });\n\t },\n\t\n\t /** return a new contents collection of only hidden items */\n\t visibleAndUndeleted : function(){\n\t return this.filter( function( c ){\n\t return ( c.get( 'visible' ) )\n\t // TODO: deletedOrPurged?\n\t && ( !c.get( 'deleted' ) );\n\t });\n\t },\n\t\n\t /** create a setter in order to publish the change */\n\t setIncludeDeleted : function( setting, options ){\n\t if( _.isBoolean( setting ) && setting !== this.includeDeleted ){\n\t this.includeDeleted = setting;\n\t if( _.result( options, 'silent' ) ){ return; }\n\t this.trigger( 'include-deleted', setting, this );\n\t }\n\t },\n\t\n\t /** create a setter in order to publish the change */\n\t setIncludeHidden : function( setting, options ){\n\t if( _.isBoolean( setting ) && setting !== this.includeHidden ){\n\t this.includeHidden = setting;\n\t options = options || {};\n\t if( _.result( options, 'silent' ) ){ return; }\n\t this.trigger( 'include-hidden', setting, this );\n\t }\n\t },\n\t\n\t // ........................................................................ ajax\n\t // ............ controlled fetch collection\n\t /** override to get expanded ids from sessionStorage and pass to API as details */\n\t fetch : function( options ){\n\t options = options || {};\n\t if( this.historyId && !options.details ){\n\t var prefs = HISTORY_PREFS.HistoryPrefs.get( this.historyId ).toJSON();\n\t if( !_.isEmpty( prefs.expandedIds ) ){\n\t options.details = _.values( prefs.expandedIds ).join( ',' );\n\t }\n\t }\n\t return _super.prototype.fetch.call( this, options );\n\t },\n\t\n\t // ............. ControlledFetch stuff\n\t /** override to include the API versioning flag */\n\t _buildFetchData : function( options ){\n\t return _.extend( _super.prototype._buildFetchData.call( this, options ), {\n\t v : 'dev'\n\t });\n\t },\n\t\n\t /** Extend to include details and version */\n\t _fetchParams : _super.prototype._fetchParams.concat([\n\t // TODO: remove (the need for) both\n\t /** version */\n\t 'v',\n\t /** dataset ids to get full details of */\n\t 'details',\n\t ]),\n\t\n\t /** override to add deleted/hidden filters */\n\t _buildFetchFilters : function( options ){\n\t var superFilters = _super.prototype._buildFetchFilters.call( this, options ) || {};\n\t var filters = {};\n\t if( !this.includeDeleted ){\n\t filters.deleted = false;\n\t filters.purged = false;\n\t }\n\t if( !this.includeHidden ){\n\t filters.visible = true;\n\t }\n\t return _.defaults( superFilters, filters );\n\t },\n\t\n\t // ............ paginated collection\n\t getTotalItemCount : function(){\n\t return this.history.contentsShown();\n\t },\n\t\n\t // ............ history contents specific ajax\n\t /** override to filter requested contents to those updated after the Date 'since' */\n\t fetchUpdated : function( since, options ){\n\t if( since ){\n\t options = options || { filters: {} };\n\t options.remove = false;\n\t options.filters = {\n\t 'update_time-ge' : since.toISOString(),\n\t // workflows will produce hidden datasets (non-output datasets) that still\n\t // need to be updated in the collection or they'll update forever\n\t // we can remove the default visible filter by using an 'empty' value\n\t visible : ''\n\t };\n\t }\n\t return this.fetch( options );\n\t },\n\t\n\t /** fetch all the deleted==true contents of this collection */\n\t fetchDeleted : function( options ){\n\t options = options || {};\n\t var self = this;\n\t options.filters = _.extend( options.filters, {\n\t // all deleted, purged or not\n\t deleted : true,\n\t purged : undefined\n\t });\n\t options.remove = false;\n\t\n\t self.trigger( 'fetching-deleted', self );\n\t return self.fetch( options )\n\t .always( function(){ self.trigger( 'fetching-deleted-done', self ); });\n\t },\n\t\n\t /** fetch all the visible==false contents of this collection */\n\t fetchHidden : function( options ){\n\t options = options || {};\n\t var self = this;\n\t options.filters = _.extend( options.filters, {\n\t visible : false\n\t });\n\t options.remove = false;\n\t\n\t self.trigger( 'fetching-hidden', self );\n\t return self.fetch( options )\n\t .always( function(){ self.trigger( 'fetching-hidden-done', self ); });\n\t },\n\t\n\t /** fetch detailed model data for all contents in this collection */\n\t fetchAllDetails : function( options ){\n\t options = options || {};\n\t var detailsFlag = { details: 'all' };\n\t options.data = _.extend( options.data || {}, detailsFlag );\n\t return this.fetch( options );\n\t },\n\t\n\t /** specialty fetch method for retrieving the element_counts of all hdcas in the history */\n\t fetchCollectionCounts : function( options ){\n\t options = options || {};\n\t options.keys = [ 'type_id', 'element_count' ].join( ',' );\n\t options.filters = _.extend( options.filters || {}, {\n\t history_content_type: 'dataset_collection',\n\t });\n\t options.remove = false;\n\t return this.fetch( options );\n\t },\n\t\n\t // ............. quasi-batch ops\n\t // TODO: to batch\n\t /** helper that fetches using filterParams then calls save on each fetched using updateWhat as the save params */\n\t _filterAndUpdate : function( filterParams, updateWhat ){\n\t var self = this;\n\t var idAttribute = self.model.prototype.idAttribute;\n\t var updateArgs = [ updateWhat ];\n\t\n\t return self.fetch({ filters: filterParams, remove: false })\n\t .then( function( fetched ){\n\t // convert filtered json array to model array\n\t fetched = fetched.reduce( function( modelArray, currJson, i ){\n\t var model = self.get( currJson[ idAttribute ] );\n\t return model? modelArray.concat( model ) : modelArray;\n\t }, []);\n\t return self.ajaxQueue( 'save', updateArgs, fetched );\n\t });\n\t },\n\t\n\t /** using a queue, perform ajaxFn on each of the models in this collection */\n\t ajaxQueue : function( ajaxFn, args, collection ){\n\t collection = collection || this.models;\n\t return new AJAX_QUEUE.AjaxQueue( collection.slice().reverse().map( function( content, i ){\n\t var fn = _.isString( ajaxFn )? content[ ajaxFn ] : ajaxFn;\n\t return function(){ return fn.apply( content, args ); };\n\t })).deferred;\n\t },\n\t\n\t /** fetch contents' details in batches of limitPerCall - note: only get searchable details here */\n\t progressivelyFetchDetails : function( options ){\n\t options = options || {};\n\t var deferred = jQuery.Deferred();\n\t var self = this;\n\t var limit = options.limitPerCall || self.limitPerProgressiveFetch;\n\t // TODO: only fetch tags and annotations if specifically requested\n\t var searchAttributes = HDA_MODEL.HistoryDatasetAssociation.prototype.searchAttributes;\n\t var detailKeys = searchAttributes.join( ',' );\n\t\n\t function _recursivelyFetch( offset ){\n\t offset = offset || 0;\n\t var _options = _.extend( _.clone( options ), {\n\t view : 'summary',\n\t keys : detailKeys,\n\t limit : limit,\n\t offset : offset,\n\t reset : offset === 0,\n\t remove : false\n\t });\n\t\n\t _.defer( function(){\n\t self.fetch.call( self, _options )\n\t .fail( deferred.reject )\n\t .done( function( response ){\n\t deferred.notify( response, limit, offset );\n\t if( response.length !== limit ){\n\t self.allFetched = true;\n\t deferred.resolve( response, limit, offset );\n\t\n\t } else {\n\t _recursivelyFetch( offset + limit );\n\t }\n\t });\n\t });\n\t }\n\t _recursivelyFetch();\n\t return deferred;\n\t },\n\t\n\t /** does some bit of JSON represent something that can be copied into this contents collection */\n\t isCopyable : function( contentsJSON ){\n\t var copyableModelClasses = [\n\t 'HistoryDatasetAssociation',\n\t 'HistoryDatasetCollectionAssociation'\n\t ];\n\t return ( ( _.isObject( contentsJSON ) && contentsJSON.id )\n\t && ( _.contains( copyableModelClasses, contentsJSON.model_class ) ) );\n\t },\n\t\n\t /** copy an existing, accessible hda into this collection */\n\t copy : function( json ){\n\t // TODO: somehow showhorn all this into 'save'\n\t var id, type, contentType;\n\t if( _.isString( json ) ){\n\t id = json;\n\t contentType = 'hda';\n\t type = 'dataset';\n\t } else {\n\t id = json.id;\n\t contentType = ({\n\t 'HistoryDatasetAssociation' : 'hda',\n\t 'LibraryDatasetDatasetAssociation' : 'ldda',\n\t 'HistoryDatasetCollectionAssociation' : 'hdca'\n\t })[ json.model_class ] || 'hda';\n\t type = ( contentType === 'hdca'? 'dataset_collection' : 'dataset' );\n\t }\n\t var collection = this,\n\t xhr = jQuery.ajax( this.url(), {\n\t method: 'POST',\n\t contentType: 'application/json',\n\t data: JSON.stringify({\n\t content : id,\n\t source : contentType,\n\t type : type\n\t })\n\t })\n\t .done( function( response ){\n\t collection.add([ response ], { parse: true });\n\t })\n\t .fail( function( error, status, message ){\n\t collection.trigger( 'error', collection, xhr, {},\n\t 'Error copying contents', { type: type, id: id, source: contentType });\n\t });\n\t return xhr;\n\t },\n\t\n\t /** create a new HDCA in this collection */\n\t createHDCA : function( elementIdentifiers, collectionType, name, options ){\n\t // normally collection.create returns the new model, but we need the promise from the ajax, so we fake create\n\t //precondition: elementIdentifiers is an array of plain js objects\n\t // in the proper form to create the collectionType\n\t var hdca = this.model({\n\t history_content_type: 'dataset_collection',\n\t collection_type : collectionType,\n\t history_id : this.historyId,\n\t name : name,\n\t // should probably be able to just send in a bunch of json here and restruct per class\n\t // note: element_identifiers is now (incorrectly) an attribute\n\t element_identifiers : elementIdentifiers\n\t // do not create the model on the client until the ajax returns\n\t });\n\t return hdca.save( options );\n\t },\n\t\n\t // ........................................................................ searching\n\t /** return true if all contents have the searchable attributes */\n\t haveSearchDetails : function(){\n\t return this.allFetched && this.all( function( content ){\n\t // null (which is a valid returned annotation value)\n\t // will return false when using content.has( 'annotation' )\n\t //TODO: a bit hacky - formalize\n\t return _.has( content.attributes, 'annotation' );\n\t });\n\t },\n\t\n\t /** return a new collection of contents whose attributes contain the substring matchesWhat */\n\t matches : function( matchesWhat ){\n\t return this.filter( function( content ){\n\t return content.matches( matchesWhat );\n\t });\n\t },\n\t\n\t // ........................................................................ misc\n\t /** In this override, copy the historyId to the clone */\n\t clone : function(){\n\t var clone = Backbone.Collection.prototype.clone.call( this );\n\t clone.historyId = this.historyId;\n\t return clone;\n\t },\n\t\n\t /** String representation. */\n\t toString : function(){\n\t return ([ 'HistoryContents(', [ this.historyId, this.length ].join(), ')' ].join( '' ));\n\t }\n\t});\n\t\n\t\n\t//==============================================================================\n\t return {\n\t HistoryContents : HistoryContents\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(2), __webpack_require__(1), __webpack_require__(3)))\n\n/***/ },\n/* 41 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(_) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(6)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( BASE_MVC ){\n\t\n\t'use strict';\n\t\n\tvar logNamespace = 'history';\n\t\n\t// ============================================================================\n\t/** session storage for individual history preferences */\n\tvar HistoryPrefs = BASE_MVC.SessionStorageModel.extend(\n\t/** @lends HistoryPrefs.prototype */{\n\t //TODO:?? move to user prefs?\n\t defaults : {\n\t //TODO:?? expandedIds to array?\n\t expandedIds : {},\n\t show_deleted : false,\n\t show_hidden : false\n\t },\n\t\n\t /** add an hda id to the hash of expanded hdas */\n\t addExpanded : function( model ){\n\t//TODO: use type_id and not model\n\t var current = this.get( 'expandedIds' );\n\t current[ model.id ] = model.get( 'id' );\n\t this.save( 'expandedIds', current );\n\t },\n\t\n\t /** remove an hda id from the hash of expanded hdas */\n\t removeExpanded : function( model ){\n\t var current = this.get( 'expandedIds' );\n\t delete current[ model.id ];\n\t this.save( 'expandedIds', current );\n\t },\n\t\n\t isExpanded : function( contentId ){\n\t return _.result( this.get( 'expandedIds' ), contentId, false );\n\t },\n\t\n\t allExpanded : function(){\n\t return _.values( this.get( 'expandedIds' ) );\n\t },\n\t\n\t clearExpanded : function(){\n\t this.set( 'expandedIds', {} );\n\t },\n\t\n\t includeDeleted : function( val ){\n\t // moving the invocation here so other components don't need to know the key\n\t // TODO: change this key later\n\t if( !_.isUndefined( val ) ){ this.set( 'show_deleted', val ); }\n\t return this.get( 'show_deleted' );\n\t },\n\t\n\t includeHidden : function( val ){\n\t // TODO: change this key later\n\t if( !_.isUndefined( val ) ){ this.set( 'show_hidden', val ); }\n\t return this.get( 'show_hidden' );\n\t },\n\t\n\t toString : function(){\n\t return 'HistoryPrefs(' + this.id + ')';\n\t }\n\t\n\t}, {\n\t // ........................................................................ class vars\n\t // class lvl for access w/o instantiation\n\t storageKeyPrefix : 'history:',\n\t\n\t /** key string to store each histories settings under */\n\t historyStorageKey : function historyStorageKey( historyId ){\n\t if( !historyId ){\n\t throw new Error( 'HistoryPrefs.historyStorageKey needs valid id: ' + historyId );\n\t }\n\t // single point of change\n\t return ( HistoryPrefs.storageKeyPrefix + historyId );\n\t },\n\t\n\t /** return the existing storage for the history with the given id (or create one if it doesn't exist) */\n\t get : function get( historyId ){\n\t return new HistoryPrefs({ id: HistoryPrefs.historyStorageKey( historyId ) });\n\t },\n\t\n\t /** clear all history related items in sessionStorage */\n\t clearAll : function clearAll( historyId ){\n\t for( var key in sessionStorage ){\n\t if( key.indexOf( HistoryPrefs.storageKeyPrefix ) === 0 ){\n\t sessionStorage.removeItem( key );\n\t }\n\t }\n\t }\n\t});\n\t\n\t//==============================================================================\n\t return {\n\t HistoryPrefs: HistoryPrefs\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(2)))\n\n/***/ },\n/* 42 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, $, _, jQuery) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(6),\n\t __webpack_require__(5)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( BASE_MVC, _l ){\n\t\n\t'use strict';\n\t\n\tvar logNamespace = 'list';\n\t//==============================================================================\n\t/** A view which, when first rendered, shows only summary data/attributes, but\n\t * can be expanded to show further details (and optionally fetch those\n\t * details from the server).\n\t */\n\tvar ExpandableView = Backbone.View.extend( BASE_MVC.LoggableMixin ).extend({\n\t _logNamespace : logNamespace,\n\t\n\t //TODO: Although the reasoning behind them is different, this shares a lot with HiddenUntilActivated above: combine them\n\t //PRECONDITION: model must have method hasDetails\n\t //PRECONDITION: subclasses must have templates.el and templates.details\n\t\n\t initialize : function( attributes ){\n\t /** are the details of this view expanded/shown or not? */\n\t this.expanded = attributes.expanded || false;\n\t this.log( '\\t expanded:', this.expanded );\n\t this.fxSpeed = attributes.fxSpeed !== undefined? attributes.fxSpeed : this.fxSpeed;\n\t },\n\t\n\t // ........................................................................ render main\n\t /** jq fx speed */\n\t fxSpeed : 'fast',\n\t\n\t /** Render this content, set up ui.\n\t * @param {Number or String} speed the speed of the render\n\t */\n\t render : function( speed ){\n\t var $newRender = this._buildNewRender();\n\t this._setUpBehaviors( $newRender );\n\t this._queueNewRender( $newRender, speed );\n\t return this;\n\t },\n\t\n\t /** Build a temp div containing the new children for the view's $el.\n\t * If the view is already expanded, build the details as well.\n\t */\n\t _buildNewRender : function(){\n\t // create a new render using a skeleton template, render title buttons, render body, and set up events, etc.\n\t var $newRender = $( this.templates.el( this.model.toJSON(), this ) );\n\t if( this.expanded ){\n\t this.$details( $newRender ).replaceWith( this._renderDetails().show() );\n\t }\n\t return $newRender;\n\t },\n\t\n\t /** Fade out the old el, swap in the new contents, then fade in.\n\t * @param {Number or String} speed jq speed to use for rendering effects\n\t * @fires rendered when rendered\n\t */\n\t _queueNewRender : function( $newRender, speed ) {\n\t speed = ( speed === undefined )?( this.fxSpeed ):( speed );\n\t var view = this;\n\t\n\t if( speed === 0 ){\n\t view._swapNewRender( $newRender );\n\t view.trigger( 'rendered', view );\n\t\n\t } else {\n\t $( view ).queue( 'fx', [\n\t function( next ){\n\t view.$el.fadeOut( speed, next );\n\t },\n\t function( next ){\n\t view._swapNewRender( $newRender );\n\t next();\n\t },\n\t function( next ){\n\t view.$el.fadeIn( speed, next );\n\t },\n\t function( next ){\n\t view.trigger( 'rendered', view );\n\t next();\n\t }\n\t ]);\n\t }\n\t },\n\t\n\t /** empty out the current el, move the $newRender's children in */\n\t _swapNewRender : function( $newRender ){\n\t return this.$el.empty()\n\t .attr( 'class', _.isFunction( this.className )? this.className(): this.className )\n\t .append( $newRender.children() );\n\t },\n\t\n\t /** set up js behaviors, event handlers for elements within the given container\n\t * @param {jQuery} $container jq object that contains the elements to process (defaults to this.$el)\n\t */\n\t _setUpBehaviors : function( $where ){\n\t $where = $where || this.$el;\n\t // set up canned behavior on children (bootstrap, popupmenus, editable_text, etc.)\n\t //make_popup_menus( $where );\n\t $where.find( '[title]' ).tooltip({ placement : 'bottom' });\n\t },\n\t\n\t // ......................................................................... details\n\t /** shortcut to details DOM (as jQ) */\n\t $details : function( $where ){\n\t $where = $where || this.$el;\n\t return $where.find( '> .details' );\n\t },\n\t\n\t /** build the DOM for the details and set up behaviors on it */\n\t _renderDetails : function(){\n\t var $newDetails = $( this.templates.details( this.model.toJSON(), this ) );\n\t this._setUpBehaviors( $newDetails );\n\t return $newDetails;\n\t },\n\t\n\t // ......................................................................... expansion/details\n\t /** Show or hide the details\n\t * @param {Boolean} expand if true, expand; if false, collapse\n\t */\n\t toggleExpanded : function( expand ){\n\t expand = ( expand === undefined )?( !this.expanded ):( expand );\n\t if( expand ){\n\t this.expand();\n\t } else {\n\t this.collapse();\n\t }\n\t return this;\n\t },\n\t\n\t /** Render and show the full, detailed body of this view including extra data and controls.\n\t * note: if the model does not have detailed data, fetch that data before showing the body\n\t * @fires expanded when a body has been expanded\n\t */\n\t expand : function(){\n\t var view = this;\n\t return view._fetchModelDetails().always( function(){\n\t view._expand();\n\t });\n\t },\n\t\n\t /** Check for model details and, if none, fetch them.\n\t * @returns {jQuery.promise} the model.fetch.xhr if details are being fetched, an empty promise if not\n\t */\n\t _fetchModelDetails : function(){\n\t if( !this.model.hasDetails() ){\n\t return this.model.fetch();\n\t }\n\t return jQuery.when();\n\t },\n\t\n\t /** Inner fn called when expand (public) has fetched the details */\n\t _expand : function(){\n\t var view = this,\n\t $newDetails = view._renderDetails();\n\t view.$details().replaceWith( $newDetails );\n\t // needs to be set after the above or the slide will not show\n\t view.expanded = true;\n\t view.$details().slideDown( view.fxSpeed, function(){\n\t view.trigger( 'expanded', view );\n\t });\n\t },\n\t\n\t /** Hide the body/details of an HDA.\n\t * @fires collapsed when a body has been collapsed\n\t */\n\t collapse : function(){\n\t this.debug( this + '(ExpandableView).collapse' );\n\t var view = this;\n\t view.expanded = false;\n\t this.$details().slideUp( view.fxSpeed, function(){\n\t view.trigger( 'collapsed', view );\n\t });\n\t }\n\t\n\t});\n\t\n\t\n\t//==============================================================================\n\t/** A view that is displayed in some larger list/grid/collection.\n\t * Inherits from Expandable, Selectable, Draggable.\n\t * The DOM contains warnings, a title bar, and a series of primary action controls.\n\t * Primary actions are meant to be easily accessible item functions (such as delete)\n\t * that are rendered in the title bar.\n\t *\n\t * Details are rendered when the user clicks the title bar or presses enter/space when\n\t * the title bar is in focus.\n\t *\n\t * Designed as a base class for history panel contents - but usable elsewhere (I hope).\n\t */\n\tvar ListItemView = ExpandableView.extend(\n\t BASE_MVC.mixin( BASE_MVC.SelectableViewMixin, BASE_MVC.DraggableViewMixin, {\n\t\n\t tagName : 'div',\n\t className : 'list-item',\n\t\n\t /** Set up the base class and all mixins */\n\t initialize : function( attributes ){\n\t ExpandableView.prototype.initialize.call( this, attributes );\n\t BASE_MVC.SelectableViewMixin.initialize.call( this, attributes );\n\t BASE_MVC.DraggableViewMixin.initialize.call( this, attributes );\n\t this._setUpListeners();\n\t },\n\t\n\t /** event listeners */\n\t _setUpListeners : function(){\n\t // hide the primary actions in the title bar when selectable and narrow\n\t this.on( 'selectable', function( isSelectable ){\n\t if( isSelectable ){\n\t this.$( '.primary-actions' ).hide();\n\t } else {\n\t this.$( '.primary-actions' ).show();\n\t }\n\t }, this );\n\t return this;\n\t },\n\t\n\t // ........................................................................ rendering\n\t /** In this override, call methods to build warnings, titlebar and primary actions */\n\t _buildNewRender : function(){\n\t var $newRender = ExpandableView.prototype._buildNewRender.call( this );\n\t $newRender.children( '.warnings' ).replaceWith( this._renderWarnings() );\n\t $newRender.children( '.title-bar' ).replaceWith( this._renderTitleBar() );\n\t $newRender.children( '.primary-actions' ).append( this._renderPrimaryActions() );\n\t $newRender.find( '> .title-bar .subtitle' ).replaceWith( this._renderSubtitle() );\n\t return $newRender;\n\t },\n\t\n\t /** In this override, render the selector controls and set up dragging before the swap */\n\t _swapNewRender : function( $newRender ){\n\t ExpandableView.prototype._swapNewRender.call( this, $newRender );\n\t if( this.selectable ){ this.showSelector( 0 ); }\n\t if( this.draggable ){ this.draggableOn(); }\n\t return this.$el;\n\t },\n\t\n\t /** Render any warnings the item may need to show (e.g. \"I'm deleted\") */\n\t _renderWarnings : function(){\n\t var view = this,\n\t $warnings = $( '
                        ' ),\n\t json = view.model.toJSON();\n\t //TODO:! unordered (map)\n\t _.each( view.templates.warnings, function( templateFn ){\n\t $warnings.append( $( templateFn( json, view ) ) );\n\t });\n\t return $warnings;\n\t },\n\t\n\t /** Render the title bar (the main/exposed SUMMARY dom element) */\n\t _renderTitleBar : function(){\n\t return $( this.templates.titleBar( this.model.toJSON(), this ) );\n\t },\n\t\n\t /** Return an array of jQ objects containing common/easily-accessible item controls */\n\t _renderPrimaryActions : function(){\n\t // override this\n\t return [];\n\t },\n\t\n\t /** Render the title bar (the main/exposed SUMMARY dom element) */\n\t _renderSubtitle : function(){\n\t return $( this.templates.subtitle( this.model.toJSON(), this ) );\n\t },\n\t\n\t // ......................................................................... events\n\t /** event map */\n\t events : {\n\t // expand the body when the title is clicked or when in focus and space or enter is pressed\n\t 'click .title-bar' : '_clickTitleBar',\n\t 'keydown .title-bar' : '_keyDownTitleBar',\n\t 'click .selector' : 'toggleSelect'\n\t },\n\t\n\t /** expand when the title bar is clicked */\n\t _clickTitleBar : function( event ){\n\t event.stopPropagation();\n\t if( event.altKey ){\n\t this.toggleSelect( event );\n\t if( !this.selectable ){\n\t this.showSelector();\n\t }\n\t } else {\n\t this.toggleExpanded();\n\t }\n\t },\n\t\n\t /** expand when the title bar is in focus and enter or space is pressed */\n\t _keyDownTitleBar : function( event ){\n\t // bail (with propagation) if keydown and not space or enter\n\t var KEYCODE_SPACE = 32, KEYCODE_RETURN = 13;\n\t if( event && ( event.type === 'keydown' )\n\t &&( event.keyCode === KEYCODE_SPACE || event.keyCode === KEYCODE_RETURN ) ){\n\t this.toggleExpanded();\n\t event.stopPropagation();\n\t return false;\n\t }\n\t return true;\n\t },\n\t\n\t // ......................................................................... misc\n\t /** String representation */\n\t toString : function(){\n\t var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n\t return 'ListItemView(' + modelString + ')';\n\t }\n\t}));\n\t\n\t// ............................................................................ TEMPLATES\n\t/** underscore templates */\n\tListItemView.prototype.templates = (function(){\n\t\n\t var elTemplato = BASE_MVC.wrapTemplate([\n\t '
                        ',\n\t // errors, messages, etc.\n\t '
                        ',\n\t\n\t // multi-select checkbox\n\t '
                        ',\n\t '',\n\t '
                        ',\n\t // space for title bar buttons - gen. floated to the right\n\t '
                        ',\n\t '
                        ',\n\t\n\t // expandable area for more details\n\t '
                        ',\n\t '
                        '\n\t ]);\n\t\n\t var warnings = {};\n\t\n\t var titleBarTemplate = BASE_MVC.wrapTemplate([\n\t // adding a tabindex here allows focusing the title bar and the use of keydown to expand the dataset display\n\t '
                        ',\n\t //TODO: prob. belongs in dataset-list-item\n\t '',\n\t '
                        ',\n\t '<%- element.name %>',\n\t '
                        ',\n\t '
                        ',\n\t '
                        '\n\t ], 'element' );\n\t\n\t var subtitleTemplate = BASE_MVC.wrapTemplate([\n\t // override this\n\t '
                        '\n\t ]);\n\t\n\t var detailsTemplate = BASE_MVC.wrapTemplate([\n\t // override this\n\t '
                        '\n\t ]);\n\t\n\t return {\n\t el : elTemplato,\n\t warnings : warnings,\n\t titleBar : titleBarTemplate,\n\t subtitle : subtitleTemplate,\n\t details : detailsTemplate\n\t };\n\t}());\n\t\n\t\n\t//==============================================================================\n\t/** A view that is displayed in some larger list/grid/collection.\n\t * *AND* can display some sub-list of it's own when expanded (e.g. dataset collections).\n\t * This list will 'foldout' when the item is expanded depending on this.foldoutStyle:\n\t * If 'foldout': will expand vertically to show the nested list\n\t * If 'drilldown': will overlay the parent list\n\t *\n\t * Inherits from ListItemView.\n\t *\n\t * _renderDetails does the work of creating this.details: a sub-view that shows the nested list\n\t */\n\tvar FoldoutListItemView = ListItemView.extend({\n\t\n\t /** If 'foldout': show the sub-panel inside the expanded item\n\t * If 'drilldown': only fire events and handle by pub-sub\n\t * (allow the panel containing this item to attach it, hide itself, etc.)\n\t */\n\t foldoutStyle : 'foldout',\n\t /** Panel view class to instantiate for the sub-panel */\n\t foldoutPanelClass : null,\n\t\n\t /** override to:\n\t * add attributes foldoutStyle and foldoutPanelClass for config poly\n\t * disrespect attributes.expanded if drilldown\n\t */\n\t initialize : function( attributes ){\n\t if( this.foldoutStyle === 'drilldown' ){ this.expanded = false; }\n\t this.foldoutStyle = attributes.foldoutStyle || this.foldoutStyle;\n\t this.foldoutPanelClass = attributes.foldoutPanelClass || this.foldoutPanelClass;\n\t\n\t ListItemView.prototype.initialize.call( this, attributes );\n\t this.foldout = this._createFoldoutPanel();\n\t },\n\t\n\t /** in this override, attach the foldout panel when rendering details */\n\t _renderDetails : function(){\n\t if( this.foldoutStyle === 'drilldown' ){ return $(); }\n\t var $newDetails = ListItemView.prototype._renderDetails.call( this );\n\t return this._attachFoldout( this.foldout, $newDetails );\n\t },\n\t\n\t /** In this override, handle collection expansion. */\n\t _createFoldoutPanel : function(){\n\t var model = this.model;\n\t var FoldoutClass = this._getFoldoutPanelClass( model ),\n\t options = this._getFoldoutPanelOptions( model ),\n\t foldout = new FoldoutClass( _.extend( options, {\n\t model : model\n\t }));\n\t return foldout;\n\t },\n\t\n\t /** Stub to return proper foldout panel class */\n\t _getFoldoutPanelClass : function(){\n\t // override\n\t return this.foldoutPanelClass;\n\t },\n\t\n\t /** Stub to return proper foldout panel options */\n\t _getFoldoutPanelOptions : function(){\n\t return {\n\t // propagate foldout style down\n\t foldoutStyle : this.foldoutStyle,\n\t fxSpeed : this.fxSpeed\n\t };\n\t },\n\t\n\t /** Render the foldout panel inside the view, hiding controls */\n\t _attachFoldout : function( foldout, $whereTo ){\n\t $whereTo = $whereTo || this.$( '> .details' );\n\t this.foldout = foldout.render( 0 );\n\t foldout.$( '> .controls' ).hide();\n\t return $whereTo.append( foldout.$el );\n\t },\n\t\n\t /** In this override, branch on foldoutStyle to show expanded */\n\t expand : function(){\n\t var view = this;\n\t return view._fetchModelDetails()\n\t .always(function(){\n\t if( view.foldoutStyle === 'foldout' ){\n\t view._expand();\n\t } else if( view.foldoutStyle === 'drilldown' ){\n\t view._expandByDrilldown();\n\t }\n\t });\n\t },\n\t\n\t /** For drilldown, set up close handler and fire expanded:drilldown\n\t * containing views can listen to this and handle other things\n\t * (like hiding themselves) by listening for expanded/collapsed:drilldown\n\t */\n\t _expandByDrilldown : function(){\n\t var view = this;\n\t // attachment and rendering done by listener\n\t view.listenTo( view.foldout, 'close', function(){\n\t view.trigger( 'collapsed:drilldown', view, view.foldout );\n\t });\n\t view.trigger( 'expanded:drilldown', view, view.foldout );\n\t }\n\t\n\t});\n\t\n\t// ............................................................................ TEMPLATES\n\t/** underscore templates */\n\tFoldoutListItemView.prototype.templates = (function(){\n\t\n\t var detailsTemplate = BASE_MVC.wrapTemplate([\n\t '
                        ',\n\t // override with more info (that goes above the panel)\n\t '
                        '\n\t ], 'collection' );\n\t\n\t return _.extend( {}, ListItemView.prototype.templates, {\n\t details : detailsTemplate\n\t });\n\t}());\n\t\n\t\n\t//==============================================================================\n\t return {\n\t ExpandableView : ExpandableView,\n\t ListItemView : ListItemView,\n\t FoldoutListItemView : FoldoutListItemView\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(1), __webpack_require__(2), __webpack_require__(1)))\n\n/***/ },\n/* 43 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function($, _) {/**\n\t This is the base class of the tool form plugin. This class is e.g. inherited by the regular and the workflow tool form.\n\t*/\n\t!(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(4), __webpack_require__(55), __webpack_require__(7), __webpack_require__(38),\n\t __webpack_require__(17), __webpack_require__(28)], __WEBPACK_AMD_DEFINE_RESULT__ = function(Utils, Deferred, Ui, FormBase, CitationModel, CitationView) {\n\t return FormBase.extend({\n\t initialize: function(options) {\n\t var self = this;\n\t FormBase.prototype.initialize.call(this, options);\n\t this.deferred = new Deferred();\n\t if (options.inputs) {\n\t this._buildForm(options);\n\t } else {\n\t this.deferred.execute(function(process) {\n\t self._buildModel(process, options, true);\n\t });\n\t }\n\t // listen to history panel\n\t if ( options.listen_to_history && parent.Galaxy && parent.Galaxy.currHistoryPanel ) {\n\t this.listenTo( parent.Galaxy.currHistoryPanel.collection, 'change', function() {\n\t this.refresh();\n\t });\n\t }\n\t // destroy dom elements\n\t this.$el.on( 'remove', function() { self.remove() } );\n\t },\n\t\n\t /** Listen to history panel changes and update the tool form */\n\t refresh: function() {\n\t var self = this;\n\t self.deferred.reset();\n\t this.deferred.execute( function (process){\n\t self._updateModel( process)\n\t });\n\t },\n\t\n\t /** Wait for deferred build processes before removal */\n\t remove: function() {\n\t var self = this;\n\t this.$el.hide();\n\t this.deferred.execute(function(){\n\t FormBase.prototype.remove.call(self);\n\t Galaxy.emit.debug('tool-form-base::remove()', 'Destroy view.');\n\t });\n\t },\n\t\n\t /** Build form */\n\t _buildForm: function(options) {\n\t var self = this;\n\t this.options = Utils.merge(options, this.options);\n\t this.options = Utils.merge({\n\t icon : options.icon,\n\t title : '' + options.name + ' ' + options.description + ' (Galaxy Version ' + options.version + ')',\n\t operations : !this.options.hide_operations && this._operations(),\n\t onchange : function() {\n\t self.refresh();\n\t }\n\t }, this.options);\n\t this.options.customize && this.options.customize( this.options );\n\t this.render();\n\t if ( !this.options.collapsible ) {\n\t this.$el.append( $( '
                        ' ).addClass( 'ui-margin-top-large' ).append( this._footer() ) );\n\t }\n\t },\n\t\n\t /** Builds a new model through api call and recreates the entire form\n\t */\n\t _buildModel: function(process, options, hide_message) {\n\t var self = this;\n\t this.options.id = options.id;\n\t this.options.version = options.version;\n\t\n\t // build request url\n\t var build_url = '';\n\t var build_data = {};\n\t if ( options.job_id ) {\n\t build_url = Galaxy.root + 'api/jobs/' + options.job_id + '/build_for_rerun';\n\t } else {\n\t build_url = Galaxy.root + 'api/tools/' + options.id + '/build';\n\t if ( Galaxy.params && Galaxy.params.tool_id == options.id ) {\n\t build_data = $.extend( {}, Galaxy.params );\n\t options.version && ( build_data[ 'tool_version' ] = options.version );\n\t }\n\t }\n\t\n\t // get initial model\n\t Utils.get({\n\t url : build_url,\n\t data : build_data,\n\t success : function(new_model) {\n\t new_model = new_model.tool_model || new_model;\n\t if( !new_model.display ) {\n\t window.location = Galaxy.root;\n\t return;\n\t }\n\t self._buildForm(new_model);\n\t !hide_message && self.message.update({\n\t status : 'success',\n\t message : 'Now you are using \\'' + self.options.name + '\\' version ' + self.options.version + ', id \\'' + self.options.id + '\\'.',\n\t persistent : false\n\t });\n\t Galaxy.emit.debug('tool-form-base::initialize()', 'Initial tool model ready.', new_model);\n\t process.resolve();\n\t },\n\t error : function(response, status) {\n\t var error_message = ( response && response.err_msg ) || 'Uncaught error.';\n\t if ( status == 401 ) {\n\t window.location = Galaxy.root + 'user/login?' + $.param({ redirect : Galaxy.root + '?tool_id=' + self.options.id });\n\t } else if ( self.$el.is(':empty') ) {\n\t self.$el.prepend((new Ui.Message({\n\t message : error_message,\n\t status : 'danger',\n\t persistent : true,\n\t large : true\n\t })).$el);\n\t } else {\n\t Galaxy.modal && Galaxy.modal.show({\n\t title : 'Tool request failed',\n\t body : error_message,\n\t buttons : {\n\t 'Close' : function() {\n\t Galaxy.modal.hide();\n\t }\n\t }\n\t });\n\t }\n\t Galaxy.emit.debug('tool-form::initialize()', 'Initial tool model request failed.', response);\n\t process.reject();\n\t }\n\t });\n\t },\n\t\n\t /** Request a new model for an already created tool form and updates the form inputs\n\t */\n\t _updateModel: function(process) {\n\t // link this\n\t var self = this;\n\t var model_url = this.options.update_url || Galaxy.root + 'api/tools/' + this.options.id + '/build';\n\t var current_state = {\n\t tool_id : this.options.id,\n\t tool_version : this.options.version,\n\t inputs : $.extend(true, {}, self.data.create())\n\t }\n\t this.wait(true);\n\t\n\t // log tool state\n\t Galaxy.emit.debug('tool-form-base::_updateModel()', 'Sending current state.', current_state);\n\t\n\t // post job\n\t Utils.request({\n\t type : 'POST',\n\t url : model_url,\n\t data : current_state,\n\t success : function(new_model) {\n\t self.update(new_model['tool_model'] || new_model);\n\t self.options.update && self.options.update(new_model);\n\t self.wait(false);\n\t Galaxy.emit.debug('tool-form-base::_updateModel()', 'Received new model.', new_model);\n\t process.resolve();\n\t },\n\t error : function(response) {\n\t Galaxy.emit.debug('tool-form-base::_updateModel()', 'Refresh request failed.', response);\n\t process.reject();\n\t }\n\t });\n\t },\n\t\n\t /** Create tool operation menu\n\t */\n\t _operations: function() {\n\t var self = this;\n\t var options = this.options;\n\t\n\t // button for version selection\n\t var versions_button = new Ui.ButtonMenu({\n\t icon : 'fa-cubes',\n\t title : (!options.narrow && 'Versions') || null,\n\t tooltip : 'Select another tool version'\n\t });\n\t if (!options.sustain_version && options.versions && options.versions.length > 1) {\n\t for (var i in options.versions) {\n\t var version = options.versions[i];\n\t if (version != options.version) {\n\t versions_button.addMenu({\n\t title : 'Switch to ' + version,\n\t version : version,\n\t icon : 'fa-cube',\n\t onclick : function() {\n\t // here we update the tool version (some tools encode the version also in the id)\n\t var id = options.id.replace(options.version, this.version);\n\t var version = this.version;\n\t // queue model request\n\t self.deferred.reset();\n\t self.deferred.execute(function(process) {\n\t self._buildModel(process, {id: id, version: version})\n\t });\n\t }\n\t });\n\t }\n\t }\n\t } else {\n\t versions_button.$el.hide();\n\t }\n\t\n\t // button for options e.g. search, help\n\t var menu_button = new Ui.ButtonMenu({\n\t icon : 'fa-caret-down',\n\t title : (!options.narrow && 'Options') || null,\n\t tooltip : 'View available options'\n\t });\n\t if(options.biostar_url) {\n\t menu_button.addMenu({\n\t icon : 'fa-question-circle',\n\t title : 'Question?',\n\t tooltip : 'Ask a question about this tool (Biostar)',\n\t onclick : function() {\n\t window.open(options.biostar_url + '/p/new/post/');\n\t }\n\t });\n\t menu_button.addMenu({\n\t icon : 'fa-search',\n\t title : 'Search',\n\t tooltip : 'Search help for this tool (Biostar)',\n\t onclick : function() {\n\t window.open(options.biostar_url + '/local/search/page/?q=' + options.name);\n\t }\n\t });\n\t };\n\t menu_button.addMenu({\n\t icon : 'fa-share',\n\t title : 'Share',\n\t tooltip : 'Share this tool',\n\t onclick : function() {\n\t prompt('Copy to clipboard: Ctrl+C, Enter', window.location.origin + Galaxy.root + 'root?tool_id=' + options.id);\n\t }\n\t });\n\t\n\t // add admin operations\n\t if (Galaxy.user && Galaxy.user.get('is_admin')) {\n\t menu_button.addMenu({\n\t icon : 'fa-download',\n\t title : 'Download',\n\t tooltip : 'Download this tool',\n\t onclick : function() {\n\t window.location.href = Galaxy.root + 'api/tools/' + options.id + '/download';\n\t }\n\t });\n\t }\n\t\n\t // button for version selection\n\t if (options.requirements && options.requirements.length > 0) {\n\t menu_button.addMenu({\n\t icon : 'fa-info-circle',\n\t title : 'Requirements',\n\t tooltip : 'Display tool requirements',\n\t onclick : function() {\n\t if (!this.visible || self.portlet.collapsed ) {\n\t this.visible = true;\n\t self.portlet.expand();\n\t self.message.update({\n\t persistent : true,\n\t message : self._templateRequirements(options),\n\t status : 'info'\n\t });\n\t } else {\n\t this.visible = false;\n\t self.message.update({\n\t message : ''\n\t });\n\t }\n\t }\n\t });\n\t }\n\t\n\t // add toolshed url\n\t if (options.sharable_url) {\n\t menu_button.addMenu({\n\t icon : 'fa-external-link',\n\t title : 'See in Tool Shed',\n\t tooltip : 'Access the repository',\n\t onclick : function() {\n\t window.open(options.sharable_url);\n\t }\n\t });\n\t }\n\t\n\t return {\n\t menu : menu_button,\n\t versions : versions_button\n\t }\n\t },\n\t\n\t /** Create footer\n\t */\n\t _footer: function() {\n\t var options = this.options;\n\t var $el = $( '
                        ' ).append( this._templateHelp( options ) );\n\t if ( options.citations ) {\n\t var $citations = $( '
                        ' );\n\t var citations = new CitationModel.ToolCitationCollection();\n\t citations.tool_id = options.id;\n\t var citation_list_view = new CitationView.CitationListView({ el: $citations, collection: citations });\n\t citation_list_view.render();\n\t citations.fetch();\n\t $el.append( $citations );\n\t }\n\t return $el;\n\t },\n\t\n\t /** Templates\n\t */\n\t _templateHelp: function( options ) {\n\t var $tmpl = $( '
                        ' ).addClass( 'ui-form-help' ).append( options.help );\n\t $tmpl.find( 'a' ).attr( 'target', '_blank' );\n\t return $tmpl;\n\t },\n\t\n\t _templateRequirements: function( options ) {\n\t var nreq = options.requirements.length;\n\t if ( nreq > 0 ) {\n\t var requirements_message = 'This tool requires ';\n\t _.each( options.requirements, function( req, i ) {\n\t requirements_message += req.name + ( req.version ? ' (Version ' + req.version + ')' : '' ) + ( i < nreq - 2 ? ', ' : ( i == nreq - 2 ? ' and ' : '' ) );\n\t });\n\t var requirements_link = $( '' ).attr( 'target', '_blank' ).attr( 'href', 'https://wiki.galaxyproject.org/Tools/Requirements' ).text( 'here' );\n\t return $( '' ).append( requirements_message + '. Click ' ).append( requirements_link ).append( ' for more information.' );\n\t }\n\t return 'No requirements found.';\n\t }\n\t });\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(1), __webpack_require__(2)))\n\n/***/ },\n/* 44 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, $) {/**\n\t * Model, view, and controller objects for Galaxy tools and tool panel.\n\t */\n\t\n\t !(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(2),\n\t __webpack_require__(16),\n\t __webpack_require__(11),\n\t __webpack_require__(18)\n\t\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function(_, util, data, ToolForm) {\n\t 'use strict';\n\t\n\t/**\n\t * Mixin for tracking model visibility.\n\t */\n\tvar VisibilityMixin = {\n\t hidden: false,\n\t\n\t show: function() {\n\t this.set(\"hidden\", false);\n\t },\n\t\n\t hide: function() {\n\t this.set(\"hidden\", true);\n\t },\n\t\n\t toggle: function() {\n\t this.set(\"hidden\", !this.get(\"hidden\"));\n\t },\n\t\n\t is_visible: function() {\n\t return !this.attributes.hidden;\n\t }\n\t\n\t};\n\t\n\t/**\n\t * A tool parameter.\n\t */\n\tvar ToolParameter = Backbone.Model.extend({\n\t defaults: {\n\t name: null,\n\t label: null,\n\t type: null,\n\t value: null,\n\t html: null,\n\t num_samples: 5\n\t },\n\t\n\t initialize: function(options) {\n\t this.attributes.html = unescape(this.attributes.html);\n\t },\n\t\n\t copy: function() {\n\t return new ToolParameter(this.toJSON());\n\t },\n\t\n\t set_value: function(value) {\n\t this.set('value', value || '');\n\t }\n\t});\n\t\n\tvar ToolParameterCollection = Backbone.Collection.extend({\n\t model: ToolParameter\n\t});\n\t\n\t/**\n\t * A data tool parameter.\n\t */\n\tvar DataToolParameter = ToolParameter.extend({});\n\t\n\t/**\n\t * An integer tool parameter.\n\t */\n\tvar IntegerToolParameter = ToolParameter.extend({\n\t set_value: function(value) {\n\t this.set('value', parseInt(value, 10));\n\t },\n\t\n\t /**\n\t * Returns samples from a tool input.\n\t */\n\t get_samples: function() {\n\t return d3.scale.linear()\n\t .domain([this.get('min'), this.get('max')])\n\t .ticks(this.get('num_samples'));\n\t }\n\t});\n\t\n\tvar FloatToolParameter = IntegerToolParameter.extend({\n\t set_value: function(value) {\n\t this.set('value', parseFloat(value));\n\t }\n\t});\n\t\n\t/**\n\t * A select tool parameter.\n\t */\n\tvar SelectToolParameter = ToolParameter.extend({\n\t /**\n\t * Returns tool options.\n\t */\n\t get_samples: function() {\n\t return _.map(this.get('options'), function(option) {\n\t return option[0];\n\t });\n\t }\n\t});\n\t\n\t// Set up dictionary of parameter types.\n\tToolParameter.subModelTypes = {\n\t 'integer': IntegerToolParameter,\n\t 'float': FloatToolParameter,\n\t 'data': DataToolParameter,\n\t 'select': SelectToolParameter\n\t};\n\t\n\t/**\n\t * A Galaxy tool.\n\t */\n\tvar Tool = Backbone.Model.extend({\n\t // Default attributes.\n\t defaults: {\n\t id: null,\n\t name: null,\n\t description: null,\n\t target: null,\n\t inputs: [],\n\t outputs: []\n\t },\n\t\n\t urlRoot: Galaxy.root + 'api/tools',\n\t\n\t initialize: function(options) {\n\t\n\t // Set parameters.\n\t this.set('inputs', new ToolParameterCollection(_.map(options.inputs, function(p) {\n\t var p_class = ToolParameter.subModelTypes[p.type] || ToolParameter;\n\t return new p_class(p);\n\t })));\n\t },\n\t\n\t /**\n\t *\n\t */\n\t toJSON: function() {\n\t var rval = Backbone.Model.prototype.toJSON.call(this);\n\t\n\t // Convert inputs to JSON manually.\n\t rval.inputs = this.get('inputs').map(function(i) { return i.toJSON(); });\n\t return rval;\n\t },\n\t\n\t /**\n\t * Removes inputs of a particular type; this is useful because not all inputs can be handled by\n\t * client and server yet.\n\t */\n\t remove_inputs: function(types) {\n\t var tool = this,\n\t incompatible_inputs = tool.get('inputs').filter( function(input) {\n\t return ( types.indexOf( input.get('type') ) !== -1);\n\t });\n\t tool.get('inputs').remove(incompatible_inputs);\n\t },\n\t\n\t /**\n\t * Returns object copy, optionally including only inputs that can be sampled.\n\t */\n\t copy: function(only_samplable_inputs) {\n\t var copy = new Tool(this.toJSON());\n\t\n\t // Return only samplable inputs if flag is set.\n\t if (only_samplable_inputs) {\n\t var valid_inputs = new Backbone.Collection();\n\t copy.get('inputs').each(function(input) {\n\t if (input.get_samples()) {\n\t valid_inputs.push(input);\n\t }\n\t });\n\t copy.set('inputs', valid_inputs);\n\t }\n\t\n\t return copy;\n\t },\n\t\n\t apply_search_results: function(results) {\n\t ( _.indexOf(results, this.attributes.id) !== -1 ? this.show() : this.hide() );\n\t return this.is_visible();\n\t },\n\t\n\t /**\n\t * Set a tool input's value.\n\t */\n\t set_input_value: function(name, value) {\n\t this.get('inputs').find(function(input) {\n\t return input.get('name') === name;\n\t }).set('value', value);\n\t },\n\t\n\t /**\n\t * Set many input values at once.\n\t */\n\t set_input_values: function(inputs_dict) {\n\t var self = this;\n\t _.each(_.keys(inputs_dict), function(input_name) {\n\t self.set_input_value(input_name, inputs_dict[input_name]);\n\t });\n\t },\n\t\n\t /**\n\t * Run tool; returns a Deferred that resolves to the tool's output(s).\n\t */\n\t run: function() {\n\t return this._run();\n\t },\n\t\n\t /**\n\t * Rerun tool using regions and a target dataset.\n\t */\n\t rerun: function(target_dataset, regions) {\n\t return this._run({\n\t action: 'rerun',\n\t target_dataset_id: target_dataset.id,\n\t regions: regions\n\t });\n\t },\n\t\n\t /**\n\t * Returns input dict for tool's inputs.\n\t */\n\t get_inputs_dict: function() {\n\t var input_dict = {};\n\t this.get('inputs').each(function(input) {\n\t input_dict[input.get('name')] = input.get('value');\n\t });\n\t return input_dict;\n\t },\n\t\n\t /**\n\t * Run tool; returns a Deferred that resolves to the tool's output(s).\n\t * NOTE: this method is a helper method and should not be called directly.\n\t */\n\t _run: function(additional_params) {\n\t // Create payload.\n\t var payload = _.extend({\n\t tool_id: this.id,\n\t inputs: this.get_inputs_dict()\n\t }, additional_params);\n\t\n\t // Because job may require indexing datasets, use server-side\n\t // deferred to ensure that job is run. Also use deferred that\n\t // resolves to outputs from tool.\n\t var run_deferred = $.Deferred(),\n\t ss_deferred = new util.ServerStateDeferred({\n\t ajax_settings: {\n\t url: this.urlRoot,\n\t data: JSON.stringify(payload),\n\t dataType: \"json\",\n\t contentType: 'application/json',\n\t type: \"POST\"\n\t },\n\t interval: 2000,\n\t success_fn: function(response) {\n\t return response !== \"pending\";\n\t }\n\t });\n\t\n\t // Run job and resolve run_deferred to tool outputs.\n\t $.when(ss_deferred.go()).then(function(result) {\n\t run_deferred.resolve(new data.DatasetCollection(result));\n\t });\n\t return run_deferred;\n\t }\n\t});\n\t_.extend(Tool.prototype, VisibilityMixin);\n\t\n\t/**\n\t * Tool view.\n\t */\n\tvar ToolView = Backbone.View.extend({\n\t\n\t});\n\t\n\t/**\n\t * Wrap collection of tools for fast access/manipulation.\n\t */\n\tvar ToolCollection = Backbone.Collection.extend({\n\t model: Tool\n\t});\n\t\n\t/**\n\t * Label or section header in tool panel.\n\t */\n\tvar ToolSectionLabel = Backbone.Model.extend(VisibilityMixin);\n\t\n\t/**\n\t * Section of tool panel with elements (labels and tools).\n\t */\n\tvar ToolSection = Backbone.Model.extend({\n\t defaults: {\n\t elems: [],\n\t open: false\n\t },\n\t\n\t clear_search_results: function() {\n\t _.each(this.attributes.elems, function(elt) {\n\t elt.show();\n\t });\n\t\n\t this.show();\n\t this.set(\"open\", false);\n\t },\n\t\n\t apply_search_results: function(results) {\n\t var all_hidden = true,\n\t cur_label;\n\t _.each(this.attributes.elems, function(elt) {\n\t if (elt instanceof ToolSectionLabel) {\n\t cur_label = elt;\n\t cur_label.hide();\n\t }\n\t else if (elt instanceof Tool) {\n\t if (elt.apply_search_results(results)) {\n\t all_hidden = false;\n\t if (cur_label) {\n\t cur_label.show();\n\t }\n\t }\n\t }\n\t });\n\t\n\t if (all_hidden) {\n\t this.hide();\n\t }\n\t else {\n\t this.show();\n\t this.set(\"open\", true);\n\t }\n\t }\n\t});\n\t_.extend(ToolSection.prototype, VisibilityMixin);\n\t\n\t/**\n\t * Tool search that updates results when query is changed. Result value of null\n\t * indicates that query was not run; if not null, results are from search using\n\t * query.\n\t */\n\tvar ToolSearch = Backbone.Model.extend({\n\t defaults: {\n\t search_hint_string: \"search tools\",\n\t min_chars_for_search: 3,\n\t clear_btn_url: \"\",\n\t search_url: \"\",\n\t visible: true,\n\t query: \"\",\n\t results: null,\n\t // ESC (27) will clear the input field and tool search filters\n\t clear_key: 27\n\t },\n\t\n\t urlRoot: Galaxy.root + 'api/tools',\n\t\n\t initialize: function() {\n\t this.on(\"change:query\", this.do_search);\n\t },\n\t\n\t /**\n\t * Do the search and update the results.\n\t */\n\t do_search: function() {\n\t var query = this.attributes.query;\n\t\n\t // If query is too short, do not search.\n\t if (query.length < this.attributes.min_chars_for_search) {\n\t this.set(\"results\", null);\n\t return;\n\t }\n\t\n\t // Do search via AJAX.\n\t var q = query;\n\t // Stop previous ajax-request\n\t if (this.timer) {\n\t clearTimeout(this.timer);\n\t }\n\t // Start a new ajax-request in X ms\n\t $(\"#search-clear-btn\").hide();\n\t $(\"#search-spinner\").show();\n\t var self = this;\n\t this.timer = setTimeout(function () {\n\t // log the search to analytics if present\n\t if ( typeof ga !== 'undefined' ) {\n\t ga( 'send', 'pageview', Galaxy.root + '?q=' + q );\n\t }\n\t $.get( self.urlRoot, { q: q }, function (data) {\n\t self.set(\"results\", data);\n\t $(\"#search-spinner\").hide();\n\t $(\"#search-clear-btn\").show();\n\t }, \"json\" );\n\t }, 400 );\n\t },\n\t\n\t clear_search: function() {\n\t this.set(\"query\", \"\");\n\t this.set(\"results\", null);\n\t }\n\t\n\t});\n\t_.extend(ToolSearch.prototype, VisibilityMixin);\n\t\n\t/**\n\t * Tool Panel.\n\t */\n\tvar ToolPanel = Backbone.Model.extend({\n\t\n\t initialize: function(options) {\n\t this.attributes.tool_search = options.tool_search;\n\t this.attributes.tool_search.on(\"change:results\", this.apply_search_results, this);\n\t this.attributes.tools = options.tools;\n\t this.attributes.layout = new Backbone.Collection( this.parse(options.layout) );\n\t },\n\t\n\t /**\n\t * Parse tool panel dictionary and return collection of tool panel elements.\n\t */\n\t parse: function(response) {\n\t // Recursive function to parse tool panel elements.\n\t var self = this,\n\t // Helper to recursively parse tool panel.\n\t parse_elt = function(elt_dict) {\n\t var type = elt_dict.model_class;\n\t // There are many types of tools; for now, anything that ends in 'Tool'\n\t // is treated as a generic tool.\n\t if ( type.indexOf('Tool') === type.length - 4 ) {\n\t return self.attributes.tools.get(elt_dict.id);\n\t }\n\t else if (type === 'ToolSection') {\n\t // Parse elements.\n\t var elems = _.map(elt_dict.elems, parse_elt);\n\t elt_dict.elems = elems;\n\t return new ToolSection(elt_dict);\n\t }\n\t else if (type === 'ToolSectionLabel') {\n\t return new ToolSectionLabel(elt_dict);\n\t }\n\t };\n\t\n\t return _.map(response, parse_elt);\n\t },\n\t\n\t clear_search_results: function() {\n\t this.get('layout').each(function(panel_elt) {\n\t if (panel_elt instanceof ToolSection) {\n\t panel_elt.clear_search_results();\n\t }\n\t else {\n\t // Label or tool, so just show.\n\t panel_elt.show();\n\t }\n\t });\n\t },\n\t\n\t apply_search_results: function() {\n\t var results = this.get('tool_search').get('results');\n\t if (results === null) {\n\t this.clear_search_results();\n\t return;\n\t }\n\t\n\t var cur_label = null;\n\t this.get('layout').each(function(panel_elt) {\n\t if (panel_elt instanceof ToolSectionLabel) {\n\t cur_label = panel_elt;\n\t cur_label.hide();\n\t }\n\t else if (panel_elt instanceof Tool) {\n\t if (panel_elt.apply_search_results(results)) {\n\t if (cur_label) {\n\t cur_label.show();\n\t }\n\t }\n\t }\n\t else {\n\t // Starting new section, so clear current label.\n\t cur_label = null;\n\t panel_elt.apply_search_results(results);\n\t }\n\t });\n\t }\n\t});\n\t\n\t/**\n\t * View classes for Galaxy tools and tool panel.\n\t *\n\t * Views use the templates defined below for rendering. Views update as needed\n\t * based on (a) model/collection events and (b) user interactions; in this sense,\n\t * they are controllers are well and the HTML is the real view in the MVC architecture.\n\t */\n\t\n\t/**\n\t * Base view that handles visibility based on model's hidden attribute.\n\t */\n\tvar BaseView = Backbone.View.extend({\n\t initialize: function() {\n\t this.model.on(\"change:hidden\", this.update_visible, this);\n\t this.update_visible();\n\t },\n\t update_visible: function() {\n\t ( this.model.attributes.hidden ? this.$el.hide() : this.$el.show() );\n\t }\n\t});\n\t\n\t/**\n\t * Link to a tool.\n\t */\n\tvar ToolLinkView = BaseView.extend({\n\t tagName: 'div',\n\t\n\t render: function() {\n\t // create element\n\t var $link = $('
                        ');\n\t $link.append(templates.tool_link(this.model.toJSON()));\n\t\n\t var formStyle = this.model.get( 'form_style', null );\n\t // open upload dialog for upload tool\n\t if (this.model.id === 'upload1') {\n\t $link.find('a').on('click', function(e) {\n\t e.preventDefault();\n\t Galaxy.upload.show();\n\t });\n\t }\n\t else if ( formStyle === 'regular' ) { // regular tools\n\t var self = this;\n\t $link.find('a').on('click', function(e) {\n\t e.preventDefault();\n\t var form = new ToolForm.View( { id : self.model.id, version : self.model.get('version') } );\n\t form.deferred.execute(function() {\n\t Galaxy.app.display( form );\n\t });\n\t });\n\t }\n\t\n\t // add element\n\t this.$el.append($link);\n\t return this;\n\t }\n\t});\n\t\n\t/**\n\t * Panel label/section header.\n\t */\n\tvar ToolSectionLabelView = BaseView.extend({\n\t tagName: 'div',\n\t className: 'toolPanelLabel',\n\t\n\t render: function() {\n\t this.$el.append( $(\"\").text(this.model.attributes.text) );\n\t return this;\n\t }\n\t});\n\t\n\t/**\n\t * Panel section.\n\t */\n\tvar ToolSectionView = BaseView.extend({\n\t tagName: 'div',\n\t className: 'toolSectionWrapper',\n\t\n\t initialize: function() {\n\t BaseView.prototype.initialize.call(this);\n\t this.model.on(\"change:open\", this.update_open, this);\n\t },\n\t\n\t render: function() {\n\t // Build using template.\n\t this.$el.append( templates.panel_section(this.model.toJSON()) );\n\t\n\t // Add tools to section.\n\t var section_body = this.$el.find(\".toolSectionBody\");\n\t _.each(this.model.attributes.elems, function(elt) {\n\t if (elt instanceof Tool) {\n\t var tool_view = new ToolLinkView({model: elt, className: \"toolTitle\"});\n\t tool_view.render();\n\t section_body.append(tool_view.$el);\n\t }\n\t else if (elt instanceof ToolSectionLabel) {\n\t var label_view = new ToolSectionLabelView({model: elt});\n\t label_view.render();\n\t section_body.append(label_view.$el);\n\t }\n\t else {\n\t // TODO: handle nested section bodies?\n\t }\n\t });\n\t return this;\n\t },\n\t\n\t events: {\n\t 'click .toolSectionTitle > a': 'toggle'\n\t },\n\t\n\t /**\n\t * Toggle visibility of tool section.\n\t */\n\t toggle: function() {\n\t this.model.set(\"open\", !this.model.attributes.open);\n\t },\n\t\n\t /**\n\t * Update whether section is open or close.\n\t */\n\t update_open: function() {\n\t (this.model.attributes.open ?\n\t this.$el.children(\".toolSectionBody\").slideDown(\"fast\") :\n\t this.$el.children(\".toolSectionBody\").slideUp(\"fast\")\n\t );\n\t }\n\t});\n\t\n\tvar ToolSearchView = Backbone.View.extend({\n\t tagName: 'div',\n\t id: 'tool-search',\n\t className: 'bar',\n\t\n\t events: {\n\t 'click': 'focus_and_select',\n\t 'keyup :input': 'query_changed',\n\t 'click #search-clear-btn': 'clear'\n\t },\n\t\n\t render: function() {\n\t this.$el.append( templates.tool_search(this.model.toJSON()) );\n\t if (!this.model.is_visible()) {\n\t this.$el.hide();\n\t }\n\t\n\t // Adjust top for issue 2907 depending on whether the messagebox is visible.\n\t if ($(\"#messagebox\").is(\":visible\")) {\n\t this.$el.css(\"top\",\"95px\");\n\t }\n\t\n\t this.$el.find('[title]').tooltip();\n\t return this;\n\t },\n\t\n\t focus_and_select: function() {\n\t this.$el.find(\":input\").focus().select();\n\t },\n\t\n\t clear: function() {\n\t this.model.clear_search();\n\t this.$el.find(\":input\").val('');\n\t this.focus_and_select();\n\t return false;\n\t },\n\t\n\t query_changed: function( evData ) {\n\t // check for the 'clear key' (ESC) first\n\t if( ( this.model.attributes.clear_key ) &&\n\t ( this.model.attributes.clear_key === evData.which ) ){\n\t this.clear();\n\t return false;\n\t }\n\t this.model.set(\"query\", this.$el.find(\":input\").val());\n\t }\n\t});\n\t\n\t/**\n\t * Tool panel view. Events triggered include:\n\t * tool_link_click(click event, tool_model)\n\t */\n\tvar ToolPanelView = Backbone.View.extend({\n\t tagName: 'div',\n\t className: 'toolMenu',\n\t\n\t /**\n\t * Set up view.\n\t */\n\t initialize: function() {\n\t this.model.get('tool_search').on(\"change:results\", this.handle_search_results, this);\n\t },\n\t\n\t render: function() {\n\t var self = this;\n\t\n\t // Render search.\n\t var search_view = new ToolSearchView( { model: this.model.get('tool_search') } );\n\t search_view.render();\n\t self.$el.append(search_view.$el);\n\t\n\t // Render panel.\n\t this.model.get('layout').each(function(panel_elt) {\n\t if (panel_elt instanceof ToolSection) {\n\t var section_title_view = new ToolSectionView({model: panel_elt});\n\t section_title_view.render();\n\t self.$el.append(section_title_view.$el);\n\t }\n\t else if (panel_elt instanceof Tool) {\n\t var tool_view = new ToolLinkView({model: panel_elt, className: \"toolTitleNoSection\"});\n\t tool_view.render();\n\t self.$el.append(tool_view.$el);\n\t }\n\t else if (panel_elt instanceof ToolSectionLabel) {\n\t var label_view = new ToolSectionLabelView({model: panel_elt});\n\t label_view.render();\n\t self.$el.append(label_view.$el);\n\t }\n\t });\n\t\n\t // Setup tool link click eventing.\n\t self.$el.find(\"a.tool-link\").click(function(e) {\n\t // Tool id is always the first class.\n\t var\n\t tool_id = $(this).attr('class').split(/\\s+/)[0],\n\t tool = self.model.get('tools').get(tool_id);\n\t\n\t self.trigger(\"tool_link_click\", e, tool);\n\t });\n\t\n\t return this;\n\t },\n\t\n\t handle_search_results: function() {\n\t var results = this.model.get('tool_search').get('results');\n\t if (results && results.length === 0) {\n\t $(\"#search-no-results\").show();\n\t }\n\t else {\n\t $(\"#search-no-results\").hide();\n\t }\n\t }\n\t});\n\t\n\t/**\n\t * View for working with a tool: setting parameters and inputs and executing the tool.\n\t */\n\tvar ToolFormView = Backbone.View.extend({\n\t className: 'toolForm',\n\t\n\t render: function() {\n\t this.$el.children().remove();\n\t this.$el.append( templates.tool_form(this.model.toJSON()) );\n\t }\n\t});\n\t\n\t/**\n\t * Integrated tool menu + tool execution.\n\t */\n\tvar IntegratedToolMenuAndView = Backbone.View.extend({\n\t className: 'toolMenuAndView',\n\t\n\t initialize: function() {\n\t this.tool_panel_view = new ToolPanelView({collection: this.collection});\n\t this.tool_form_view = new ToolFormView();\n\t },\n\t\n\t render: function() {\n\t // Render and append tool panel.\n\t this.tool_panel_view.render();\n\t this.tool_panel_view.$el.css(\"float\", \"left\");\n\t this.$el.append(this.tool_panel_view.$el);\n\t\n\t // Append tool form view.\n\t this.tool_form_view.$el.hide();\n\t this.$el.append(this.tool_form_view.$el);\n\t\n\t // On tool link click, show tool.\n\t var self = this;\n\t this.tool_panel_view.on(\"tool_link_click\", function(e, tool) {\n\t // Prevents click from activating link:\n\t e.preventDefault();\n\t // Show tool that was clicked on:\n\t self.show_tool(tool);\n\t });\n\t },\n\t\n\t /**\n\t * Fetch and display tool.\n\t */\n\t show_tool: function(tool) {\n\t var self = this;\n\t tool.fetch().done( function() {\n\t self.tool_form_view.model = tool;\n\t self.tool_form_view.render();\n\t self.tool_form_view.$el.show();\n\t $('#left').width(\"650px\");\n\t });\n\t }\n\t});\n\t\n\t// TODO: move into relevant views\n\tvar templates = {\n\t // the search bar at the top of the tool panel\n\t tool_search : _.template([\n\t '\" autocomplete=\"off\" type=\"text\" />',\n\t ' ',\n\t //TODO: replace with icon\n\t '',\n\t ].join('')),\n\t\n\t // the category level container in the tool panel (e.g. 'Get Data', 'Text Manipulation')\n\t panel_section : _.template([\n\t '
                        \">',\n\t '<%- name %>',\n\t '
                        ',\n\t '
                        \" class=\"toolSectionBody\" style=\"display: none;\">',\n\t '
                        ',\n\t '
                        '\n\t ].join('')),\n\t\n\t // a single tool's link in the tool panel; will load the tool form in the center panel\n\t tool_link : _.template([\n\t '',\n\t '<% _.each( labels, function( label ){ %>',\n\t '\">',\n\t '<%- label %>',\n\t '',\n\t '<% }); %>',\n\t '',\n\t ' tool-link\" href=\"<%= link %>\" target=\"<%- target %>\" minsizehint=\"<%- min_width %>\">',\n\t '<%- name %>',\n\t '',\n\t ' <%- description %>'\n\t ].join('')),\n\t\n\t // the tool form for entering tool parameters, viewing help and executing the tool\n\t // loaded when a tool link is clicked in the tool panel\n\t tool_form : _.template([\n\t '
                        <%- tool.name %> (version <%- tool.version %>)
                        ',\n\t '
                        ',\n\t '<% _.each( tool.inputs, function( input ){ %>',\n\t '
                        ',\n\t '',\n\t '
                        ',\n\t '<%= input.html %>',\n\t '
                        ',\n\t '
                        ',\n\t '<%- input.help %>',\n\t '
                        ',\n\t '
                        ',\n\t '
                        ',\n\t '<% }); %>',\n\t '
                        ',\n\t '
                        ',\n\t '',\n\t '
                        ',\n\t '
                        ',\n\t '
                        <% tool.help %>
                        ',\n\t '
                        ',\n\t // TODO: we need scoping here because 'help' is the dom for the help menu in the masthead\n\t // which implies a leaky variable that I can't find\n\t ].join(''), { variable: 'tool' }),\n\t};\n\t\n\t\n\t// Exports\n\treturn {\n\t ToolParameter: ToolParameter,\n\t IntegerToolParameter: IntegerToolParameter,\n\t SelectToolParameter: SelectToolParameter,\n\t Tool: Tool,\n\t ToolCollection: ToolCollection,\n\t ToolSearch: ToolSearch,\n\t ToolPanel: ToolPanel,\n\t ToolPanelView: ToolPanelView,\n\t ToolFormView: ToolFormView\n\t};\n\t\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(1)))\n\n/***/ },\n/* 45 */,\n/* 46 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, $) {/** Renders the color picker used e.g. in the tool form **/\n\t!(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(4)], __WEBPACK_AMD_DEFINE_RESULT__ = function( Utils ) {\n\t return Backbone.View.extend({\n\t colors: {\n\t standard: ['c00000','ff0000','ffc000','ffff00','92d050','00b050','00b0f0','0070c0','002060','7030a0'],\n\t base : ['ffffff','000000','eeece1','1f497d','4f81bd','c0504d','9bbb59','8064a2','4bacc6','f79646'],\n\t theme :[['f2f2f2','7f7f7f','ddd9c3','c6d9f0','dbe5f1','f2dcdb','ebf1dd','e5e0ec','dbeef3','fdeada'],\n\t ['d8d8d8','595959','c4bd97','8db3e2','b8cce4','e5b9b7','d7e3bc','ccc1d9','b7dde8','fbd5b5'],\n\t ['bfbfbf','3f3f3f','938953','548dd4','95b3d7','d99694','c3d69b','b2a2c7','92cddc','fac08f'],\n\t ['a5a5a5','262626','494429','17365d','366092','953734','76923c','5f497a','31859b','e36c09'],\n\t ['7f7f7e','0c0c0c','1d1b10','0f243e','244061','632423','4f6128','3f3151','205867','974806']]\n\t },\n\t\n\t initialize : function( options ) {\n\t this.options = Utils.merge( options, {} );\n\t this.setElement( this._template() );\n\t this.$panel = this.$( '.ui-color-picker-panel' );\n\t this.$view = this.$( '.ui-color-picker-view' );\n\t this.$value = this.$( '.ui-color-picker-value' );\n\t this.$header = this.$( '.ui-color-picker-header' );\n\t this._build();\n\t this.visible = false;\n\t this.value( this.options.value );\n\t this.$boxes = this.$( '.ui-color-picker-box' );\n\t var self = this;\n\t this.$boxes.on( 'click', function() {\n\t self.value( $( this ).css( 'background-color' ) );\n\t self.$header.trigger( 'click' );\n\t } );\n\t this.$header.on( 'click', function() {\n\t self.visible = !self.visible;\n\t if ( self.visible ) {\n\t self.$view.fadeIn( 'fast' );\n\t } else {\n\t self.$view.fadeOut( 'fast' );\n\t }\n\t } );\n\t },\n\t\n\t /** Get/set value */\n\t value : function ( new_val ) {\n\t if ( new_val !== undefined && new_val !== null ) {\n\t this.$value.css( 'background-color', new_val );\n\t this.$( '.ui-color-picker-box' ).empty();\n\t this.$( this._getValue() ).html( this._templateCheck() );\n\t this.options.onchange && this.options.onchange( new_val );\n\t }\n\t return this._getValue();\n\t },\n\t\n\t /** Get value from dom */\n\t _getValue: function() {\n\t var rgb = this.$value.css( 'background-color' );\n\t rgb = rgb.match(/^rgb\\((\\d+),\\s*(\\d+),\\s*(\\d+)\\)$/);\n\t if ( rgb ) {\n\t function hex( x ) {\n\t return ( '0' + parseInt( x ).toString( 16 ) ).slice( -2 );\n\t }\n\t return '#' + hex( rgb[ 1] ) + hex( rgb[ 2 ] ) + hex( rgb[ 3 ] );\n\t } else {\n\t return null;\n\t }\n\t },\n\t\n\t /** Build color panel */\n\t _build: function() {\n\t var $content = this._content({\n\t label : 'Theme Colors',\n\t colors : this.colors.base,\n\t padding : 10\n\t });\n\t for ( var i in this.colors.theme ) {\n\t var line_def = {};\n\t if ( i == 0 ) {\n\t line_def[ 'bottom' ] = true;\n\t } else {\n\t if ( i != this.colors.theme.length - 1 ) {\n\t line_def[ 'top' ] = true;\n\t line_def[ 'bottom' ] = true;\n\t } else {\n\t line_def[ 'top' ] = true;\n\t line_def[ 'padding' ] = 5;\n\t }\n\t }\n\t line_def[ 'colors' ] = this.colors.theme[ i ];\n\t this._content( line_def );\n\t }\n\t this._content({\n\t label : 'Standard Colors',\n\t colors : this.colors.standard,\n\t padding : 5\n\t });\n\t },\n\t\n\t /** Create content */\n\t _content: function( options ) {\n\t var label = options.label;\n\t var colors = options.colors;\n\t var padding = options.padding;\n\t var top = options.top;\n\t var bottom = options.bottom;\n\t var $content = $( this._templateContent() );\n\t var $label = $content.find( '.label' );\n\t if ( options.label ) {\n\t $label.html( options.label );\n\t } else {\n\t $label.hide();\n\t }\n\t var $line = $content.find( '.line' );\n\t this.$panel.append( $content );\n\t for ( var i in colors ) {\n\t var $box = $( this._templateBox( colors[ i ] ) );\n\t if ( top ) {\n\t $box.css( 'border-top', 'none' );\n\t $box.css( 'border-top-left-radius', '0px' );\n\t $box.css( 'border-top-right-radius', '0px' );\n\t }\n\t if ( bottom ) {\n\t $box.css( 'border-bottom', 'none' );\n\t $box.css( 'border-bottom-left-radius', '0px' );\n\t $box.css( 'border-bottom-right-radius', '0px' );\n\t }\n\t $line.append( $box );\n\t }\n\t if (padding) {\n\t $line.css( 'padding-bottom', padding );\n\t }\n\t return $content;\n\t },\n\t\n\t /** Check icon */\n\t _templateCheck: function() {\n\t return '
                        ';\n\t },\n\t\n\t /** Content template */\n\t _templateContent: function() {\n\t return '
                        ' +\n\t '
                        ' +\n\t '
                        ' +\n\t '
                        ';\n\t },\n\t\n\t /** Box template */\n\t _templateBox: function( color ) {\n\t return '
                        ';\n\t },\n\t\n\t /** Main template */\n\t _template: function() {\n\t return '
                        ' +\n\t '
                        ' +\n\t '
                        ' +\n\t '
                        Select a color
                        ' +\n\t '
                        ' +\n\t '
                        ' +\n\t '
                        ' +\n\t '
                        '\n\t '
                        ';\n\t }\n\t });\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(1)))\n\n/***/ },\n/* 47 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function($, _) {/** This class creates/wraps a drill down element. */\n\t!(__WEBPACK_AMD_DEFINE_ARRAY__ = [ __webpack_require__(4), __webpack_require__(20) ], __WEBPACK_AMD_DEFINE_RESULT__ = function( Utils, Options ) {\n\t\n\tvar View = Options.BaseIcons.extend({\n\t initialize: function( options ) {\n\t options.type = options.display || 'checkbox';\n\t options.multiple = ( options.type == 'checkbox' );\n\t Options.BaseIcons.prototype.initialize.call( this, options );\n\t },\n\t\n\t /** Set states for selected values */\n\t _setValue: function ( new_value ) {\n\t Options.BaseIcons.prototype._setValue.call( this, new_value );\n\t if ( new_value !== undefined && new_value !== null && this.header_index ) {\n\t var self = this;\n\t var values = $.isArray( new_value ) ? new_value : [ new_value ];\n\t _.each( values, function( v ) {\n\t var list = self.header_index[ v ];\n\t _.each( list, function( element ) {\n\t self._setState( element, true );\n\t });\n\t });\n\t }\n\t },\n\t\n\t /** Expand/collapse a sub group */\n\t _setState: function ( header_id, is_expanded ) {\n\t var $button = this.$( '.button-' + header_id );\n\t var $subgroup = this.$( '.subgroup-' + header_id );\n\t $button.data( 'is_expanded', is_expanded );\n\t if ( is_expanded ) {\n\t $subgroup.show();\n\t $button.removeClass( 'fa-plus-square' ).addClass( 'fa-minus-square' );\n\t } else {\n\t $subgroup.hide();\n\t $button.removeClass( 'fa-minus-square' ).addClass( 'fa-plus-square' );\n\t }\n\t },\n\t\n\t /** Template to create options tree */\n\t _templateOptions: function() {\n\t var self = this;\n\t this.header_index = {};\n\t\n\t // attach event handler\n\t function attach( $el, header_id ) {\n\t var $button = $el.find( '.button-' + header_id );\n\t $button.on( 'click', function() {\n\t self._setState( header_id, !$button.data( 'is_expanded' ) );\n\t });\n\t }\n\t\n\t // recursive function which iterates through options\n\t function iterate ( $tmpl, options, header ) {\n\t header = header || [];\n\t for ( i in options ) {\n\t var level = options[ i ];\n\t var has_options = level.options && level.options.length > 0;\n\t var new_header = header.slice( 0 );\n\t self.header_index[ level.value ] = new_header.slice( 0 );\n\t var $group = $( '
                        ' );\n\t if ( has_options ) {\n\t var header_id = Utils.uid();\n\t var $button = $( '' ).addClass( 'button-' + header_id ).addClass( 'ui-drilldown-button fa fa-plus-square' );\n\t var $subgroup = $( '
                        ' ).addClass( 'subgroup-' + header_id ).addClass( 'ui-drilldown-subgroup' );\n\t $group.append( $( '
                        ' )\n\t .append( $button )\n\t .append( self._templateOption( { label: level.name, value: level.value } ) ) );\n\t new_header.push( header_id );\n\t iterate ( $subgroup, level.options, new_header );\n\t $group.append( $subgroup );\n\t attach( $group, header_id );\n\t } else {\n\t $group.append( self._templateOption( { label: level.name, value: level.value } ) );\n\t }\n\t $tmpl.append( $group );\n\t }\n\t }\n\t\n\t // iterate through options and create dom\n\t var $tmpl = $( '
                        ' );\n\t iterate( $tmpl, this.model.get( 'data' ) );\n\t return $tmpl;\n\t },\n\t\n\t /** Template for drill down view */\n\t _template: function() {\n\t return $( '
                        ' ).addClass( 'ui-options-list drilldown-container' ).attr( 'id', this.model.id );\n\t }\n\t});\n\t\n\treturn {\n\t View: View\n\t}\n\t\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(1), __webpack_require__(2)))\n\n/***/ },\n/* 48 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, $, _) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [ __webpack_require__(4), __webpack_require__(7), __webpack_require__(21) ], __WEBPACK_AMD_DEFINE_RESULT__ = function( Utils, Ui, Select ) {\n\t\n\t/** Batch mode variations */\n\tvar Batch = { DISABLED: 'disabled', ENABLED: 'enabled', LINKED: 'linked' };\n\t\n\t/** List of available content selectors options */\n\tvar Configurations = {\n\t data: [\n\t { src: 'hda', icon: 'fa-file-o', tooltip: 'Single dataset', multiple: false, batch: Batch.DISABLED },\n\t { src: 'hda', icon: 'fa-files-o', tooltip: 'Multiple datasets', multiple: true, batch: Batch.LINKED },\n\t { src: 'hdca', icon: 'fa-folder-o', tooltip: 'Dataset collection', multiple: false, batch: Batch.LINKED } ],\n\t data_multiple: [\n\t { src: 'hda', icon: 'fa-files-o', tooltip: 'Multiple datasets', multiple: true, batch: Batch.DISABLED },\n\t { src: 'hdca', icon: 'fa-folder-o', tooltip: 'Dataset collection', multiple: false, batch: Batch.DISABLED } ],\n\t data_collection: [\n\t { src: 'hdca', icon: 'fa-folder-o', tooltip: 'Dataset collection', multiple: false, batch: Batch.DISABLED } ],\n\t workflow_data: [\n\t { src: 'hda', icon: 'fa-file-o', tooltip: 'Single dataset', multiple: false, batch: Batch.DISABLED } ],\n\t workflow_data_multiple: [\n\t { src: 'hda', icon: 'fa-files-o', tooltip: 'Multiple datasets', multiple: true, batch: Batch.DISABLED } ],\n\t workflow_data_collection: [\n\t { src: 'hdca', icon: 'fa-folder-o', tooltip: 'Dataset collection', multiple: false, batch: Batch.DISABLED } ],\n\t module_data: [\n\t { src: 'hda', icon: 'fa-file-o', tooltip: 'Single dataset', multiple: false, batch: Batch.DISABLED },\n\t { src: 'hda', icon: 'fa-files-o', tooltip: 'Multiple datasets', multiple: true, batch: Batch.ENABLED } ],\n\t module_data_collection: [\n\t { src: 'hdca', icon: 'fa-folder-o', tooltip: 'Dataset collection', multiple: false, batch: Batch.DISABLED },\n\t { src: 'hdca', icon: 'fa-folder', tooltip: 'Multiple collections', multiple: true, batch: Batch.ENABLED } ]\n\t};\n\t\n\t/** View for hda and hdca content selector ui elements */\n\tvar View = Backbone.View.extend({\n\t initialize : function( options ) {\n\t var self = this;\n\t this.model = options && options.model || new Backbone.Model({\n\t src_labels : { 'hda' : 'dataset', 'hdca': 'dataset collection' },\n\t pagelimit : 100\n\t }).set( options );\n\t this.setElement( $( '
                        ' ).addClass( 'ui-select-content' ) );\n\t this.button_product = new Ui.RadioButton.View( {\n\t value : 'false',\n\t data : [ { icon: 'fa fa-chain', value: 'false',\n\t tooltip: 'Linked inputs will be run in matched order with other datasets e.g. use this for matching forward and reverse reads.' },\n\t { icon: 'fa fa-chain-broken', value: 'true',\n\t tooltip: 'Unlinked dataset inputs will be run against *all* other inputs.' } ] } );\n\t var $batch_div = $( '
                        ' ).addClass( 'ui-form-info' )\n\t .append( $( '' ).addClass( 'fa fa-sitemap' ) )\n\t .append( $( '' ).html( 'This is a batch mode input field. Separate jobs will be triggered for each dataset selection.' ) );\n\t this.$batch = {\n\t linked : $batch_div.clone(),\n\t enabled : $batch_div.clone().append( $( '
                        ' )\n\t .append( $( '
                        ' ).addClass( 'ui-form-title' ).html( 'Batch options:' ) )\n\t .append( this.button_product.$el ) )\n\t .append( $( '
                        ' ).css( 'clear', 'both' ) )\n\t };\n\t\n\t // track current history elements\n\t this.history = {};\n\t\n\t // add listeners\n\t this.listenTo( this.model, 'change:data', this._changeData, this );\n\t this.listenTo( this.model, 'change:wait', this._changeWait, this );\n\t this.listenTo( this.model, 'change:current', this._changeCurrent, this );\n\t this.listenTo( this.model, 'change:value', this._changeValue, this );\n\t this.listenTo( this.model, 'change:type change:optional change:multiple change:extensions', this._changeType, this );\n\t this.render();\n\t\n\t // add change event\n\t this.on( 'change', function() { options.onchange && options.onchange( self.value() ) } );\n\t },\n\t\n\t render: function() {\n\t this._changeType();\n\t this._changeValue();\n\t this._changeWait();\n\t },\n\t\n\t /** Indicate that select fields are being updated */\n\t wait: function() {\n\t this.model.set( 'wait', true );\n\t },\n\t\n\t /** Indicate that the options update has been completed */\n\t unwait: function() {\n\t this.model.set( 'wait', false );\n\t },\n\t\n\t /** Update data representing selectable options */\n\t update: function( options ) {\n\t this.model.set( 'data', options );\n\t },\n\t\n\t /** Return the currently selected dataset values */\n\t value: function ( new_value ) {\n\t new_value !== undefined && this.model.set( 'value', new_value );\n\t var current = this.model.get( 'current' );\n\t if ( this.config[ current ] ) {\n\t var id_list = this.fields[ current ].value();\n\t if (id_list !== null) {\n\t id_list = $.isArray( id_list ) ? id_list : [ id_list ];\n\t if ( id_list.length > 0 ) {\n\t var result = this._batch( { values: [] } );\n\t for ( var i in id_list ) {\n\t var details = this.history[ id_list[ i ] + '_' + this.config[ current ].src ];\n\t if ( details ) {\n\t result.values.push( details );\n\t } else {\n\t Galaxy.emit.debug( 'ui-select-content::value()', 'Requested details not found for \\'' + id_list[ i ] + '\\'.' );\n\t return null;\n\t }\n\t }\n\t result.values.sort( function( a, b ) { return a.hid - b.hid } );\n\t return result;\n\t }\n\t }\n\t } else {\n\t Galaxy.emit.debug( 'ui-select-content::value()', 'Invalid value/source \\'' + new_value + '\\'.' );\n\t }\n\t return null;\n\t },\n\t\n\t /** Change of current select field */\n\t _changeCurrent: function() {\n\t var self = this;\n\t _.each( this.fields, function( field, i ) {\n\t if ( self.model.get( 'current' ) == i ) {\n\t field.$el.show();\n\t _.each( self.$batch, function( $batchfield, batchmode ) {\n\t $batchfield[ self.config[ i ].batch == batchmode ? 'show' : 'hide' ]();\n\t });\n\t self.button_type.value( i );\n\t } else {\n\t field.$el.hide();\n\t }\n\t });\n\t },\n\t\n\t /** Change of type */\n\t _changeType: function() {\n\t var self = this;\n\t\n\t // identify selector type identifier i.e. [ flavor ]_[ type ]_[ multiple ]\n\t var config_id = ( this.model.get( 'flavor' ) ? this.model.get( 'flavor' ) + '_' : '' ) +\n\t String( this.model.get( 'type' ) ) + ( this.model.get( 'multiple' ) ? '_multiple' : '' );\n\t if ( Configurations[ config_id ] ) {\n\t this.config = Configurations[ config_id ];\n\t } else {\n\t this.config = Configurations[ 'data' ];\n\t Galaxy.emit.debug( 'ui-select-content::_changeType()', 'Invalid configuration/type id \\'' + config_id + '\\'.' );\n\t }\n\t\n\t // prepare extension component of error message\n\t var data = self.model.get( 'data' );\n\t var extensions = Utils.textify( this.model.get( 'extensions' ) );\n\t var src_labels = this.model.get( 'src_labels' );\n\t\n\t // build views\n\t this.fields = [];\n\t this.button_data = [];\n\t _.each( this.config, function( c, i ) {\n\t self.button_data.push({\n\t value : i,\n\t icon : c.icon,\n\t tooltip : c.tooltip\n\t });\n\t self.fields.push(\n\t new Select.View({\n\t optional : self.model.get( 'optional' ),\n\t multiple : c.multiple,\n\t searchable : !c.multiple || ( data && data[ c.src ] && data[ c.src ].length > self.model.get( 'pagelimit' ) ),\n\t selectall : false,\n\t error_text : 'No ' + ( extensions ? extensions + ' ' : '' ) + ( src_labels[ c.src ] || 'content' ) + ' available.',\n\t onchange : function() {\n\t self.trigger( 'change' );\n\t }\n\t })\n\t );\n\t });\n\t this.button_type = new Ui.RadioButton.View({\n\t value : this.model.get( 'current' ),\n\t data : this.button_data,\n\t onchange: function( value ) {\n\t self.model.set( 'current', value );\n\t self.trigger( 'change' );\n\t }\n\t });\n\t\n\t // append views\n\t this.$el.empty();\n\t var button_width = 0;\n\t if ( this.fields.length > 1 ) {\n\t this.$el.append( this.button_type.$el );\n\t button_width = Math.max( 0, this.fields.length * 36 ) + 'px';\n\t }\n\t _.each( this.fields, function( field ) {\n\t self.$el.append( field.$el.css( { 'margin-left': button_width } ) );\n\t });\n\t _.each( this.$batch, function( $batchfield, batchmode ) {\n\t self.$el.append( $batchfield.css( { 'margin-left': button_width } ) );\n\t });\n\t this.model.set( 'current', 0 );\n\t this._changeCurrent();\n\t this._changeData();\n\t },\n\t\n\t /** Change of wait flag */\n\t _changeWait: function() {\n\t var self = this;\n\t _.each( this.fields, function( field ) { field[ self.model.get( 'wait' ) ? 'wait' : 'unwait' ]() } );\n\t },\n\t\n\t /** Change of available options */\n\t _changeData: function() {\n\t var options = this.model.get( 'data' );\n\t var self = this;\n\t var select_options = {};\n\t _.each( options, function( items, src ) {\n\t select_options[ src ] = [];\n\t _.each( items, function( item ) {\n\t select_options[ src ].push({\n\t hid : item.hid,\n\t keep : item.keep,\n\t label: item.hid + ': ' + item.name,\n\t value: item.id\n\t });\n\t self.history[ item.id + '_' + src ] = item;\n\t });\n\t });\n\t _.each( this.config, function( c, i ) {\n\t select_options[ c.src ] && self.fields[ i ].add( select_options[ c.src ], function( a, b ) { return b.hid - a.hid } );\n\t });\n\t },\n\t\n\t /** Change of incoming value */\n\t _changeValue: function () {\n\t var new_value = this.model.get( 'value' );\n\t if ( new_value && new_value.values && new_value.values.length > 0 ) {\n\t // create list with content ids\n\t var list = [];\n\t _.each( new_value.values, function( value ) {\n\t list.push( value.id );\n\t });\n\t // sniff first suitable field type from config list\n\t var src = new_value.values[ 0 ].src;\n\t var multiple = new_value.values.length > 1;\n\t for( var i = 0; i < this.config.length; i++ ) {\n\t var field = this.fields[ i ];\n\t var c = this.config[ i ];\n\t if ( c.src == src && [ multiple, true ].indexOf( c.multiple ) !== -1 ) {\n\t this.model.set( 'current', i );\n\t field.value( list );\n\t break;\n\t }\n\t }\n\t } else {\n\t _.each( this.fields, function( field ) {\n\t field.value( null );\n\t });\n\t }\n\t },\n\t\n\t /** Assists in identifying the batch mode */\n\t _batch: function( result ) {\n\t result[ 'batch' ] = false;\n\t var current = this.model.get( 'current' );\n\t var config = this.config[ current ];\n\t if ( config.src == 'hdca' && !config.multiple ) {\n\t var hdca = this.history[ this.fields[ current ].value() + '_hdca' ];\n\t if ( hdca && hdca.map_over_type ) {\n\t result[ 'batch' ] = true;\n\t }\n\t }\n\t if ( config.batch == Batch.LINKED || config.batch == Batch.ENABLED ) {\n\t result[ 'batch' ] = true;\n\t if ( config.batch == Batch.ENABLED && this.button_product.value() === 'true' ) {\n\t result[ 'product' ] = true;\n\t }\n\t }\n\t return result;\n\t }\n\t});\n\t\n\treturn {\n\t View: View\n\t}\n\t\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(1), __webpack_require__(2)))\n\n/***/ },\n/* 49 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone) {// dependencies\n\t!(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(4), __webpack_require__(19)], __WEBPACK_AMD_DEFINE_RESULT__ = function(Utils, List) {\n\t\n\t/**\n\t * FTP file selector\n\t */\n\tvar View = Backbone.View.extend({\n\t // initialize\n\t initialize : function(options) {\n\t // link this\n\t var self = this;\n\t\n\t // create ui-list view to keep track of selected ftp files\n\t this.ftpfile_list = new List.View({\n\t name : 'file',\n\t optional : options.optional,\n\t multiple : options.multiple,\n\t onchange : function() {\n\t options.onchange && options.onchange(self.value());\n\t }\n\t });\n\t\n\t // create elements\n\t this.setElement(this.ftpfile_list.$el);\n\t\n\t // initial fetch of ftps\n\t Utils.get({\n\t url : Galaxy.root + 'api/remote_files',\n\t success : function(response) {\n\t var data = [];\n\t for (var i in response) {\n\t data.push({\n\t value : response[i]['path'],\n\t label : response[i]['path']\n\t });\n\t }\n\t self.ftpfile_list.update(data);\n\t }\n\t });\n\t },\n\t\n\t /** Return/Set currently selected ftp datasets */\n\t value: function(val) {\n\t return this.ftpfile_list.value(val);\n\t }\n\t});\n\t\n\treturn {\n\t View: View\n\t}\n\t\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3)))\n\n/***/ },\n/* 50 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone) {// dependencies\n\t!(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(4), __webpack_require__(7), __webpack_require__(52), __webpack_require__(19)], __WEBPACK_AMD_DEFINE_RESULT__ = function(Utils, Ui, Table, List) {\n\t\n\t// collection of libraries\n\tvar Libraries = Backbone.Collection.extend({\n\t url: Galaxy.root + 'api/libraries?deleted=false'\n\t});\n\t\n\t// collection of dataset\n\tvar LibraryDatasets = Backbone.Collection.extend({\n\t initialize: function() {\n\t var self = this;\n\t this.config = new Backbone.Model({ library_id: null });\n\t this.config.on('change', function() {\n\t self.fetch({ reset: true });\n\t });\n\t },\n\t url: function() {\n\t return Galaxy.root + 'api/libraries/' + this.config.get('library_id') + '/contents';\n\t }\n\t});\n\t\n\t// hda/hdca content selector ui element\n\tvar View = Backbone.View.extend({\n\t // initialize\n\t initialize : function(options) {\n\t // link this\n\t var self = this;\n\t\n\t // collections\n\t this.libraries = new Libraries();\n\t this.datasets = new LibraryDatasets();\n\t\n\t // link app and options\n\t this.options = options;\n\t\n\t // select field for the library\n\t // TODO: Remove this once the library API supports searching for library datasets\n\t this.library_select = new Ui.Select.View({\n\t onchange : function(value) {\n\t self.datasets.config.set('library_id', value);\n\t }\n\t });\n\t\n\t // create ui-list view to keep track of selected data libraries\n\t this.dataset_list = new List.View({\n\t name : 'dataset',\n\t optional : options.optional,\n\t multiple : options.multiple,\n\t onchange : function() {\n\t self.trigger('change');\n\t }\n\t });\n\t\n\t // add reset handler for fetched libraries\n\t this.libraries.on('reset', function() {\n\t var data = [];\n\t self.libraries.each(function(model) {\n\t data.push({\n\t value : model.id,\n\t label : model.get('name')\n\t });\n\t });\n\t self.library_select.update(data);\n\t });\n\t\n\t // add reset handler for fetched library datasets\n\t this.datasets.on('reset', function() {\n\t var data = [];\n\t var library_current = self.library_select.text();\n\t if (library_current !== null) {\n\t self.datasets.each(function(model) {\n\t if (model.get('type') === 'file') {\n\t data.push({\n\t value : model.id,\n\t label : model.get('name')\n\t });\n\t }\n\t });\n\t }\n\t self.dataset_list.update(data);\n\t });\n\t\n\t // add change event. fires on trigger\n\t this.on('change', function() {\n\t options.onchange && options.onchange(self.value());\n\t });\n\t\n\t // create elements\n\t this.setElement(this._template());\n\t this.$('.library-select').append(this.library_select.$el);\n\t this.$el.append(this.dataset_list.$el);\n\t\n\t // initial fetch of libraries\n\t this.libraries.fetch({\n\t reset: true,\n\t success: function() {\n\t self.library_select.trigger('change');\n\t if (self.options.value !== undefined) {\n\t self.value(self.options.value);\n\t }\n\t }\n\t });\n\t },\n\t\n\t /** Return/Set currently selected library datasets */\n\t value: function(val) {\n\t return this.dataset_list.value(val);\n\t },\n\t\n\t /** Template */\n\t _template: function() {\n\t return '
                        ' +\n\t '
                        ' +\n\t 'Select Library' +\n\t '' +\n\t '
                        ' +\n\t '
                        ';\n\t }\n\t});\n\t\n\treturn {\n\t View: View\n\t}\n\t\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3)))\n\n/***/ },\n/* 51 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, $) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [ __webpack_require__(4) ], __WEBPACK_AMD_DEFINE_RESULT__ = function( Utils ) {\n\tvar View = Backbone.View.extend({\n\t initialize : function( options ) {\n\t var self = this;\n\t this.options = Utils.merge( options, {\n\t id : Utils.uid(),\n\t min : null,\n\t max : null,\n\t step : null,\n\t precise : false,\n\t split : 10000\n\t } );\n\t\n\t // create new element\n\t this.setElement( this._template( this.options ) );\n\t\n\t // determine wether to use the slider\n\t this.useslider = this.options.max !== null && this.options.min !== null && this.options.max > this.options.min;\n\t\n\t // set default step size\n\t if ( this.options.step === null ) {\n\t this.options.step = 1.0;\n\t if ( this.options.precise && this.useslider ) {\n\t this.options.step = ( this.options.max - this.options.min ) / this.options.split;\n\t }\n\t }\n\t\n\t // create slider if min and max are defined properly\n\t if ( this.useslider ) {\n\t this.$slider = this.$( '#slider' );\n\t this.$slider.slider( this.options );\n\t this.$slider.on( 'slide', function ( event, ui ) {\n\t self.value( ui.value );\n\t });\n\t } else {\n\t this.$( '.ui-form-slider-text' ).css( 'width', '100%' );\n\t }\n\t\n\t // link text input field\n\t this.$text = this.$( '#text' );\n\t\n\t // set initial value\n\t this.options.value !== undefined && ( this.value( this.options.value ) );\n\t\n\t // add text field event\n\t var pressed = [];\n\t this.$text.on( 'change', function () {\n\t self.value( $( this ).val() );\n\t });\n\t this.$text.on( 'keyup', function( e ) {\n\t pressed[e.which] = false;\n\t self.options.onchange && self.options.onchange( $( this ).val() );\n\t });\n\t this.$text.on( 'keydown', function ( e ) {\n\t var v = e.which;\n\t pressed[ v ] = true;\n\t if ( self.options.is_workflow && pressed[ 16 ] && v == 52 ) {\n\t self.value( '$' )\n\t event.preventDefault();\n\t } else if (!( v == 8 || v == 9 || v == 13 || v == 37 || v == 39 || ( v >= 48 && v <= 57 && !pressed[ 16 ] ) || ( v >= 96 && v <= 105 )\n\t || ( ( v == 190 || v == 110 ) && $( this ).val().indexOf( '.' ) == -1 && self.options.precise )\n\t || ( ( v == 189 || v == 109 ) && $( this ).val().indexOf( '-' ) == -1 )\n\t || self._isParameter( $( this ).val() )\n\t || pressed[ 91 ] || pressed[ 17 ] ) ) {\n\t event.preventDefault();\n\t }\n\t });\n\t },\n\t\n\t /** Set and Return the current value\n\t */\n\t value : function ( new_val ) {\n\t if ( new_val !== undefined ) {\n\t if ( new_val !== null && new_val !== '' && !this._isParameter( new_val ) ) {\n\t isNaN( new_val ) && ( new_val = 0 );\n\t this.options.max !== null && ( new_val = Math.min( new_val, this.options.max ) );\n\t this.options.min !== null && ( new_val = Math.max( new_val, this.options.min ) );\n\t }\n\t this.$slider && this.$slider.slider( 'value', new_val );\n\t this.$text.val( new_val );\n\t this.options.onchange && this.options.onchange( new_val );\n\t }\n\t return this.$text.val();\n\t },\n\t\n\t /** Return true if the field contains a workflow parameter i.e. $('name')\n\t */\n\t _isParameter: function( value ) {\n\t return this.options.is_workflow && String( value ).substring( 0, 1 ) === '$';\n\t },\n\t\n\t /** Slider template\n\t */\n\t _template: function( options ) {\n\t return '
                        ' +\n\t '' +\n\t '
                        ' +\n\t '
                        ';\n\t }\n\t});\n\t\n\treturn {\n\t View : View\n\t};\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(1)))\n\n/***/ },\n/* 52 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, $) {// dependencies\n\t!(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(4)], __WEBPACK_AMD_DEFINE_RESULT__ = function(Utils) {\n\t\n\t/**\n\t * This class creates a ui table element.\n\t */\n\tvar View = Backbone.View.extend({\n\t // current row\n\t row: null,\n\t \n\t // count rows\n\t row_count: 0,\n\t \n\t // defaults options\n\t optionsDefault: {\n\t content : 'No content available.',\n\t onchange : null,\n\t ondblclick : null,\n\t onconfirm : null,\n\t cls : 'ui-table',\n\t cls_tr : ''\n\t },\n\t \n\t // events\n\t events : {\n\t 'click' : '_onclick',\n\t 'dblclick' : '_ondblclick'\n\t },\n\t \n\t // initialize\n\t initialize : function(options) {\n\t // configure options\n\t this.options = Utils.merge(options, this.optionsDefault);\n\t \n\t // create new element\n\t var $el = $(this._template(this.options));\n\t \n\t // link sub-elements\n\t this.$thead = $el.find('thead');\n\t this.$tbody = $el.find('tbody');\n\t this.$tmessage = $el.find('tmessage');\n\t \n\t // set element\n\t this.setElement($el);\n\t \n\t // initialize row\n\t this.row = this._row();\n\t },\n\t \n\t // add header cell\n\t addHeader: function($el) {\n\t var wrapper = $('
                        ' +\n\t '' +\n\t '' +\n\t '
                        ' +\n\t '' + options.content + '' +\n\t '
                        ';\n\t }\n\t});\n\t\n\treturn {\n\t View: View\n\t}\n\t\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(1)))\n\n/***/ },\n/* 53 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_RESULT__ = function() {\n\t var Model = Backbone.Model.extend({\n\t defaults: {\n\t extension : 'auto',\n\t genome : '?',\n\t url_paste : '',\n\t status : 'init',\n\t info : null,\n\t file_name : '',\n\t file_mode : '',\n\t file_size : 0,\n\t file_type : null,\n\t file_path : '',\n\t file_data : null,\n\t percentage : 0,\n\t space_to_tab : false,\n\t to_posix_lines : true,\n\t enabled : true\n\t },\n\t reset: function( attr ) {\n\t this.clear().set( this.defaults ).set( attr );\n\t }\n\t });\n\t var Collection = Backbone.Collection.extend( { model: Model } );\n\t return { Model: Model, Collection : Collection };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3)))\n\n/***/ },\n/* 54 */,\n/* 55 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, $) {/**\n\t * This class defines a queue to ensure that multiple deferred callbacks are executed sequentially.\n\t */\n\t!(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(4)], __WEBPACK_AMD_DEFINE_RESULT__ = function( Utils ) {\n\treturn Backbone.Model.extend({\n\t initialize: function(){\n\t this.active = {};\n\t this.last = null;\n\t },\n\t\n\t /** Adds a callback to the queue. Upon execution a deferred object is parsed to the callback i.e. callback( deferred ).\n\t * If the callback does not take any arguments, the deferred is resolved instantly.\n\t */\n\t execute: function( callback ) {\n\t var self = this;\n\t var id = Utils.uid();\n\t var has_deferred = callback.length > 0;\n\t\n\t // register process\n\t this.active[ id ] = true;\n\t\n\t // deferred process\n\t var process = $.Deferred();\n\t process.promise().always(function() {\n\t delete self.active[ id ];\n\t has_deferred && Galaxy.emit.debug( 'deferred::execute()', this.state().charAt(0).toUpperCase() + this.state().slice(1) + ' ' + id );\n\t });\n\t\n\t // deferred queue\n\t $.when( this.last ).always(function() {\n\t if ( self.active[ id ] ) {\n\t has_deferred && Galaxy.emit.debug( 'deferred::execute()', 'Running ' + id );\n\t callback( process );\n\t !has_deferred && process.resolve();\n\t } else {\n\t process.reject();\n\t }\n\t });\n\t this.last = process.promise();\n\t },\n\t\n\t /** Resets the promise queue. All currently queued but unexecuted callbacks/promises will be rejected.\n\t */\n\t reset: function() {\n\t Galaxy.emit.debug('deferred::execute()', 'Reset');\n\t for ( var i in this.active ) {\n\t this.active[ i ] = false;\n\t }\n\t },\n\t\n\t /** Returns true if all processes are done.\n\t */\n\t ready: function() {\n\t return $.isEmptyObject( this.active );\n\t }\n\t});\n\t\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(1)))\n\n/***/ },\n/* 56 */,\n/* 57 */,\n/* 58 */,\n/* 59 */,\n/* 60 */,\n/* 61 */,\n/* 62 */,\n/* 63 */,\n/* 64 */,\n/* 65 */,\n/* 66 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, _) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(6),\n\t __webpack_require__(5),\n\t __webpack_require__(15),\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( baseMVC, _l ){\n\t// =============================================================================\n\t/** A view on any model that has a 'annotation' attribute\n\t */\n\tvar AnnotationEditor = Backbone.View\n\t .extend( baseMVC.LoggableMixin )\n\t .extend( baseMVC.HiddenUntilActivatedViewMixin ).extend({\n\t\n\t tagName : 'div',\n\t className : 'annotation-display',\n\t\n\t /** Set up listeners, parse options */\n\t initialize : function( options ){\n\t options = options || {};\n\t this.tooltipConfig = options.tooltipConfig || { placement: 'bottom' };\n\t //console.debug( this, options );\n\t // only listen to the model only for changes to annotations\n\t this.listenTo( this.model, 'change:annotation', function(){\n\t this.render();\n\t });\n\t this.hiddenUntilActivated( options.$activator, options );\n\t },\n\t\n\t /** Build the DOM elements, call select to on the created input, and set up behaviors */\n\t render : function(){\n\t var view = this;\n\t this.$el.html( this._template() );\n\t\n\t //TODO: handle empties better\n\t this.$annotation().make_text_editable({\n\t use_textarea: true,\n\t on_finish: function( newAnnotation ){\n\t view.$annotation().text( newAnnotation );\n\t view.model.save({ annotation: newAnnotation }, { silent: true })\n\t .fail( function(){\n\t view.$annotation().text( view.model.previous( 'annotation' ) );\n\t });\n\t }\n\t });\n\t return this;\n\t },\n\t\n\t /** @returns {String} the html text used to build the view's DOM */\n\t _template : function(){\n\t var annotation = this.model.get( 'annotation' );\n\t return [\n\t //TODO: make prompt optional\n\t '',\n\t // set up initial tags by adding as CSV to input vals (necc. to init select2)\n\t '
                        ',\n\t _.escape( annotation ),\n\t '
                        '\n\t ].join( '' );\n\t },\n\t\n\t /** @returns {jQuery} the main element for this view */\n\t $annotation : function(){\n\t return this.$el.find( '.annotation' );\n\t },\n\t\n\t /** shut down event listeners and remove this view's DOM */\n\t remove : function(){\n\t this.$annotation.off();\n\t this.stopListening( this.model );\n\t Backbone.View.prototype.remove.call( this );\n\t },\n\t\n\t /** string rep */\n\t toString : function(){ return [ 'AnnotationEditor(', this.model + '', ')' ].join(''); }\n\t});\n\t// =============================================================================\n\treturn {\n\t AnnotationEditor : AnnotationEditor\n\t};\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(2)))\n\n/***/ },\n/* 67 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(jQuery) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(2),\n\t __webpack_require__(3),\n\t __webpack_require__(6),\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( _, Backbone, BASE_MVC ){\n\t'use strict';\n\t\n\t//=============================================================================\n\t/**\n\t * A Collection that can be limited/offset/re-ordered/filtered.\n\t * @type {Backbone.Collection}\n\t */\n\tvar ControlledFetchCollection = Backbone.Collection.extend({\n\t\n\t /** call setOrder on initialization to build the comparator based on options */\n\t initialize : function( models, options ){\n\t Backbone.Collection.prototype.initialize.call( this, models, options );\n\t this.setOrder( options.order || this.order, { silent: true });\n\t },\n\t\n\t /** set up to track order changes and re-sort when changed */\n\t _setUpListeners : function(){\n\t return this.on({\n\t 'changed-order' : this.sort\n\t });\n\t },\n\t\n\t /** override to provide order and offsets based on instance vars, set limit if passed,\n\t * and set allFetched/fire 'all-fetched' when xhr returns\n\t */\n\t fetch : function( options ){\n\t options = this._buildFetchOptions( options );\n\t // console.log( 'fetch options:', options );\n\t return Backbone.Collection.prototype.fetch.call( this, options );\n\t },\n\t\n\t /** build ajax data/parameters from options */\n\t _buildFetchOptions : function( options ){\n\t // note: we normally want options passed in to override the defaults built here\n\t // so most of these fns will generate defaults\n\t options = _.clone( options ) || {};\n\t var self = this;\n\t\n\t // jquery ajax option; allows multiple q/qv for filters (instead of 'q[]')\n\t options.traditional = true;\n\t\n\t // options.data\n\t // we keep limit, offset, etc. in options *as well as move it into data* because:\n\t // - it makes fetch calling convenient to add it to a single options map (instead of as mult. args)\n\t // - it allows the std. event handlers (for fetch, etc.) to have access\n\t // to the pagination options too\n\t // (i.e. this.on( 'sync', function( options ){ if( options.limit ){ ... } }))\n\t // however, when we send to xhr/jquery we copy them to data also so that they become API query params\n\t options.data = options.data || self._buildFetchData( options );\n\t // console.log( 'data:', options.data );\n\t\n\t // options.data.filters --> options.data.q, options.data.qv\n\t var filters = this._buildFetchFilters( options );\n\t // console.log( 'filters:', filters );\n\t if( !_.isEmpty( filters ) ){\n\t _.extend( options.data, this._fetchFiltersToAjaxData( filters ) );\n\t }\n\t // console.log( 'data:', options.data );\n\t return options;\n\t },\n\t\n\t /** Build the dictionary to send to fetch's XHR as data */\n\t _buildFetchData : function( options ){\n\t var defaults = {};\n\t if( this.order ){ defaults.order = this.order; }\n\t return _.defaults( _.pick( options, this._fetchParams ), defaults );\n\t },\n\t\n\t /** These attribute keys are valid params to fetch/API-index */\n\t _fetchParams : [\n\t /** model dependent string to control the order of models returned */\n\t 'order',\n\t /** limit the number of models returned from a fetch */\n\t 'limit',\n\t /** skip this number of models when fetching */\n\t 'offset',\n\t /** what series of attributes to return (model dependent) */\n\t 'view',\n\t /** individual keys to return for the models (see api/histories.index) */\n\t 'keys'\n\t ],\n\t\n\t /** add any needed filters here based on collection state */\n\t _buildFetchFilters : function( options ){\n\t // override\n\t return _.clone( options.filters || {} );\n\t },\n\t\n\t /** Convert dictionary filters to qqv style arrays */\n\t _fetchFiltersToAjaxData : function( filters ){\n\t // return as a map so ajax.data can extend from it\n\t var filterMap = {\n\t q : [],\n\t qv : []\n\t };\n\t _.each( filters, function( v, k ){\n\t // don't send if filter value is empty\n\t if( v === undefined || v === '' ){ return; }\n\t // json to python\n\t if( v === true ){ v = 'True'; }\n\t if( v === false ){ v = 'False'; }\n\t if( v === null ){ v = 'None'; }\n\t // map to k/v arrays (q/qv)\n\t filterMap.q.push( k );\n\t filterMap.qv.push( v );\n\t });\n\t return filterMap;\n\t },\n\t\n\t /** override to reset allFetched flag to false */\n\t reset : function( models, options ){\n\t this.allFetched = false;\n\t return Backbone.Collection.prototype.reset.call( this, models, options );\n\t },\n\t\n\t // ........................................................................ order\n\t order : null,\n\t\n\t /** @type {Object} map of collection available sorting orders containing comparator fns */\n\t comparators : {\n\t 'update_time' : BASE_MVC.buildComparator( 'update_time', { ascending: false }),\n\t 'update_time-asc' : BASE_MVC.buildComparator( 'update_time', { ascending: true }),\n\t 'create_time' : BASE_MVC.buildComparator( 'create_time', { ascending: false }),\n\t 'create_time-asc' : BASE_MVC.buildComparator( 'create_time', { ascending: true }),\n\t },\n\t\n\t /** set the order and comparator for this collection then sort with the new order\n\t * @event 'changed-order' passed the new order and the collection\n\t */\n\t setOrder : function( order, options ){\n\t options = options || {};\n\t var collection = this;\n\t var comparator = collection.comparators[ order ];\n\t if( _.isUndefined( comparator ) ){ throw new Error( 'unknown order: ' + order ); }\n\t // if( _.isUndefined( comparator ) ){ return; }\n\t if( comparator === collection.comparator ){ return; }\n\t\n\t var oldOrder = collection.order;\n\t collection.order = order;\n\t collection.comparator = comparator;\n\t\n\t if( !options.silent ){\n\t collection.trigger( 'changed-order', options );\n\t }\n\t return collection;\n\t },\n\t\n\t});\n\t\n\t\n\t//=============================================================================\n\t/**\n\t *\n\t */\n\tvar PaginatedCollection = ControlledFetchCollection.extend({\n\t\n\t /** @type {Number} limit used for each page's fetch */\n\t limitPerPage : 500,\n\t\n\t initialize : function( models, options ){\n\t ControlledFetchCollection.prototype.initialize.call( this, models, options );\n\t this.currentPage = options.currentPage || 0;\n\t },\n\t\n\t getTotalItemCount : function(){\n\t return this.length;\n\t },\n\t\n\t shouldPaginate : function(){\n\t return this.getTotalItemCount() >= this.limitPerPage;\n\t },\n\t\n\t getLastPage : function(){\n\t return Math.floor( this.getTotalItemCount() / this.limitPerPage );\n\t },\n\t\n\t getPageCount : function(){\n\t return this.getLastPage() + 1;\n\t },\n\t\n\t getPageLimitOffset : function( pageNum ){\n\t pageNum = this.constrainPageNum( pageNum );\n\t return {\n\t limit : this.limitPerPage,\n\t offset: pageNum * this.limitPerPage\n\t };\n\t },\n\t\n\t constrainPageNum : function( pageNum ){\n\t return Math.max( 0, Math.min( pageNum, this.getLastPage() ));\n\t },\n\t\n\t /** fetch the next page of data */\n\t fetchPage : function( pageNum, options ){\n\t var self = this;\n\t pageNum = self.constrainPageNum( pageNum );\n\t self.currentPage = pageNum;\n\t options = _.defaults( options || {}, self.getPageLimitOffset( pageNum ) );\n\t\n\t self.trigger( 'fetching-more' );\n\t return self.fetch( options )\n\t .always( function(){\n\t self.trigger( 'fetching-more-done' );\n\t });\n\t },\n\t\n\t fetchCurrentPage : function( options ){\n\t return this.fetchPage( this.currentPage, options );\n\t },\n\t\n\t fetchPrevPage : function( options ){\n\t return this.fetchPage( this.currentPage - 1, options );\n\t },\n\t\n\t fetchNextPage : function( options ){\n\t return this.fetchPage( this.currentPage + 1, options );\n\t },\n\t});\n\t\n\t\n\t//=============================================================================\n\t/**\n\t * A Collection that will load more elements without reseting.\n\t */\n\tvar InfinitelyScrollingCollection = ControlledFetchCollection.extend({\n\t\n\t /** @type {Number} limit used for the first fetch (or a reset) */\n\t limitOnFirstFetch : null,\n\t /** @type {Number} limit used for each subsequent fetch */\n\t limitPerFetch : 100,\n\t\n\t initialize : function( models, options ){\n\t ControlledFetchCollection.prototype.initialize.call( this, models, options );\n\t /** @type {Integer} number of contents to return from the first fetch */\n\t this.limitOnFirstFetch = options.limitOnFirstFetch || this.limitOnFirstFetch;\n\t /** @type {Integer} limit for every fetch after the first */\n\t this.limitPerFetch = options.limitPerFetch || this.limitPerFetch;\n\t /** @type {Boolean} are all contents fetched? */\n\t this.allFetched = false;\n\t /** @type {Integer} what was the offset of the last content returned */\n\t this.lastFetched = options.lastFetched || 0;\n\t },\n\t\n\t /** build ajax data/parameters from options */\n\t _buildFetchOptions : function( options ){\n\t // options (options for backbone.fetch and jquery.ajax generally)\n\t // backbone option; false here to make fetching an addititive process\n\t options.remove = options.remove || false;\n\t return ControlledFetchCollection.prototype._buildFetchOptions.call( this, options );\n\t },\n\t\n\t /** fetch the first 'page' of data */\n\t fetchFirst : function( options ){\n\t // console.log( 'ControlledFetchCollection.fetchFirst:', options );\n\t options = options? _.clone( options ) : {};\n\t this.allFetched = false;\n\t this.lastFetched = 0;\n\t return this.fetchMore( _.defaults( options, {\n\t reset : true,\n\t limit : this.limitOnFirstFetch,\n\t }));\n\t },\n\t\n\t /** fetch the next page of data */\n\t fetchMore : function( options ){\n\t // console.log( 'ControlledFetchCollection.fetchMore:', options );\n\t options = _.clone( options || {} );\n\t var collection = this;\n\t\n\t // console.log( 'fetchMore, options.reset:', options.reset );\n\t if( ( !options.reset && collection.allFetched ) ){\n\t return jQuery.when();\n\t }\n\t\n\t // TODO: this fails in the edge case where\n\t // the first fetch offset === limit (limit 4, offset 4, collection.length 4)\n\t options.offset = options.reset? 0 : ( options.offset || collection.lastFetched );\n\t var limit = options.limit = options.limit || collection.limitPerFetch || null;\n\t // console.log( 'fetchMore, limit:', limit, 'offset:', options.offset );\n\t\n\t collection.trigger( 'fetching-more' );\n\t return collection.fetch( options )\n\t .always( function(){\n\t collection.trigger( 'fetching-more-done' );\n\t })\n\t // maintain allFetched flag and trigger if all were fetched this time\n\t .done( function _postFetchMore( fetchedData ){\n\t var numFetched = _.isArray( fetchedData )? fetchedData.length : 0;\n\t collection.lastFetched += numFetched;\n\t // console.log( 'fetchMore, lastFetched:', collection.lastFetched );\n\t // anything less than a full page means we got all there is to get\n\t if( !limit || numFetched < limit ){\n\t collection.allFetched = true;\n\t collection.trigger( 'all-fetched', this );\n\t }\n\t }\n\t );\n\t },\n\t\n\t /** fetch all the collection */\n\t fetchAll : function( options ){\n\t // whitelist options to prevent allowing limit/offset/filters\n\t // (use vanilla fetch instead)\n\t options = options || {};\n\t var self = this;\n\t options = _.pick( options, 'silent' );\n\t options.filters = {};\n\t return self.fetch( options ).done( function( fetchData ){\n\t self.allFetched = true;\n\t self.trigger( 'all-fetched', self );\n\t });\n\t },\n\t});\n\t\n\t\n\t//==============================================================================\n\t return {\n\t ControlledFetchCollection : ControlledFetchCollection,\n\t PaginatedCollection : PaginatedCollection,\n\t InfinitelyScrollingCollection : InfinitelyScrollingCollection,\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(1)))\n\n/***/ },\n/* 68 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(_) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(76),\n\t __webpack_require__(30),\n\t __webpack_require__(29),\n\t __webpack_require__(6),\n\t __webpack_require__(5)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( LIST_VIEW, DC_MODEL, DC_LI, BASE_MVC, _l ){\n\t\n\t'use strict';\n\t\n\tvar logNamespace = 'collections';\n\t/* =============================================================================\n\tTODO:\n\t\n\t============================================================================= */\n\t/** @class non-editable, read-only View/Controller for a dataset collection.\n\t */\n\tvar _super = LIST_VIEW.ModelListPanel;\n\tvar CollectionView = _super.extend(\n\t/** @lends CollectionView.prototype */{\n\t //MODEL is either a DatasetCollection (or subclass) or a DatasetCollectionElement (list of pairs)\n\t _logNamespace : logNamespace,\n\t\n\t className : _super.prototype.className + ' dataset-collection-panel',\n\t\n\t /** sub view class used for datasets */\n\t DatasetDCEViewClass : DC_LI.DatasetDCEListItemView,\n\t /** sub view class used for nested collections */\n\t NestedDCDCEViewClass: DC_LI.NestedDCDCEListItemView,\n\t /** key of attribute in model to assign to this.collection */\n\t modelCollectionKey : 'elements',\n\t\n\t // ......................................................................... SET UP\n\t /** Set up the view, set up storage, bind listeners to HistoryContents events\n\t * @param {Object} attributes optional settings for the panel\n\t */\n\t initialize : function( attributes ){\n\t _super.prototype.initialize.call( this, attributes );\n\t this.linkTarget = attributes.linkTarget || '_blank';\n\t\n\t this.hasUser = attributes.hasUser;\n\t /** A stack of panels that currently cover or hide this panel */\n\t this.panelStack = [];\n\t /** The text of the link to go back to the panel containing this one */\n\t this.parentName = attributes.parentName;\n\t /** foldout or drilldown */\n\t this.foldoutStyle = attributes.foldoutStyle || 'foldout';\n\t },\n\t\n\t _queueNewRender : function( $newRender, speed ) {\n\t speed = ( speed === undefined )?( this.fxSpeed ):( speed );\n\t var panel = this;\n\t panel.log( '_queueNewRender:', $newRender, speed );\n\t\n\t // TODO: jquery@1.12 doesn't change display when the elem has display: flex\n\t // this causes display: block for those elems after the use of show/hide animations\n\t // animations are removed from this view for now until fixed\n\t panel._swapNewRender( $newRender );\n\t panel.trigger( 'rendered', panel );\n\t },\n\t\n\t // ------------------------------------------------------------------------ sub-views\n\t /** In this override, use model.getVisibleContents */\n\t _filterCollection : function(){\n\t //TODO: should *not* be model.getVisibleContents - visibility is not model related\n\t return this.model.getVisibleContents();\n\t },\n\t\n\t /** override to return proper view class based on element_type */\n\t _getItemViewClass : function( model ){\n\t //this.debug( this + '._getItemViewClass:', model );\n\t //TODO: subclasses use DCEViewClass - but are currently unused - decide\n\t switch( model.get( 'element_type' ) ){\n\t case 'hda':\n\t return this.DatasetDCEViewClass;\n\t case 'dataset_collection':\n\t return this.NestedDCDCEViewClass;\n\t }\n\t throw new TypeError( 'Unknown element type:', model.get( 'element_type' ) );\n\t },\n\t\n\t /** override to add link target and anon */\n\t _getItemViewOptions : function( model ){\n\t var options = _super.prototype._getItemViewOptions.call( this, model );\n\t return _.extend( options, {\n\t linkTarget : this.linkTarget,\n\t hasUser : this.hasUser,\n\t //TODO: could move to only nested: list:paired\n\t foldoutStyle : this.foldoutStyle\n\t });\n\t },\n\t\n\t // ------------------------------------------------------------------------ collection sub-views\n\t /** In this override, add/remove expanded/collapsed model ids to/from web storage */\n\t _setUpItemViewListeners : function( view ){\n\t var panel = this;\n\t _super.prototype._setUpItemViewListeners.call( panel, view );\n\t\n\t // use pub-sub to: handle drilldown expansion and collapse\n\t panel.listenTo( view, {\n\t 'expanded:drilldown': function( v, drilldown ){\n\t this._expandDrilldownPanel( drilldown );\n\t },\n\t 'collapsed:drilldown': function( v, drilldown ){\n\t this._collapseDrilldownPanel( drilldown );\n\t }\n\t });\n\t return this;\n\t },\n\t\n\t /** Handle drill down by hiding this panels list and controls and showing the sub-panel */\n\t _expandDrilldownPanel : function( drilldown ){\n\t this.panelStack.push( drilldown );\n\t // hide this panel's controls and list, set the name for back navigation, and attach to the $el\n\t this.$( '> .controls' ).add( this.$list() ).hide();\n\t drilldown.parentName = this.model.get( 'name' );\n\t this.$el.append( drilldown.render().$el );\n\t },\n\t\n\t /** Handle drilldown close by freeing the panel and re-rendering this panel */\n\t _collapseDrilldownPanel : function( drilldown ){\n\t this.panelStack.pop();\n\t this.render();\n\t },\n\t\n\t // ------------------------------------------------------------------------ panel events\n\t /** event map */\n\t events : {\n\t 'click .navigation .back' : 'close'\n\t },\n\t\n\t /** close/remove this collection panel */\n\t close : function( event ){\n\t this.remove();\n\t this.trigger( 'close' );\n\t },\n\t\n\t // ........................................................................ misc\n\t /** string rep */\n\t toString : function(){\n\t return 'CollectionView(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';\n\t }\n\t});\n\t\n\t\n\t//------------------------------------------------------------------------------ TEMPLATES\n\tCollectionView.prototype.templates = (function(){\n\t\n\t var controlsTemplate = BASE_MVC.wrapTemplate([\n\t '
                        ',\n\t '',\n\t\n\t '
                        ',\n\t '
                        <%- collection.name || collection.element_identifier %>
                        ',\n\t '
                        ',\n\t '<% if( collection.collection_type === \"list\" ){ %>',\n\t _l( 'a list of datasets' ),\n\t '<% } else if( collection.collection_type === \"paired\" ){ %>',\n\t _l( 'a pair of datasets' ),\n\t '<% } else if( collection.collection_type === \"list:paired\" ){ %>',\n\t _l( 'a list of paired datasets' ),\n\t '<% } else if( collection.collection_type === \"list:list\" ){ %>',\n\t _l( 'a list of dataset lists' ),\n\t '<% } %>',\n\t '
                        ',\n\t '
                        ',\n\t '
                        '\n\t ], 'collection' );\n\t\n\t return _.extend( _.clone( _super.prototype.templates ), {\n\t controls : controlsTemplate\n\t });\n\t}());\n\t\n\t\n\t\n\t// =============================================================================\n\t/** @class non-editable, read-only View/Controller for a dataset collection. */\n\tvar ListCollectionView = CollectionView.extend(\n\t/** @lends ListCollectionView.prototype */{\n\t\n\t //TODO: not strictly needed - due to switch in CollectionView._getContentClass\n\t /** sub view class used for datasets */\n\t DatasetDCEViewClass : DC_LI.DatasetDCEListItemView,\n\t\n\t // ........................................................................ misc\n\t /** string rep */\n\t toString : function(){\n\t return 'ListCollectionView(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';\n\t }\n\t});\n\t\n\t\n\t// =============================================================================\n\t/** @class non-editable, read-only View/Controller for a dataset collection. */\n\tvar PairCollectionView = ListCollectionView.extend(\n\t/** @lends PairCollectionView.prototype */{\n\t\n\t // ........................................................................ misc\n\t /** string rep */\n\t toString : function(){\n\t return 'PairCollectionView(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';\n\t }\n\t});\n\t\n\t\n\t// =============================================================================\n\t/** @class non-editable, read-only View/Controller for a dataset collection. */\n\tvar ListOfPairsCollectionView = CollectionView.extend(\n\t/** @lends ListOfPairsCollectionView.prototype */{\n\t\n\t //TODO: not strictly needed - due to switch in CollectionView._getContentClass\n\t /** sub view class used for nested collections */\n\t NestedDCDCEViewClass : DC_LI.NestedDCDCEListItemView.extend({\n\t foldoutPanelClass : PairCollectionView\n\t }),\n\t\n\t // ........................................................................ misc\n\t /** string rep */\n\t toString : function(){\n\t return 'ListOfPairsCollectionView(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';\n\t }\n\t});\n\t\n\t\n\t// =============================================================================\n\t/** @class non-editable, read-only View/Controller for a list of lists dataset collection. */\n\tvar ListOfListsCollectionView = CollectionView.extend({\n\t\n\t /** sub view class used for nested collections */\n\t NestedDCDCEViewClass : DC_LI.NestedDCDCEListItemView.extend({\n\t foldoutPanelClass : PairCollectionView\n\t }),\n\t\n\t /** string rep */\n\t toString : function(){\n\t return 'ListOfListsCollectionView(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';\n\t }\n\t});\n\t\n\t\n\t//==============================================================================\n\t return {\n\t CollectionView : CollectionView,\n\t ListCollectionView : ListCollectionView,\n\t PairCollectionView : PairCollectionView,\n\t ListOfPairsCollectionView : ListOfPairsCollectionView,\n\t ListOfListsCollectionView : ListOfListsCollectionView\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(2)))\n\n/***/ },\n/* 69 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(_, $) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(12),\n\t __webpack_require__(32),\n\t __webpack_require__(77),\n\t __webpack_require__(66),\n\t __webpack_require__(22),\n\t __webpack_require__(6),\n\t __webpack_require__(5)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( STATES, DATASET_LI, TAGS, ANNOTATIONS, faIconButton, BASE_MVC, _l ){\n\t\n\t'use strict';\n\t//==============================================================================\n\tvar _super = DATASET_LI.DatasetListItemView;\n\t/** @class Editing view for DatasetAssociation.\n\t */\n\tvar DatasetListItemEdit = _super.extend(\n\t/** @lends DatasetListItemEdit.prototype */{\n\t\n\t /** set up: options */\n\t initialize : function( attributes ){\n\t _super.prototype.initialize.call( this, attributes );\n\t this.hasUser = attributes.hasUser;\n\t\n\t /** allow user purge of dataset files? */\n\t this.purgeAllowed = attributes.purgeAllowed || false;\n\t\n\t //TODO: move to HiddenUntilActivatedViewMixin\n\t /** should the tags editor be shown or hidden initially? */\n\t this.tagsEditorShown = attributes.tagsEditorShown || false;\n\t /** should the tags editor be shown or hidden initially? */\n\t this.annotationEditorShown = attributes.annotationEditorShown || false;\n\t },\n\t\n\t // ......................................................................... titlebar actions\n\t /** In this override, add the other two primary actions: edit and delete */\n\t _renderPrimaryActions : function(){\n\t var actions = _super.prototype._renderPrimaryActions.call( this );\n\t if( this.model.get( 'state' ) === STATES.NOT_VIEWABLE ){\n\t return actions;\n\t }\n\t // render the display, edit attr and delete icon-buttons\n\t return _super.prototype._renderPrimaryActions.call( this ).concat([\n\t this._renderEditButton(),\n\t this._renderDeleteButton()\n\t ]);\n\t },\n\t\n\t //TODO: move titleButtons into state renderers, remove state checks in the buttons\n\t\n\t /** Render icon-button to edit the attributes (format, permissions, etc.) this dataset. */\n\t _renderEditButton : function(){\n\t // don't show edit while uploading, in-accessible\n\t // DO show if in error (ala previous history panel)\n\t if( ( this.model.get( 'state' ) === STATES.DISCARDED )\n\t || ( !this.model.get( 'accessible' ) ) ){\n\t return null;\n\t }\n\t\n\t var purged = this.model.get( 'purged' ),\n\t deleted = this.model.get( 'deleted' ),\n\t editBtnData = {\n\t title : _l( 'Edit attributes' ),\n\t href : this.model.urls.edit,\n\t target : this.linkTarget,\n\t faIcon : 'fa-pencil',\n\t classes : 'edit-btn'\n\t };\n\t\n\t // disable if purged or deleted and explain why in the tooltip\n\t if( deleted || purged ){\n\t editBtnData.disabled = true;\n\t if( purged ){\n\t editBtnData.title = _l( 'Cannot edit attributes of datasets removed from disk' );\n\t } else if( deleted ){\n\t editBtnData.title = _l( 'Undelete dataset to edit attributes' );\n\t }\n\t\n\t // disable if still uploading or new\n\t } else if( _.contains( [ STATES.UPLOAD, STATES.NEW ], this.model.get( 'state' ) ) ){\n\t editBtnData.disabled = true;\n\t editBtnData.title = _l( 'This dataset is not yet editable' );\n\t }\n\t return faIconButton( editBtnData );\n\t },\n\t\n\t /** Render icon-button to delete this hda. */\n\t _renderDeleteButton : function(){\n\t // don't show delete if...\n\t if( ( !this.model.get( 'accessible' ) ) ){\n\t return null;\n\t }\n\t\n\t var self = this,\n\t deletedAlready = this.model.isDeletedOrPurged();\n\t return faIconButton({\n\t title : !deletedAlready? _l( 'Delete' ) : _l( 'Dataset is already deleted' ),\n\t disabled : deletedAlready,\n\t faIcon : 'fa-times',\n\t classes : 'delete-btn',\n\t onclick : function() {\n\t // ...bler... tooltips being left behind in DOM (hover out never called on deletion)\n\t self.$el.find( '.icon-btn.delete-btn' ).trigger( 'mouseout' );\n\t self.model[ 'delete' ]();\n\t }\n\t });\n\t },\n\t\n\t // ......................................................................... details\n\t /** In this override, add tags and annotations controls, make the ? dbkey a link to editing page */\n\t _renderDetails : function(){\n\t //TODO: generalize to be allow different details for each state\n\t var $details = _super.prototype._renderDetails.call( this ),\n\t state = this.model.get( 'state' );\n\t\n\t if( !this.model.isDeletedOrPurged() && _.contains([ STATES.OK, STATES.FAILED_METADATA ], state ) ){\n\t this._renderTags( $details );\n\t this._renderAnnotation( $details );\n\t this._makeDbkeyEditLink( $details );\n\t }\n\t\n\t this._setUpBehaviors( $details );\n\t return $details;\n\t },\n\t\n\t /** Add less commonly used actions in the details section based on state */\n\t _renderSecondaryActions : function(){\n\t var actions = _super.prototype._renderSecondaryActions.call( this );\n\t switch( this.model.get( 'state' ) ){\n\t case STATES.UPLOAD:\n\t case STATES.NOT_VIEWABLE:\n\t return actions;\n\t case STATES.ERROR:\n\t // error button comes first\n\t actions.unshift( this._renderErrButton() );\n\t return actions.concat([ this._renderRerunButton() ]);\n\t case STATES.OK:\n\t case STATES.FAILED_METADATA:\n\t return actions.concat([ this._renderRerunButton(), this._renderVisualizationsButton() ]);\n\t }\n\t return actions.concat([ this._renderRerunButton() ]);\n\t },\n\t\n\t /** Render icon-button to report an error on this dataset to the galaxy admin. */\n\t _renderErrButton : function(){\n\t return faIconButton({\n\t title : _l( 'View or report this error' ),\n\t href : this.model.urls.report_error,\n\t classes : 'report-error-btn',\n\t target : this.linkTarget,\n\t faIcon : 'fa-bug'\n\t });\n\t },\n\t\n\t /** Render icon-button to re-run the job that created this dataset. */\n\t _renderRerunButton : function(){\n\t var creating_job = this.model.get( 'creating_job' );\n\t if( this.model.get( 'rerunnable' ) ){\n\t return faIconButton({\n\t title : _l( 'Run this job again' ),\n\t href : this.model.urls.rerun,\n\t classes : 'rerun-btn',\n\t target : this.linkTarget,\n\t faIcon : 'fa-refresh',\n\t onclick : function( ev ) {\n\t ev.preventDefault();\n\t // create webpack split point in order to load the tool form async\n\t // TODO: split not working (tool loads fine)\n\t !/* require */(/* empty */function() { var __WEBPACK_AMD_REQUIRE_ARRAY__ = [ __webpack_require__(18) ]; (function( ToolForm ){\n\t var form = new ToolForm.View({ 'job_id' : creating_job });\n\t form.deferred.execute( function(){\n\t Galaxy.app.display( form );\n\t });\n\t }.apply(null, __WEBPACK_AMD_REQUIRE_ARRAY__));}());\n\t }\n\t });\n\t }\n\t },\n\t\n\t /** Render an icon-button or popupmenu of links based on the applicable visualizations */\n\t _renderVisualizationsButton : function(){\n\t //TODO: someday - lazyload visualizations\n\t var visualizations = this.model.get( 'visualizations' );\n\t if( ( this.model.isDeletedOrPurged() )\n\t || ( !this.hasUser )\n\t || ( !this.model.hasData() )\n\t || ( _.isEmpty( visualizations ) ) ){\n\t return null;\n\t }\n\t if( !_.isObject( visualizations[0] ) ){\n\t this.warn( 'Visualizations have been switched off' );\n\t return null;\n\t }\n\t\n\t var $visualizations = $( this.templates.visualizations( visualizations, this ) );\n\t //HACK: need to re-write those directed at galaxy_main with linkTarget\n\t $visualizations.find( '[target=\"galaxy_main\"]').attr( 'target', this.linkTarget );\n\t // use addBack here to include the root $visualizations elem (for the case of 1 visualization)\n\t this._addScratchBookFn( $visualizations.find( '.visualization-link' ).addBack( '.visualization-link' ) );\n\t return $visualizations;\n\t },\n\t\n\t /** add scratchbook functionality to visualization links */\n\t _addScratchBookFn : function( $links ){\n\t var li = this;\n\t $links.click( function( ev ){\n\t if( Galaxy.frame && Galaxy.frame.active ){\n\t Galaxy.frame.add({\n\t title : 'Visualization',\n\t url : $( this ).attr( 'href' )\n\t });\n\t ev.preventDefault();\n\t ev.stopPropagation();\n\t }\n\t });\n\t },\n\t\n\t //TODO: if possible move these to readonly view - but display the owner's tags/annotation (no edit)\n\t /** Render the tags list/control */\n\t _renderTags : function( $where ){\n\t if( !this.hasUser ){ return; }\n\t var view = this;\n\t this.tagsEditor = new TAGS.TagsEditor({\n\t model : this.model,\n\t el : $where.find( '.tags-display' ),\n\t onshowFirstTime : function(){ this.render(); },\n\t // persist state on the hda view (and not the editor) since these are currently re-created each time\n\t onshow : function(){ view.tagsEditorShown = true; },\n\t onhide : function(){ view.tagsEditorShown = false; },\n\t $activator : faIconButton({\n\t title : _l( 'Edit dataset tags' ),\n\t classes : 'tag-btn',\n\t faIcon : 'fa-tags'\n\t }).appendTo( $where.find( '.actions .right' ) )\n\t });\n\t if( this.tagsEditorShown ){ this.tagsEditor.toggle( true ); }\n\t },\n\t\n\t /** Render the annotation display/control */\n\t _renderAnnotation : function( $where ){\n\t if( !this.hasUser ){ return; }\n\t var view = this;\n\t this.annotationEditor = new ANNOTATIONS.AnnotationEditor({\n\t model : this.model,\n\t el : $where.find( '.annotation-display' ),\n\t onshowFirstTime : function(){ this.render(); },\n\t // persist state on the hda view (and not the editor) since these are currently re-created each time\n\t onshow : function(){ view.annotationEditorShown = true; },\n\t onhide : function(){ view.annotationEditorShown = false; },\n\t $activator : faIconButton({\n\t title : _l( 'Edit dataset annotation' ),\n\t classes : 'annotate-btn',\n\t faIcon : 'fa-comment'\n\t }).appendTo( $where.find( '.actions .right' ) )\n\t });\n\t if( this.annotationEditorShown ){ this.annotationEditor.toggle( true ); }\n\t },\n\t\n\t /** If the format/dbkey/genome_build isn't set, make the display a link to the edit page */\n\t _makeDbkeyEditLink : function( $details ){\n\t // make the dbkey a link to editing\n\t if( this.model.get( 'metadata_dbkey' ) === '?'\n\t && !this.model.isDeletedOrPurged() ){\n\t var editableDbkey = $( '?' )\n\t .attr( 'href', this.model.urls.edit )\n\t .attr( 'target', this.linkTarget );\n\t $details.find( '.dbkey .value' ).replaceWith( editableDbkey );\n\t }\n\t },\n\t\n\t // ......................................................................... events\n\t /** event map */\n\t events : _.extend( _.clone( _super.prototype.events ), {\n\t 'click .undelete-link' : '_clickUndeleteLink',\n\t 'click .purge-link' : '_clickPurgeLink',\n\t\n\t 'click .edit-btn' : function( ev ){ this.trigger( 'edit', this, ev ); },\n\t 'click .delete-btn' : function( ev ){ this.trigger( 'delete', this, ev ); },\n\t 'click .rerun-btn' : function( ev ){ this.trigger( 'rerun', this, ev ); },\n\t 'click .report-err-btn' : function( ev ){ this.trigger( 'report-err', this, ev ); },\n\t 'click .visualization-btn' : function( ev ){ this.trigger( 'visualize', this, ev ); },\n\t 'click .dbkey a' : function( ev ){ this.trigger( 'edit', this, ev ); }\n\t }),\n\t\n\t /** listener for item undelete (in the messages section) */\n\t _clickUndeleteLink : function( ev ){\n\t this.model.undelete();\n\t return false;\n\t },\n\t\n\t /** listener for item purge (in the messages section) */\n\t _clickPurgeLink : function( ev ){\n\t if( confirm( _l( 'This will permanently remove the data in your dataset. Are you sure?' ) ) ){\n\t this.model.purge();\n\t }\n\t return false;\n\t },\n\t\n\t // ......................................................................... misc\n\t /** string rep */\n\t toString : function(){\n\t var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n\t return 'HDAEditView(' + modelString + ')';\n\t }\n\t});\n\t\n\t\n\t// ............................................................................ TEMPLATES\n\t/** underscore templates */\n\tDatasetListItemEdit.prototype.templates = (function(){\n\t\n\t var warnings = _.extend( {}, _super.prototype.templates.warnings, {\n\t failed_metadata : BASE_MVC.wrapTemplate([\n\t // in this override, provide a link to the edit page\n\t '<% if( dataset.state === \"failed_metadata\" ){ %>',\n\t '',\n\t '<% } %>'\n\t ], 'dataset' ),\n\t\n\t deleted : BASE_MVC.wrapTemplate([\n\t // in this override, provide links to undelete or purge the dataset\n\t '<% if( dataset.deleted && !dataset.purged ){ %>',\n\t // deleted not purged\n\t '
                        ',\n\t _l( 'This dataset has been deleted' ),\n\t '
                        ', _l( 'Undelete it' ), '',\n\t '<% if( view.purgeAllowed ){ %>',\n\t '
                        ',\n\t _l( 'Permanently remove it from disk' ),\n\t '',\n\t '<% } %>',\n\t '
                        ',\n\t '<% } %>'\n\t ], 'dataset' )\n\t });\n\t\n\t var visualizationsTemplate = BASE_MVC.wrapTemplate([\n\t '<% if( visualizations.length === 1 ){ %>',\n\t '\"',\n\t ' target=\"<%- visualizations[0].target %>\" title=\"', _l( 'Visualize in' ),\n\t ' <%- visualizations[0].html %>\">',\n\t '',\n\t '',\n\t\n\t '<% } else { %>',\n\t '
                        ',\n\t '',\n\t '',\n\t '',\n\t '',\n\t '
                        ',\n\t '<% } %>'\n\t ], 'visualizations' );\n\t\n\t return _.extend( {}, _super.prototype.templates, {\n\t warnings : warnings,\n\t visualizations : visualizationsTemplate\n\t });\n\t}());\n\t\n\t\n\t//==============================================================================\n\t return {\n\t DatasetListItemEdit : DatasetListItemEdit\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(2), __webpack_require__(1)))\n\n/***/ },\n/* 70 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, _, jQuery) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(12),\n\t __webpack_require__(6),\n\t __webpack_require__(5)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( STATES, BASE_MVC, _l ){\n\t'use strict';\n\t\n\tvar logNamespace = 'dataset';\n\t//==============================================================================\n\tvar searchableMixin = BASE_MVC.SearchableModelMixin;\n\t/** @class base model for any DatasetAssociation (HDAs, LDDAs, DatasetCollectionDAs).\n\t * No knowledge of what type (HDA/LDDA/DCDA) should be needed here.\n\t * The DA's are made searchable (by attribute) by mixing in SearchableModelMixin.\n\t */\n\tvar DatasetAssociation = Backbone.Model\n\t .extend( BASE_MVC.LoggableMixin )\n\t .extend( BASE_MVC.mixin( searchableMixin, /** @lends DatasetAssociation.prototype */{\n\t _logNamespace : logNamespace,\n\t\n\t /** default attributes for a model */\n\t defaults : {\n\t state : STATES.NEW,\n\t deleted : false,\n\t purged : false,\n\t name : '(unnamed dataset)',\n\t accessible : true,\n\t // sniffed datatype (sam, tabular, bed, etc.)\n\t data_type : '',\n\t file_ext : '',\n\t file_size : 0,\n\t\n\t // array of associated file types (eg. [ 'bam_index', ... ])\n\t meta_files : [],\n\t\n\t misc_blurb : '',\n\t misc_info : '',\n\t\n\t tags : []\n\t // do NOT default on annotation, as this default is valid and will be passed on 'save'\n\t // which is incorrect behavior when the model is only partially fetched (annos are not passed in summary data)\n\t //annotation : ''\n\t },\n\t\n\t /** instance vars and listeners */\n\t initialize : function( attributes, options ){\n\t this.debug( this + '(Dataset).initialize', attributes, options );\n\t\n\t //!! this state is not in trans.app.model.Dataset.states - set it here -\n\t if( !this.get( 'accessible' ) ){\n\t this.set( 'state', STATES.NOT_VIEWABLE );\n\t }\n\t\n\t /** Datasets rely/use some web controllers - have the model generate those URLs on startup */\n\t this.urls = this._generateUrls();\n\t\n\t this._setUpListeners();\n\t },\n\t\n\t /** returns misc. web urls for rendering things like re-run, display, etc. */\n\t _generateUrls : function(){\n\t var id = this.get( 'id' );\n\t if( !id ){ return {}; }\n\t var urls = {\n\t 'purge' : 'datasets/' + id + '/purge_async',\n\t 'display' : 'datasets/' + id + '/display/?preview=True',\n\t 'edit' : 'datasets/' + id + '/edit',\n\t 'download' : 'datasets/' + id + '/display?to_ext=' + this.get( 'file_ext' ),\n\t 'report_error' : 'dataset/errors?id=' + id,\n\t 'rerun' : 'tool_runner/rerun?id=' + id,\n\t 'show_params' : 'datasets/' + id + '/show_params',\n\t 'visualization' : 'visualization',\n\t 'meta_download' : 'dataset/get_metadata_file?hda_id=' + id + '&metadata_name='\n\t };\n\t _.each( urls, function( value, key ){\n\t urls[ key ] = Galaxy.root + value;\n\t });\n\t this.urls = urls;\n\t return urls;\n\t },\n\t\n\t /** set up any event listeners\n\t * event: state:ready fired when this DA moves into/is already in a ready state\n\t */\n\t _setUpListeners : function(){\n\t // if the state has changed and the new state is a ready state, fire an event\n\t this.on( 'change:state', function( currModel, newState ){\n\t this.log( this + ' has changed state:', currModel, newState );\n\t if( this.inReadyState() ){\n\t this.trigger( 'state:ready', currModel, newState, this.previous( 'state' ) );\n\t }\n\t });\n\t // the download url (currently) relies on having a correct file extension\n\t this.on( 'change:id change:file_ext', function( currModel ){\n\t this._generateUrls();\n\t });\n\t },\n\t\n\t // ........................................................................ common queries\n\t /** override to add urls */\n\t toJSON : function(){\n\t var json = Backbone.Model.prototype.toJSON.call( this );\n\t //console.warn( 'returning json?' );\n\t //return json;\n\t return _.extend( json, {\n\t urls : this.urls\n\t });\n\t },\n\t\n\t /** Is this dataset deleted or purged? */\n\t isDeletedOrPurged : function(){\n\t return ( this.get( 'deleted' ) || this.get( 'purged' ) );\n\t },\n\t\n\t /** Is this dataset in a 'ready' state; where 'Ready' states are states where no\n\t * processing (for the ds) is left to do on the server.\n\t */\n\t inReadyState : function(){\n\t var ready = _.contains( STATES.READY_STATES, this.get( 'state' ) );\n\t return ( this.isDeletedOrPurged() || ready );\n\t },\n\t\n\t /** Does this model already contain detailed data (as opposed to just summary level data)? */\n\t hasDetails : function(){\n\t // if it's inaccessible assume it has everything it needs\n\t if( !this.get( 'accessible' ) ){ return true; }\n\t return this.has( 'annotation' );\n\t },\n\t\n\t /** Convenience function to match dataset.has_data. */\n\t hasData : function(){\n\t return ( this.get( 'file_size' ) > 0 );\n\t },\n\t\n\t // ........................................................................ ajax\n\t fetch : function( options ){\n\t var dataset = this;\n\t return Backbone.Model.prototype.fetch.call( this, options )\n\t .always( function(){\n\t dataset._generateUrls();\n\t });\n\t },\n\t\n\t /** override to use actual Dates objects for create/update times */\n\t parse : function( response, options ){\n\t var parsed = Backbone.Model.prototype.parse.call( this, response, options );\n\t if( parsed.create_time ){\n\t parsed.create_time = new Date( parsed.create_time );\n\t }\n\t if( parsed.update_time ){\n\t parsed.update_time = new Date( parsed.update_time );\n\t }\n\t return parsed;\n\t },\n\t\n\t /** override to wait by default */\n\t save : function( attrs, options ){\n\t options = options || {};\n\t options.wait = _.isUndefined( options.wait ) ? true : options.wait;\n\t return Backbone.Model.prototype.save.call( this, attrs, options );\n\t },\n\t\n\t //NOTE: subclasses of DA's will need to implement url and urlRoot in order to have these work properly\n\t /** save this dataset, _Mark_ing it as deleted (just a flag) */\n\t 'delete' : function( options ){\n\t if( this.get( 'deleted' ) ){ return jQuery.when(); }\n\t return this.save( { deleted: true }, options );\n\t },\n\t /** save this dataset, _Mark_ing it as undeleted */\n\t undelete : function( options ){\n\t if( !this.get( 'deleted' ) || this.get( 'purged' ) ){ return jQuery.when(); }\n\t return this.save( { deleted: false }, options );\n\t },\n\t\n\t /** remove the file behind this dataset from the filesystem (if permitted) */\n\t purge : function _purge( options ){\n\t //TODO: use, override model.destroy, HDA.delete({ purge: true })\n\t if( this.get( 'purged' ) ){ return jQuery.when(); }\n\t options = options || {};\n\t options.url = this.urls.purge;\n\t\n\t //TODO: ideally this would be a DELETE call to the api\n\t // using purge async for now\n\t var hda = this,\n\t xhr = jQuery.ajax( options );\n\t xhr.done( function( message, status, responseObj ){\n\t hda.set({ deleted: true, purged: true });\n\t });\n\t xhr.fail( function( xhr, status, message ){\n\t // Exception messages are hidden within error page including: '...not allowed in this Galaxy instance.'\n\t // unbury and re-add to xhr\n\t var error = _l( \"Unable to purge dataset\" );\n\t var messageBuriedInUnfortunatelyFormattedError = ( 'Removal of datasets by users '\n\t + 'is not allowed in this Galaxy instance' );\n\t if( xhr.responseJSON && xhr.responseJSON.error ){\n\t error = xhr.responseJSON.error;\n\t } else if( xhr.responseText.indexOf( messageBuriedInUnfortunatelyFormattedError ) !== -1 ){\n\t error = messageBuriedInUnfortunatelyFormattedError;\n\t }\n\t xhr.responseText = error;\n\t hda.trigger( 'error', hda, xhr, options, _l( error ), { error: error } );\n\t });\n\t return xhr;\n\t },\n\t\n\t // ........................................................................ searching\n\t /** what attributes of an HDA will be used in a text search */\n\t searchAttributes : [\n\t 'name', 'file_ext', 'genome_build', 'misc_blurb', 'misc_info', 'annotation', 'tags'\n\t ],\n\t\n\t /** our attr keys don't often match the labels we display to the user - so, when using\n\t * attribute specifiers ('name=\"bler\"') in a term, allow passing in aliases for the\n\t * following attr keys.\n\t */\n\t searchAliases : {\n\t title : 'name',\n\t format : 'file_ext',\n\t database : 'genome_build',\n\t blurb : 'misc_blurb',\n\t description : 'misc_blurb',\n\t info : 'misc_info',\n\t tag : 'tags'\n\t },\n\t\n\t // ........................................................................ misc\n\t /** String representation */\n\t toString : function(){\n\t var nameAndId = this.get( 'id' ) || '';\n\t if( this.get( 'name' ) ){\n\t nameAndId = '\"' + this.get( 'name' ) + '\",' + nameAndId;\n\t }\n\t return 'Dataset(' + nameAndId + ')';\n\t }\n\t}));\n\t\n\t\n\t//==============================================================================\n\t/** @class Backbone collection for dataset associations.\n\t */\n\tvar DatasetAssociationCollection = Backbone.Collection.extend( BASE_MVC.LoggableMixin ).extend(\n\t/** @lends HistoryContents.prototype */{\n\t _logNamespace : logNamespace,\n\t\n\t model : DatasetAssociation,\n\t\n\t /** root api url */\n\t urlRoot : Galaxy.root + 'api/datasets',\n\t\n\t /** url fn */\n\t url : function(){\n\t return this.urlRoot;\n\t },\n\t\n\t // ........................................................................ common queries\n\t /** Get the ids of every item in this collection\n\t * @returns array of encoded ids\n\t */\n\t ids : function(){\n\t return this.map( function( item ){ return item.get('id'); });\n\t },\n\t\n\t /** Get contents that are not ready\n\t * @returns array of content models\n\t */\n\t notReady : function(){\n\t return this.filter( function( content ){\n\t return !content.inReadyState();\n\t });\n\t },\n\t\n\t /** return true if any datasets don't have details */\n\t haveDetails : function(){\n\t return this.all( function( dataset ){ return dataset.hasDetails(); });\n\t },\n\t\n\t // ........................................................................ ajax\n\t /** using a queue, perform ajaxFn on each of the models in this collection */\n\t ajaxQueue : function( ajaxFn, options ){\n\t var deferred = jQuery.Deferred(),\n\t startingLength = this.length,\n\t responses = [];\n\t\n\t if( !startingLength ){\n\t deferred.resolve([]);\n\t return deferred;\n\t }\n\t\n\t // use reverse order (stylistic choice)\n\t var ajaxFns = this.chain().reverse().map( function( dataset, i ){\n\t return function(){\n\t var xhr = ajaxFn.call( dataset, options );\n\t // if successful, notify using the deferred to allow tracking progress\n\t xhr.done( function( response ){\n\t deferred.notify({ curr: i, total: startingLength, response: response, model: dataset });\n\t });\n\t // (regardless of previous error or success) if not last ajax call, shift and call the next\n\t // if last fn, resolve deferred\n\t xhr.always( function( response ){\n\t responses.push( response );\n\t if( ajaxFns.length ){\n\t ajaxFns.shift()();\n\t } else {\n\t deferred.resolve( responses );\n\t }\n\t });\n\t };\n\t }).value();\n\t // start the queue\n\t ajaxFns.shift()();\n\t\n\t return deferred;\n\t },\n\t\n\t // ........................................................................ sorting/filtering\n\t /** return a new collection of datasets whose attributes contain the substring matchesWhat */\n\t matches : function( matchesWhat ){\n\t return this.filter( function( dataset ){\n\t return dataset.matches( matchesWhat );\n\t });\n\t },\n\t\n\t /** String representation. */\n\t toString : function(){\n\t return ([ 'DatasetAssociationCollection(', this.length, ')' ].join( '' ));\n\t }\n\t});\n\t\n\t\n\t//==============================================================================\n\t return {\n\t DatasetAssociation : DatasetAssociation,\n\t DatasetAssociationCollection : DatasetAssociationCollection\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(2), __webpack_require__(1)))\n\n/***/ },\n/* 71 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(_) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(32),\n\t __webpack_require__(6),\n\t __webpack_require__(5)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( DATASET_LI, BASE_MVC, _l ){\n\t\n\t'use strict';\n\t\n\t//==============================================================================\n\tvar _super = DATASET_LI.DatasetListItemView;\n\t/** @class Read only view for HistoryDatasetAssociation.\n\t * Since there are no controls on the HDAView to hide the dataset,\n\t * the primary thing this class does (currently) is override templates\n\t * to render the HID.\n\t */\n\tvar HDAListItemView = _super.extend(\n\t/** @lends HDAListItemView.prototype */{\n\t\n\t className : _super.prototype.className + \" history-content\",\n\t\n\t initialize : function( attributes, options ){\n\t _super.prototype.initialize.call( this, attributes, options );\n\t },\n\t\n\t // ......................................................................... misc\n\t /** String representation */\n\t toString : function(){\n\t var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n\t return 'HDAListItemView(' + modelString + ')';\n\t }\n\t});\n\t\n\t// ............................................................................ TEMPLATES\n\t/** underscore templates */\n\tHDAListItemView.prototype.templates = (function(){\n\t\n\t var titleBarTemplate = BASE_MVC.wrapTemplate([\n\t // adding the hid display to the title\n\t '
                        ',\n\t '',\n\t '
                        ',\n\t //TODO: remove whitespace and use margin-right\n\t '<%- dataset.hid %> ',\n\t '<%- dataset.name %>',\n\t '
                        ',\n\t '
                        '\n\t ], 'dataset' );\n\t\n\t var warnings = _.extend( {}, _super.prototype.templates.warnings, {\n\t hidden : BASE_MVC.wrapTemplate([\n\t // add a warning when hidden\n\t '<% if( !dataset.visible ){ %>',\n\t '
                        ',\n\t _l( 'This dataset has been hidden' ),\n\t '
                        ',\n\t '<% } %>'\n\t ], 'dataset' )\n\t });\n\t\n\t return _.extend( {}, _super.prototype.templates, {\n\t titleBar : titleBarTemplate,\n\t warnings : warnings\n\t });\n\t}());\n\t\n\t\n\t\n\t//==============================================================================\n\t return {\n\t HDAListItemView : HDAListItemView\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(2)))\n\n/***/ },\n/* 72 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(_) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(70),\n\t __webpack_require__(74),\n\t __webpack_require__(6),\n\t __webpack_require__(5)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( DATASET, HISTORY_CONTENT, BASE_MVC, _l ){\n\t'use strict';\n\t\n\t//==============================================================================\n\tvar _super = DATASET.DatasetAssociation,\n\t hcontentMixin = HISTORY_CONTENT.HistoryContentMixin;\n\t/** @class (HDA) model for a Galaxy dataset contained in and related to a history.\n\t */\n\tvar HistoryDatasetAssociation = _super.extend( BASE_MVC.mixin( hcontentMixin,\n\t/** @lends HistoryDatasetAssociation.prototype */{\n\t\n\t /** default attributes for a model */\n\t defaults : _.extend( {}, _super.prototype.defaults, hcontentMixin.defaults, {\n\t history_content_type: 'dataset',\n\t model_class : 'HistoryDatasetAssociation'\n\t }),\n\t}));\n\t\n\t//==============================================================================\n\t return {\n\t HistoryDatasetAssociation : HistoryDatasetAssociation\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(2)))\n\n/***/ },\n/* 73 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(_) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(12),\n\t __webpack_require__(29),\n\t __webpack_require__(68),\n\t __webpack_require__(6),\n\t __webpack_require__(5)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( STATES, DC_LI, DC_VIEW, BASE_MVC, _l ){\n\t\n\t'use strict';\n\t\n\t//==============================================================================\n\tvar _super = DC_LI.DCListItemView;\n\t/** @class Read only view for HistoryDatasetCollectionAssociation (a dataset collection inside a history).\n\t */\n\tvar HDCAListItemView = _super.extend(\n\t/** @lends HDCAListItemView.prototype */{\n\t\n\t className : _super.prototype.className + \" history-content\",\n\t\n\t /** event listeners */\n\t _setUpListeners : function(){\n\t _super.prototype._setUpListeners.call( this );\n\t\n\t this.listenTo( this.model, {\n\t 'change:populated change:visible' : function( model, options ){ this.render(); },\n\t });\n\t },\n\t\n\t /** Override to provide the proper collections panels as the foldout */\n\t _getFoldoutPanelClass : function(){\n\t switch( this.model.get( 'collection_type' ) ){\n\t case 'list':\n\t return DC_VIEW.ListCollectionView;\n\t case 'paired':\n\t return DC_VIEW.PairCollectionView;\n\t case 'list:paired':\n\t return DC_VIEW.ListOfPairsCollectionView;\n\t case 'list:list':\n\t return DC_VIEW.ListOfListsCollectionView;\n\t }\n\t throw new TypeError( 'Uknown collection_type: ' + this.model.get( 'collection_type' ) );\n\t },\n\t\n\t /** In this override, add the state as a class for use with state-based CSS */\n\t _swapNewRender : function( $newRender ){\n\t _super.prototype._swapNewRender.call( this, $newRender );\n\t //TODO: model currently has no state\n\t var state = !this.model.get( 'populated' ) ? STATES.RUNNING : STATES.OK;\n\t //if( this.model.has( 'state' ) ){\n\t this.$el.addClass( 'state-' + state );\n\t //}\n\t return this.$el;\n\t },\n\t\n\t // ......................................................................... misc\n\t /** String representation */\n\t toString : function(){\n\t var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n\t return 'HDCAListItemView(' + modelString + ')';\n\t }\n\t});\n\t\n\t/** underscore templates */\n\tHDCAListItemView.prototype.templates = (function(){\n\t\n\t var warnings = _.extend( {}, _super.prototype.templates.warnings, {\n\t hidden : BASE_MVC.wrapTemplate([\n\t // add a warning when hidden\n\t '<% if( !collection.visible ){ %>',\n\t '
                        ',\n\t _l( 'This collection has been hidden' ),\n\t '
                        ',\n\t '<% } %>'\n\t ], 'collection' )\n\t });\n\t\n\t// could steal this from hda-base (or use mixed content)\n\t var titleBarTemplate = BASE_MVC.wrapTemplate([\n\t // adding the hid display to the title\n\t '
                        ',\n\t '',\n\t '
                        ',\n\t //TODO: remove whitespace and use margin-right\n\t '<%- collection.hid %> ',\n\t '<%- collection.name %>',\n\t '
                        ',\n\t '
                        ',\n\t '
                        '\n\t ], 'collection' );\n\t\n\t return _.extend( {}, _super.prototype.templates, {\n\t warnings : warnings,\n\t titleBar : titleBarTemplate\n\t });\n\t}());\n\t\n\t\n\t//==============================================================================\n\t return {\n\t HDCAListItemView : HDCAListItemView\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(2)))\n\n/***/ },\n/* 74 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(jQuery) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(12),\n\t __webpack_require__(6),\n\t __webpack_require__(5)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( STATES, BASE_MVC, _l ){\n\t'use strict';\n\t\n\t//==============================================================================\n\t/** @class Mixin for HistoryContents content (HDAs, HDCAs).\n\t */\n\tvar HistoryContentMixin = {\n\t\n\t /** default attributes for a model */\n\t defaults : {\n\t /** parent (containing) history */\n\t history_id : null,\n\t /** some content_type (HistoryContents can contain mixed model classes) */\n\t history_content_type: null,\n\t /** indicating when/what order the content was generated in the context of the history */\n\t hid : null,\n\t /** whether the user wants the content shown (visible) */\n\t visible : true\n\t },\n\t\n\t // ........................................................................ mixed content element\n\t // In order to be part of a MIXED bbone collection, we can't rely on the id\n\t // (which may collide btwn models of different classes)\n\t // Instead, use type_id which prefixes the history_content_type so the bbone collection can differentiate\n\t idAttribute : 'type_id',\n\t\n\t // ........................................................................ common queries\n\t /** the more common alias of visible */\n\t hidden : function(){\n\t return !this.get( 'visible' );\n\t },\n\t\n\t//TODO: remove\n\t /** based on includeDeleted, includeHidden (gen. from the container control),\n\t * would this ds show in the list of ds's?\n\t * @param {Boolean} includeDeleted are we showing deleted hdas?\n\t * @param {Boolean} includeHidden are we showing hidden hdas?\n\t */\n\t isVisible : function( includeDeleted, includeHidden ){\n\t var isVisible = true;\n\t if( ( !includeDeleted )\n\t && ( this.get( 'deleted' ) || this.get( 'purged' ) ) ){\n\t isVisible = false;\n\t }\n\t if( ( !includeHidden )\n\t && ( !this.get( 'visible' ) ) ){\n\t isVisible = false;\n\t }\n\t return isVisible;\n\t },\n\t\n\t // ........................................................................ ajax\n\t //TODO?: these are probably better done on the leaf classes\n\t /** history content goes through the 'api/histories' API */\n\t urlRoot: Galaxy.root + 'api/histories/',\n\t\n\t /** full url spec. for this content */\n\t url : function(){\n\t var url = this.urlRoot + this.get( 'history_id' ) + '/contents/'\n\t + this.get('history_content_type') + 's/' + this.get( 'id' );\n\t return url;\n\t },\n\t\n\t /** save this content as not visible */\n\t hide : function( options ){\n\t if( !this.get( 'visible' ) ){ return jQuery.when(); }\n\t return this.save( { visible: false }, options );\n\t },\n\t /** save this content as visible */\n\t unhide : function( options ){\n\t if( this.get( 'visible' ) ){ return jQuery.when(); }\n\t return this.save( { visible: true }, options );\n\t },\n\t\n\t // ........................................................................ misc\n\t toString : function(){\n\t return ([ this.get( 'type_id' ), this.get( 'hid' ), this.get( 'name' ) ].join(':'));\n\t }\n\t};\n\t\n\t\n\t//==============================================================================\n\t return {\n\t HistoryContentMixin : HistoryContentMixin\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(1)))\n\n/***/ },\n/* 75 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, jQuery, _, $) {\n\t!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(40),\n\t __webpack_require__(41),\n\t __webpack_require__(67),\n\t __webpack_require__(4),\n\t __webpack_require__(6),\n\t __webpack_require__(5)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( HISTORY_CONTENTS, HISTORY_PREFS, CONTROLLED_FETCH_COLLECTION, UTILS, BASE_MVC, _l ){\n\t'use strict';\n\t\n\t//==============================================================================\n\t/** @class Model for a Galaxy history resource - both a record of user\n\t * tool use and a collection of the datasets those tools produced.\n\t * @name History\n\t * @augments Backbone.Model\n\t */\n\tvar History = Backbone.Model\n\t .extend( BASE_MVC.LoggableMixin )\n\t .extend( BASE_MVC.mixin( BASE_MVC.SearchableModelMixin, /** @lends History.prototype */{\n\t _logNamespace : 'history',\n\t\n\t /** ms between fetches when checking running jobs/datasets for updates */\n\t UPDATE_DELAY : 4000,\n\t\n\t // values from api (may need more)\n\t defaults : {\n\t model_class : 'History',\n\t id : null,\n\t name : 'Unnamed History',\n\t state : 'new',\n\t\n\t deleted : false,\n\t contents_active : {},\n\t contents_states : {},\n\t },\n\t\n\t urlRoot: Galaxy.root + 'api/histories',\n\t\n\t contentsClass : HISTORY_CONTENTS.HistoryContents,\n\t\n\t /** What model fields to search with */\n\t searchAttributes : [\n\t 'name', 'annotation', 'tags'\n\t ],\n\t\n\t /** Adding title and singular tag */\n\t searchAliases : {\n\t title : 'name',\n\t tag : 'tags'\n\t },\n\t\n\t // ........................................................................ set up/tear down\n\t /** Set up the model\n\t * @param {Object} historyJSON model data for this History\n\t * @param {Object} options any extra settings including logger\n\t */\n\t initialize : function( historyJSON, options ){\n\t options = options || {};\n\t this.logger = options.logger || null;\n\t this.log( this + \".initialize:\", historyJSON, options );\n\t\n\t /** HistoryContents collection of the HDAs contained in this history. */\n\t this.contents = new this.contentsClass( [], {\n\t history : this,\n\t historyId : this.get( 'id' ),\n\t order : options.order,\n\t });\n\t\n\t this._setUpListeners();\n\t this._setUpCollectionListeners();\n\t\n\t /** cached timeout id for the dataset updater */\n\t this.updateTimeoutId = null;\n\t },\n\t\n\t /** set up any event listeners for this history including those to the contained HDAs\n\t * events: error:contents if an error occurred with the contents collection\n\t */\n\t _setUpListeners : function(){\n\t // if the model's id changes ('current' or null -> an actual id), update the contents history_id\n\t return this.on({\n\t 'error' : function( model, xhr, options, msg, details ){\n\t this.clearUpdateTimeout();\n\t },\n\t 'change:id' : function( model, newId ){\n\t if( this.contents ){\n\t this.contents.historyId = newId;\n\t }\n\t },\n\t });\n\t },\n\t\n\t /** event handlers for the contents submodels */\n\t _setUpCollectionListeners : function(){\n\t if( !this.contents ){ return this; }\n\t // bubble up errors\n\t return this.listenTo( this.contents, {\n\t 'error' : function(){\n\t this.trigger.apply( this, jQuery.makeArray( arguments ) );\n\t },\n\t });\n\t },\n\t\n\t // ........................................................................ derived attributes\n\t /** */\n\t contentsShown : function(){\n\t var contentsActive = this.get( 'contents_active' );\n\t var shown = contentsActive.active || 0;\n\t shown += this.contents.includeDeleted? contentsActive.deleted : 0;\n\t shown += this.contents.includeHidden? contentsActive.hidden : 0;\n\t return shown;\n\t },\n\t\n\t /** convert size in bytes to a more human readable version */\n\t nice_size : function(){\n\t var size = this.get( 'size' );\n\t return size? UTILS.bytesToString( size, true, 2 ) : _l( '(empty)' );\n\t },\n\t\n\t /** override to add nice_size */\n\t toJSON : function(){\n\t return _.extend( Backbone.Model.prototype.toJSON.call( this ), {\n\t nice_size : this.nice_size()\n\t });\n\t },\n\t\n\t /** override to allow getting nice_size */\n\t get : function( key ){\n\t if( key === 'nice_size' ){\n\t return this.nice_size();\n\t }\n\t return Backbone.Model.prototype.get.apply( this, arguments );\n\t },\n\t\n\t // ........................................................................ common queries\n\t /** T/F is this history owned by the current user (Galaxy.user)\n\t * Note: that this will return false for an anon user even if the history is theirs.\n\t */\n\t ownedByCurrUser : function(){\n\t // no currUser\n\t if( !Galaxy || !Galaxy.user ){\n\t return false;\n\t }\n\t // user is anon or history isn't owned\n\t if( Galaxy.user.isAnonymous() || Galaxy.user.id !== this.get( 'user_id' ) ){\n\t return false;\n\t }\n\t return true;\n\t },\n\t\n\t /** Return the number of running jobs assoc with this history (note: unknown === 0) */\n\t numOfUnfinishedJobs : function(){\n\t var unfinishedJobIds = this.get( 'non_ready_jobs' );\n\t return unfinishedJobIds? unfinishedJobIds.length : 0;\n\t },\n\t\n\t /** Return the number of running hda/hdcas in this history (note: unknown === 0) */\n\t numOfUnfinishedShownContents : function(){\n\t return this.contents.runningAndActive().length || 0;\n\t },\n\t\n\t // ........................................................................ updates\n\t _fetchContentRelatedAttributes : function(){\n\t var contentRelatedAttrs = [ 'size', 'non_ready_jobs', 'contents_active', 'hid_counter' ];\n\t return this.fetch({ data : $.param({ keys : contentRelatedAttrs.join( ',' ) }) });\n\t },\n\t\n\t /** check for any changes since the last time we updated (or fetch all if ) */\n\t refresh : function( options ){\n\t // console.log( this + '.refresh' );\n\t options = options || {};\n\t var self = this;\n\t\n\t // note if there was no previous update time, all summary contents will be fetched\n\t var lastUpdateTime = self.lastUpdateTime;\n\t // if we don't flip this, then a fully-fetched list will not be re-checked via fetch\n\t this.contents.allFetched = false;\n\t var fetchFn = self.contents.currentPage !== 0\n\t ? function(){ return self.contents.fetchPage( 0 ); }\n\t : function(){ return self.contents.fetchUpdated( lastUpdateTime ); };\n\t // note: if there was no previous update time, all summary contents will be fetched\n\t return fetchFn()\n\t .done( function( response, status, xhr ){\n\t var serverResponseDatetime;\n\t try {\n\t serverResponseDatetime = new Date( xhr.getResponseHeader( 'Date' ) );\n\t } catch( err ){}\n\t self.lastUpdateTime = serverResponseDatetime || new Date();\n\t self.checkForUpdates( options );\n\t });\n\t },\n\t\n\t /** continuously fetch updated contents every UPDATE_DELAY ms if this history's datasets or jobs are unfinished */\n\t checkForUpdates : function( options ){\n\t // console.log( this + '.checkForUpdates' );\n\t options = options || {};\n\t var delay = this.UPDATE_DELAY;\n\t var self = this;\n\t if( !self.id ){ return; }\n\t\n\t function _delayThenUpdate(){\n\t // prevent buildup of updater timeouts by clearing previous if any, then set new and cache id\n\t self.clearUpdateTimeout();\n\t self.updateTimeoutId = setTimeout( function(){\n\t self.refresh( options );\n\t }, delay );\n\t }\n\t\n\t // if there are still datasets in the non-ready state, recurse into this function with the new time\n\t var nonReadyContentCount = this.numOfUnfinishedShownContents();\n\t // console.log( 'nonReadyContentCount:', nonReadyContentCount );\n\t if( nonReadyContentCount > 0 ){\n\t _delayThenUpdate();\n\t\n\t } else {\n\t // no datasets are running, but currently runnning jobs may still produce new datasets\n\t // see if the history has any running jobs and continue to update if so\n\t // (also update the size for the user in either case)\n\t self._fetchContentRelatedAttributes()\n\t .done( function( historyData ){\n\t // console.log( 'non_ready_jobs:', historyData.non_ready_jobs );\n\t if( self.numOfUnfinishedJobs() > 0 ){\n\t _delayThenUpdate();\n\t\n\t } else {\n\t // otherwise, let listeners know that all updates have stopped\n\t self.trigger( 'ready' );\n\t }\n\t });\n\t }\n\t },\n\t\n\t /** clear the timeout and the cached timeout id */\n\t clearUpdateTimeout : function(){\n\t if( this.updateTimeoutId ){\n\t clearTimeout( this.updateTimeoutId );\n\t this.updateTimeoutId = null;\n\t }\n\t },\n\t\n\t // ........................................................................ ajax\n\t /** override to use actual Dates objects for create/update times */\n\t parse : function( response, options ){\n\t var parsed = Backbone.Model.prototype.parse.call( this, response, options );\n\t if( parsed.create_time ){\n\t parsed.create_time = new Date( parsed.create_time );\n\t }\n\t if( parsed.update_time ){\n\t parsed.update_time = new Date( parsed.update_time );\n\t }\n\t return parsed;\n\t },\n\t\n\t /** fetch this histories data (using options) then it's contents (using contentsOptions) */\n\t fetchWithContents : function( options, contentsOptions ){\n\t options = options || {};\n\t var self = this;\n\t\n\t // console.log( this + '.fetchWithContents' );\n\t // TODO: push down to a base class\n\t options.view = 'dev-detailed';\n\t\n\t // fetch history then use history data to fetch (paginated) contents\n\t return this.fetch( options ).then( function getContents( history ){\n\t self.contents.history = self;\n\t self.contents.setHistoryId( history.id );\n\t return self.fetchContents( contentsOptions );\n\t });\n\t },\n\t\n\t /** fetch this histories contents, adjusting options based on the stored history preferences */\n\t fetchContents : function( options ){\n\t options = options || {};\n\t var self = this;\n\t\n\t // we're updating, reset the update time\n\t self.lastUpdateTime = new Date();\n\t return self.contents.fetchCurrentPage( options );\n\t },\n\t\n\t /** save this history, _Mark_ing it as deleted (just a flag) */\n\t _delete : function( options ){\n\t if( this.get( 'deleted' ) ){ return jQuery.when(); }\n\t return this.save( { deleted: true }, options );\n\t },\n\t /** purge this history, _Mark_ing it as purged and removing all dataset data from the server */\n\t purge : function( options ){\n\t if( this.get( 'purged' ) ){ return jQuery.when(); }\n\t return this.save( { deleted: true, purged: true }, options );\n\t },\n\t /** save this history, _Mark_ing it as undeleted */\n\t undelete : function( options ){\n\t if( !this.get( 'deleted' ) ){ return jQuery.when(); }\n\t return this.save( { deleted: false }, options );\n\t },\n\t\n\t /** Make a copy of this history on the server\n\t * @param {Boolean} current if true, set the copy as the new current history (default: true)\n\t * @param {String} name name of new history (default: none - server sets to: Copy of )\n\t * @fires copied passed this history and the response JSON from the copy\n\t * @returns {xhr}\n\t */\n\t copy : function( current, name, allDatasets ){\n\t current = ( current !== undefined )?( current ):( true );\n\t if( !this.id ){\n\t throw new Error( 'You must set the history ID before copying it.' );\n\t }\n\t\n\t var postData = { history_id : this.id };\n\t if( current ){\n\t postData.current = true;\n\t }\n\t if( name ){\n\t postData.name = name;\n\t }\n\t if( !allDatasets ){\n\t postData.all_datasets = false;\n\t }\n\t postData.view = 'dev-detailed';\n\t\n\t var history = this;\n\t var copy = jQuery.post( this.urlRoot, postData );\n\t // if current - queue to setAsCurrent before firing 'copied'\n\t if( current ){\n\t return copy.then( function( response ){\n\t var newHistory = new History( response );\n\t return newHistory.setAsCurrent()\n\t .done( function(){\n\t history.trigger( 'copied', history, response );\n\t });\n\t });\n\t }\n\t return copy.done( function( response ){\n\t history.trigger( 'copied', history, response );\n\t });\n\t },\n\t\n\t setAsCurrent : function(){\n\t var history = this,\n\t xhr = jQuery.getJSON( Galaxy.root + 'history/set_as_current?id=' + this.id );\n\t\n\t xhr.done( function(){\n\t history.trigger( 'set-as-current', history );\n\t });\n\t return xhr;\n\t },\n\t\n\t // ........................................................................ misc\n\t toString : function(){\n\t return 'History(' + this.get( 'id' ) + ',' + this.get( 'name' ) + ')';\n\t }\n\t}));\n\t\n\t\n\t//==============================================================================\n\tvar _collectionSuper = CONTROLLED_FETCH_COLLECTION.InfinitelyScrollingCollection;\n\t/** @class A collection of histories (per user)\n\t * that maintains the current history as the first in the collection.\n\t * New or copied histories become the current history.\n\t */\n\tvar HistoryCollection = _collectionSuper.extend( BASE_MVC.LoggableMixin ).extend({\n\t _logNamespace : 'history',\n\t\n\t model : History,\n\t /** @type {String} initial order used by collection */\n\t order : 'update_time',\n\t /** @type {Number} limit used for the first fetch (or a reset) */\n\t limitOnFirstFetch : 10,\n\t /** @type {Number} limit used for each subsequent fetch */\n\t limitPerFetch : 10,\n\t\n\t initialize : function( models, options ){\n\t options = options || {};\n\t this.log( 'HistoryCollection.initialize', models, options );\n\t _collectionSuper.prototype.initialize.call( this, models, options );\n\t\n\t /** @type {boolean} should deleted histories be included */\n\t this.includeDeleted = options.includeDeleted || false;\n\t\n\t /** @type {String} encoded id of the history that's current */\n\t this.currentHistoryId = options.currentHistoryId;\n\t\n\t this.setUpListeners();\n\t // note: models are sent to reset *after* this fn ends; up to this point\n\t // the collection *is empty*\n\t },\n\t\n\t urlRoot : Galaxy.root + 'api/histories',\n\t url : function(){ return this.urlRoot; },\n\t\n\t /** set up reflexive event handlers */\n\t setUpListeners : function setUpListeners(){\n\t return this.on({\n\t // when a history is deleted, remove it from the collection (if optionally set to do so)\n\t 'change:deleted' : function( history ){\n\t // TODO: this becomes complicated when more filters are used\n\t this.debug( 'change:deleted', this.includeDeleted, history.get( 'deleted' ) );\n\t if( !this.includeDeleted && history.get( 'deleted' ) ){\n\t this.remove( history );\n\t }\n\t },\n\t // listen for a history copy, setting it to current\n\t 'copied' : function( original, newData ){\n\t this.setCurrent( new History( newData, [] ) );\n\t },\n\t // when a history is made current, track the id in the collection\n\t 'set-as-current' : function( history ){\n\t var oldCurrentId = this.currentHistoryId;\n\t this.trigger( 'no-longer-current', oldCurrentId );\n\t this.currentHistoryId = history.id;\n\t }\n\t });\n\t },\n\t\n\t /** override to change view */\n\t _buildFetchData : function( options ){\n\t return _.extend( _collectionSuper.prototype._buildFetchData.call( this, options ), {\n\t view : 'dev-detailed'\n\t });\n\t },\n\t\n\t /** override to filter out deleted and purged */\n\t _buildFetchFilters : function( options ){\n\t var superFilters = _collectionSuper.prototype._buildFetchFilters.call( this, options ) || {};\n\t var filters = {};\n\t if( !this.includeDeleted ){\n\t filters.deleted = false;\n\t filters.purged = false;\n\t } else {\n\t // force API to return both deleted and non\n\t //TODO: when the API is updated, remove this\n\t filters.deleted = null;\n\t }\n\t return _.defaults( superFilters, filters );\n\t },\n\t\n\t /** override to fetch current as well (as it may be outside the first 10, etc.) */\n\t fetchFirst : function( options ){\n\t var self = this;\n\t // TODO: batch?\n\t var xhr = $.when();\n\t if( this.currentHistoryId ){\n\t xhr = _collectionSuper.prototype.fetchFirst.call( self, {\n\t silent: true,\n\t limit : 1,\n\t filters: {\n\t // without these a deleted current history will return [] here and block the other xhr\n\t 'purged' : '',\n\t 'deleted' : '',\n\t 'encoded_id-in' : this.currentHistoryId,\n\t }\n\t });\n\t }\n\t return xhr.then( function(){\n\t options = options || {};\n\t options.offset = 0;\n\t return self.fetchMore( options );\n\t });\n\t },\n\t\n\t /** @type {Object} map of collection available sorting orders containing comparator fns */\n\t comparators : _.extend( _.clone( _collectionSuper.prototype.comparators ), {\n\t 'name' : BASE_MVC.buildComparator( 'name', { ascending: true }),\n\t 'name-dsc' : BASE_MVC.buildComparator( 'name', { ascending: false }),\n\t 'size' : BASE_MVC.buildComparator( 'size', { ascending: false }),\n\t 'size-asc' : BASE_MVC.buildComparator( 'size', { ascending: true }),\n\t }),\n\t\n\t /** override to always have the current history first */\n\t sort : function( options ){\n\t options = options || {};\n\t var silent = options.silent;\n\t var currentHistory = this.remove( this.get( this.currentHistoryId ) );\n\t _collectionSuper.prototype.sort.call( this, _.defaults({ silent: true }, options ) );\n\t this.unshift( currentHistory, { silent: true });\n\t if( !silent ){\n\t this.trigger( 'sort', this, options );\n\t }\n\t return this;\n\t },\n\t\n\t /** create a new history and by default set it to be the current history */\n\t create : function create( data, hdas, historyOptions, xhrOptions ){\n\t //TODO: .create is actually a collection function that's overridden here\n\t var collection = this,\n\t xhr = jQuery.getJSON( Galaxy.root + 'history/create_new_current' );\n\t return xhr.done( function( newData ){\n\t collection.setCurrent( new History( newData, [], historyOptions || {} ) );\n\t });\n\t },\n\t\n\t /** set the current history to the given history, placing it first in the collection.\n\t * Pass standard bbone options for use in unshift.\n\t * @triggers new-current passed history and this collection\n\t */\n\t setCurrent : function( history, options ){\n\t options = options || {};\n\t // new histories go in the front\n\t this.unshift( history, options );\n\t this.currentHistoryId = history.get( 'id' );\n\t if( !options.silent ){\n\t this.trigger( 'new-current', history, this );\n\t }\n\t return this;\n\t },\n\t\n\t toString: function toString(){\n\t return 'HistoryCollection(' + this.length + ',current:' + this.currentHistoryId + ')';\n\t }\n\t});\n\t\n\t\n\t//==============================================================================\n\treturn {\n\t History : History,\n\t HistoryCollection : HistoryCollection\n\t};}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(1), __webpack_require__(2), __webpack_require__(1)))\n\n/***/ },\n/* 76 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, _, $) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(42),\n\t __webpack_require__(127),\n\t __webpack_require__(6),\n\t __webpack_require__(5),\n\t __webpack_require__(85)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( LIST_ITEM, LoadingIndicator, BASE_MVC, _l ){\n\t\n\t'use strict';\n\t\n\tvar logNamespace = 'list';\n\t/* ============================================================================\n\tTODO:\n\t\n\t============================================================================ */\n\t/** @class View for a list/collection of models and the sub-views of those models.\n\t * Sub-views must (at least have the interface if not) inherit from ListItemView.\n\t * (For a list panel that also includes some 'container' model (History->HistoryContents)\n\t * use ModelWithListPanel)\n\t *\n\t * Allows for:\n\t * searching collection/sub-views\n\t * selecting/multi-selecting sub-views\n\t *\n\t * Currently used:\n\t * for dataset/dataset-choice\n\t * as superclass of ModelListPanel\n\t */\n\tvar ListPanel = Backbone.View.extend( BASE_MVC.LoggableMixin ).extend(/** @lends ListPanel.prototype */{\n\t _logNamespace : logNamespace,\n\t\n\t /** class to use for constructing the sub-views */\n\t viewClass : LIST_ITEM.ListItemView,\n\t /** class to used for constructing collection of sub-view models */\n\t collectionClass : Backbone.Collection,\n\t\n\t tagName : 'div',\n\t className : 'list-panel',\n\t\n\t /** (in ms) that jquery effects will use */\n\t fxSpeed : 'fast',\n\t\n\t /** string to display when the collection has no contents */\n\t emptyMsg : _l( 'This list is empty' ),\n\t /** displayed when no items match the search terms */\n\t noneFoundMsg : _l( 'No matching items found' ),\n\t /** string used for search placeholder */\n\t searchPlaceholder : _l( 'search' ),\n\t\n\t // ......................................................................... SET UP\n\t /** Set up the view, set up storage, bind listeners to HistoryContents events\n\t * @param {Object} attributes optional settings for the list\n\t */\n\t initialize : function( attributes, options ){\n\t attributes = attributes || {};\n\t // set the logger if requested\n\t if( attributes.logger ){\n\t this.logger = attributes.logger;\n\t }\n\t this.log( this + '.initialize:', attributes );\n\t\n\t // ---- instance vars\n\t /** how quickly should jquery fx run? */\n\t this.fxSpeed = _.has( attributes, 'fxSpeed' )?( attributes.fxSpeed ):( this.fxSpeed );\n\t\n\t /** filters for displaying subviews */\n\t this.filters = [];\n\t /** current search terms */\n\t this.searchFor = attributes.searchFor || '';\n\t\n\t /** loading indicator */\n\t // this.indicator = new LoadingIndicator( this.$el );\n\t\n\t /** currently showing selectors on items? */\n\t this.selecting = ( attributes.selecting !== undefined )? attributes.selecting : true;\n\t //this.selecting = false;\n\t\n\t /** cached selected item.model.ids to persist btwn renders */\n\t this.selected = attributes.selected || [];\n\t /** the last selected item.model.id */\n\t this.lastSelected = null;\n\t\n\t /** are sub-views draggable */\n\t this.dragItems = attributes.dragItems || false;\n\t\n\t /** list item view class (when passed models) */\n\t this.viewClass = attributes.viewClass || this.viewClass;\n\t\n\t /** list item views */\n\t this.views = [];\n\t /** list item models */\n\t this.collection = attributes.collection || this._createDefaultCollection();\n\t\n\t /** filter fns run over collection items to see if they should show in the list */\n\t this.filters = attributes.filters || [];\n\t\n\t /** override $scrollContainer fn via attributes - fn should return jq for elem to call scrollTo on */\n\t this.$scrollContainer = attributes.$scrollContainer || this.$scrollContainer;\n\t\n\t /** @type {String} generic title */\n\t this.title = attributes.title || '';\n\t /** @type {String} generic subtitle */\n\t this.subtitle = attributes.subtitle || '';\n\t\n\t this._setUpListeners();\n\t },\n\t\n\t // ------------------------------------------------------------------------ listeners\n\t /** create any event listeners for the list */\n\t _setUpListeners : function(){\n\t this.off();\n\t\n\t //TODO: move errorHandler down into list-view from history-view or\n\t // pass to global error handler (Galaxy)\n\t this.on({\n\t error: function( model, xhr, options, msg, details ){\n\t //this.errorHandler( model, xhr, options, msg, details );\n\t console.error( model, xhr, options, msg, details );\n\t },\n\t // show hide the loading indicator\n\t loading: function(){\n\t this._showLoadingIndicator( 'loading...', 40 );\n\t },\n\t 'loading-done': function(){\n\t this._hideLoadingIndicator( 40 );\n\t },\n\t });\n\t\n\t // throw the first render up as a diff namespace using once (for outside consumption)\n\t this.once( 'rendered', function(){\n\t this.trigger( 'rendered:initial', this );\n\t });\n\t\n\t this._setUpCollectionListeners();\n\t this._setUpViewListeners();\n\t return this;\n\t },\n\t\n\t /** create and return a collection for when none is initially passed */\n\t _createDefaultCollection : function(){\n\t // override\n\t return new this.collectionClass([]);\n\t },\n\t\n\t /** listening for collection events */\n\t _setUpCollectionListeners : function(){\n\t this.log( this + '._setUpCollectionListeners', this.collection );\n\t this.stopListening( this.collection );\n\t\n\t // bubble up error events\n\t this.listenTo( this.collection, {\n\t error : function( model, xhr, options, msg, details ){\n\t this.trigger( 'error', model, xhr, options, msg, details );\n\t },\n\t update : function( collection, options ){\n\t var changes = options.changes;\n\t // console.info( collection + ', update:', changes, '\\noptions:', options );\n\t // more than one: render everything\n\t if( options.renderAll || ( changes.added.length + changes.removed.length > 1 ) ){\n\t return this.renderItems();\n\t }\n\t // otherwise, let the single add/remove handlers do it\n\t if( changes.added.length === 1 ){\n\t return this.addItemView( _.first( changes.added ), collection, options );\n\t }\n\t if( changes.removed.length === 1 ){\n\t return this.removeItemView( _.first( changes.removed ), collection, options );\n\t }\n\t }\n\t });\n\t return this;\n\t },\n\t\n\t /** listening for sub-view events that bubble up with the 'view:' prefix */\n\t _setUpViewListeners : function(){\n\t this.log( this + '._setUpViewListeners' );\n\t\n\t // shift to select a range\n\t this.on({\n\t 'view:selected': function( view, ev ){\n\t if( ev && ev.shiftKey && this.lastSelected ){\n\t var lastSelectedView = this.viewFromModelId( this.lastSelected );\n\t if( lastSelectedView ){\n\t this.selectRange( view, lastSelectedView );\n\t }\n\t } else if( ev && ev.altKey && !this.selecting ){\n\t this.showSelectors();\n\t }\n\t this.selected.push( view.model.id );\n\t this.lastSelected = view.model.id;\n\t },\n\t\n\t 'view:de-selected': function( view, ev ){\n\t this.selected = _.without( this.selected, view.model.id );\n\t }\n\t });\n\t },\n\t\n\t // ------------------------------------------------------------------------ rendering\n\t /** Render this content, set up ui.\n\t * @param {Number or String} speed the speed of the render\n\t */\n\t render : function( speed ){\n\t this.log( this + '.render', speed );\n\t var $newRender = this._buildNewRender();\n\t this._setUpBehaviors( $newRender );\n\t this._queueNewRender( $newRender, speed );\n\t return this;\n\t },\n\t\n\t /** Build a temp div containing the new children for the view's $el. */\n\t _buildNewRender : function(){\n\t this.debug( this + '(ListPanel)._buildNewRender' );\n\t var $newRender = $( this.templates.el( {}, this ) );\n\t this._renderControls( $newRender );\n\t this._renderTitle( $newRender );\n\t this._renderSubtitle( $newRender );\n\t this._renderSearch( $newRender );\n\t this.renderItems( $newRender );\n\t return $newRender;\n\t },\n\t\n\t /** Build a temp div containing the new children for the view's $el. */\n\t _renderControls : function( $newRender ){\n\t this.debug( this + '(ListPanel)._renderControls' );\n\t var $controls = $( this.templates.controls( {}, this ) );\n\t $newRender.find( '.controls' ).replaceWith( $controls );\n\t return $controls;\n\t },\n\t\n\t /** return a jQuery object containing the title DOM */\n\t _renderTitle : function( $where ){\n\t //$where = $where || this.$el;\n\t //$where.find( '.title' ).replaceWith( ... )\n\t },\n\t\n\t /** return a jQuery object containing the subtitle DOM (if any) */\n\t _renderSubtitle : function( $where ){\n\t //$where = $where || this.$el;\n\t //$where.find( '.title' ).replaceWith( ... )\n\t },\n\t\n\t /** Fade out the old el, swap in the new contents, then fade in.\n\t * @param {Number or String} speed jq speed to use for rendering effects\n\t * @fires rendered when rendered\n\t */\n\t _queueNewRender : function( $newRender, speed ) {\n\t speed = ( speed === undefined )?( this.fxSpeed ):( speed );\n\t var panel = this;\n\t panel.log( '_queueNewRender:', $newRender, speed );\n\t\n\t $( panel ).queue( 'fx', [\n\t function( next ){\n\t panel.$el.fadeOut( speed, next );\n\t },\n\t function( next ){\n\t panel._swapNewRender( $newRender );\n\t next();\n\t },\n\t function( next ){\n\t panel.$el.fadeIn( speed, next );\n\t },\n\t function( next ){\n\t panel.trigger( 'rendered', panel );\n\t next();\n\t }\n\t ]);\n\t },\n\t\n\t /** empty out the current el, move the $newRender's children in */\n\t _swapNewRender : function( $newRender ){\n\t this.$el.empty().attr( 'class', this.className ).append( $newRender.children() );\n\t if( this.selecting ){ this.showSelectors( 0 ); }\n\t return this;\n\t },\n\t\n\t /** Set up any behaviors, handlers (ep. plugins) that need to be called when the entire view has been built but\n\t * not attached to the page yet.\n\t */\n\t _setUpBehaviors : function( $where ){\n\t $where = $where || this.$el;\n\t this.$controls( $where ).find('[title]').tooltip();\n\t // set up the pupup for actions available when multi selecting\n\t this._renderMultiselectActionMenu( $where );\n\t return this;\n\t },\n\t\n\t /** render a menu containing the actions available to sets of selected items */\n\t _renderMultiselectActionMenu : function( $where ){\n\t $where = $where || this.$el;\n\t var $menu = $where.find( '.list-action-menu' ),\n\t actions = this.multiselectActions();\n\t if( !actions.length ){\n\t return $menu.empty();\n\t }\n\t\n\t var $newMenu = $([\n\t '
                        ',\n\t '',\n\t '
                          ', '
                        ',\n\t '
                        '\n\t ].join(''));\n\t var $actions = actions.map( function( action ){\n\t var html = [ '
                      • ', action.html, '
                      • ' ].join( '' );\n\t return $( html ).click( function( ev ){\n\t ev.preventDefault();\n\t return action.func( ev );\n\t });\n\t });\n\t $newMenu.find( 'ul' ).append( $actions );\n\t $menu.replaceWith( $newMenu );\n\t return $newMenu;\n\t },\n\t\n\t /** return a list of plain objects used to render multiselect actions menu. Each object should have:\n\t * html: an html string used as the anchor contents\n\t * func: a function called when the anchor is clicked (passed the click event)\n\t */\n\t multiselectActions : function(){\n\t return [];\n\t },\n\t\n\t // ------------------------------------------------------------------------ sub-$element shortcuts\n\t /** the scroll container for this panel - can be $el, $el.parent(), or grandparent depending on context */\n\t $scrollContainer : function( $where ){\n\t // override or set via attributes.$scrollContainer\n\t return ( $where || this.$el ).parent().parent();\n\t },\n\t /** convenience selector for the section that displays the list controls */\n\t $controls : function( $where ){\n\t return ( $where || this.$el ).find( '> .controls' );\n\t },\n\t /** list-items: where the subviews are contained in the view's dom */\n\t $list : function( $where ){\n\t return ( $where || this.$el ).find( '> .list-items' );\n\t },\n\t /** container where list messages are attached */\n\t $messages : function( $where ){\n\t //TODO: controls isn't really correct here (only for ModelListPanel)\n\t return ( $where || this.$el ).find( '> .controls .messages' );\n\t },\n\t /** the message displayed when no views can be shown (no views, none matching search) */\n\t $emptyMessage : function( $where ){\n\t return ( $where || this.$el ).find( '> .empty-message' );\n\t },\n\t\n\t // ------------------------------------------------------------------------ hda sub-views\n\t /** render the subviews for the list's collection */\n\t renderItems : function( $whereTo ){\n\t $whereTo = $whereTo || this.$el;\n\t var panel = this;\n\t panel.log( this + '.renderItems', $whereTo );\n\t\n\t var $list = panel.$list( $whereTo );\n\t panel.freeViews();\n\t // console.log( 'views freed' );\n\t //TODO:? cache and re-use views?\n\t var shownModels = panel._filterCollection();\n\t // console.log( 'models filtered:', shownModels );\n\t\n\t panel.views = shownModels.map( function( itemModel ){\n\t var view = panel._createItemView( itemModel );\n\t return view;\n\t });\n\t\n\t $list.empty();\n\t // console.log( 'list emptied' );\n\t if( panel.views.length ){\n\t panel._attachItems( $whereTo );\n\t // console.log( 'items attached' );\n\t }\n\t panel._renderEmptyMessage( $whereTo ).toggle( !panel.views.length );\n\t panel.trigger( 'views:ready', panel.views );\n\t\n\t // console.log( '------------------------------------------- rendering items' );\n\t return panel.views;\n\t },\n\t\n\t /** Filter the collection to only those models that should be currently viewed */\n\t _filterCollection : function(){\n\t // override this\n\t var panel = this;\n\t return panel.collection.filter( _.bind( panel._filterItem, panel ) );\n\t },\n\t\n\t /** Should the model be viewable in the current state?\n\t * Checks against this.filters and this.searchFor\n\t */\n\t _filterItem : function( model ){\n\t // override this\n\t var panel = this;\n\t return ( _.every( panel.filters.map( function( fn ){ return fn.call( model ); }) ) )\n\t && ( !panel.searchFor || model.matchesAll( panel.searchFor ) );\n\t },\n\t\n\t /** Create a view for a model and set up it's listeners */\n\t _createItemView : function( model ){\n\t var ViewClass = this._getItemViewClass( model );\n\t var options = _.extend( this._getItemViewOptions( model ), {\n\t model : model\n\t });\n\t var view = new ViewClass( options );\n\t this._setUpItemViewListeners( view );\n\t return view;\n\t },\n\t\n\t /** Free a view for a model. Note: does not remove it from the DOM */\n\t _destroyItemView : function( view ){\n\t this.stopListening( view );\n\t this.views = _.without( this.views, view );\n\t },\n\t\n\t _destroyItemViews : function( view ){\n\t var self = this;\n\t self.views.forEach( function( v ){\n\t self.stopListening( v );\n\t });\n\t self.views = [];\n\t return self;\n\t },\n\t\n\t /** free any sub-views the list has */\n\t freeViews : function(){\n\t return this._destroyItemViews();\n\t },\n\t\n\t /** Get the bbone view class based on the model */\n\t _getItemViewClass : function( model ){\n\t // override this\n\t return this.viewClass;\n\t },\n\t\n\t /** Get the options passed to the new view based on the model */\n\t _getItemViewOptions : function( model ){\n\t // override this\n\t return {\n\t //logger : this.logger,\n\t fxSpeed : this.fxSpeed,\n\t expanded : false,\n\t selectable : this.selecting,\n\t selected : _.contains( this.selected, model.id ),\n\t draggable : this.dragItems\n\t };\n\t },\n\t\n\t /** Set up listeners for new models */\n\t _setUpItemViewListeners : function( view ){\n\t var panel = this;\n\t // send all events to the panel, re-namspaceing them with the view prefix\n\t this.listenTo( view, 'all', function(){\n\t var args = Array.prototype.slice.call( arguments, 0 );\n\t args[0] = 'view:' + args[0];\n\t panel.trigger.apply( panel, args );\n\t });\n\t\n\t // drag multiple - hijack ev.setData to add all selected items\n\t this.listenTo( view, 'draggable:dragstart', function( ev, v ){\n\t //TODO: set multiple drag data here\n\t var json = {},\n\t selected = this.getSelectedModels();\n\t if( selected.length ){\n\t json = selected.toJSON();\n\t } else {\n\t json = [ v.model.toJSON() ];\n\t }\n\t ev.dataTransfer.setData( 'text', JSON.stringify( json ) );\n\t //ev.dataTransfer.setDragImage( v.el, 60, 60 );\n\t }, this );\n\t\n\t return panel;\n\t },\n\t\n\t /** Attach views in this.views to the model based on $whereTo */\n\t _attachItems : function( $whereTo ){\n\t var self = this;\n\t // console.log( '_attachItems:', $whereTo, this.$list( $whereTo ) );\n\t //ASSUMES: $list has been emptied\n\t this.$list( $whereTo ).append( this.views.map( function( view ){\n\t return self._renderItemView$el( view );\n\t }));\n\t return this;\n\t },\n\t\n\t /** get a given subview's $el (or whatever may wrap it) and return it */\n\t _renderItemView$el : function( view ){\n\t // useful to wrap and override\n\t return view.render(0).$el;\n\t },\n\t\n\t /** render the empty/none-found message */\n\t _renderEmptyMessage : function( $whereTo ){\n\t this.debug( '_renderEmptyMessage', $whereTo, this.searchFor );\n\t var text = this.searchFor? this.noneFoundMsg : this.emptyMsg;\n\t return this.$emptyMessage( $whereTo ).text( text );\n\t },\n\t\n\t /** expand all item views */\n\t expandAll : function(){\n\t _.each( this.views, function( view ){\n\t view.expand();\n\t });\n\t },\n\t\n\t /** collapse all item views */\n\t collapseAll : function(){\n\t _.each( this.views, function( view ){\n\t view.collapse();\n\t });\n\t },\n\t\n\t // ------------------------------------------------------------------------ collection/views syncing\n\t /** Add a view (if the model should be viewable) to the panel */\n\t addItemView : function( model, collection, options ){\n\t // console.log( this + '.addItemView:', model );\n\t var panel = this;\n\t // get the index of the model in the list of filtered models shown by this list\n\t // in order to insert the view in the proper place\n\t //TODO:? potentially expensive\n\t var modelIndex = panel._filterCollection().indexOf( model );\n\t if( modelIndex === -1 ){ return undefined; }\n\t var view = panel._createItemView( model );\n\t // console.log( 'adding and rendering:', modelIndex, view.toString() );\n\t\n\t $( view ).queue( 'fx', [\n\t function( next ){\n\t // hide the empty message first if only view\n\t if( panel.$emptyMessage().is( ':visible' ) ){\n\t panel.$emptyMessage().fadeOut( panel.fxSpeed, next );\n\t } else {\n\t next();\n\t }\n\t },\n\t function( next ){\n\t panel._attachView( view, modelIndex );\n\t next();\n\t }\n\t ]);\n\t return view;\n\t },\n\t\n\t /** internal fn to add view (to both panel.views and panel.$list) */\n\t _attachView : function( view, modelIndex, useFx ){\n\t // console.log( this + '._attachView:', view, modelIndex, useFx );\n\t useFx = _.isUndefined( useFx )? true : useFx;\n\t modelIndex = modelIndex || 0;\n\t var panel = this;\n\t\n\t // use the modelIndex to splice into views and insert at the proper index in the DOM\n\t panel.views.splice( modelIndex, 0, view );\n\t panel._insertIntoListAt( modelIndex, panel._renderItemView$el( view ).hide() );\n\t\n\t panel.trigger( 'view:attached', view );\n\t if( useFx ){\n\t view.$el.slideDown( panel.fxSpeed, function(){\n\t panel.trigger( 'view:attached:rendered' );\n\t });\n\t } else {\n\t view.$el.show();\n\t panel.trigger( 'view:attached:rendered' );\n\t }\n\t return view;\n\t },\n\t\n\t /** insert a jq object as a child of list-items at the specified *DOM index* */\n\t _insertIntoListAt : function( index, $what ){\n\t // console.log( this + '._insertIntoListAt:', index, $what );\n\t var $list = this.$list();\n\t if( index === 0 ){\n\t $list.prepend( $what );\n\t } else {\n\t $list.children().eq( index - 1 ).after( $what );\n\t }\n\t return $what;\n\t },\n\t\n\t /** Remove a view from the panel (if found) */\n\t removeItemView : function( model, collection, options ){\n\t var panel = this;\n\t var view = _.find( panel.views, function( v ){ return v.model === model; });\n\t if( !view ){ return undefined; }\n\t panel.views = _.without( panel.views, view );\n\t panel.trigger( 'view:removed', view );\n\t\n\t // potentially show the empty message if no views left\n\t // use anonymous queue here - since remove can happen multiple times\n\t $({}).queue( 'fx', [\n\t function( next ){\n\t view.$el.fadeOut( panel.fxSpeed, next );\n\t },\n\t function( next ){\n\t view.remove();\n\t panel.trigger( 'view:removed:rendered' );\n\t if( !panel.views.length ){\n\t panel._renderEmptyMessage().fadeIn( panel.fxSpeed, next );\n\t } else {\n\t next();\n\t }\n\t }\n\t ]);\n\t return view;\n\t },\n\t\n\t /** get views based on model.id */\n\t viewFromModelId : function( id ){\n\t return _.find( this.views, function( v ){ return v.model.id === id; });\n\t },\n\t\n\t /** get views based on model */\n\t viewFromModel : function( model ){\n\t return model ? this.viewFromModelId( model.id ) : undefined;\n\t },\n\t\n\t /** get views based on model properties */\n\t viewsWhereModel : function( properties ){\n\t return this.views.filter( function( view ){\n\t return _.isMatch( view.model.attributes, properties );\n\t });\n\t },\n\t\n\t /** A range of views between (and including) viewA and viewB */\n\t viewRange : function( viewA, viewB ){\n\t if( viewA === viewB ){ return ( viewA )?( [ viewA ] ):( [] ); }\n\t\n\t var indexA = this.views.indexOf( viewA ),\n\t indexB = this.views.indexOf( viewB );\n\t\n\t // handle not found\n\t if( indexA === -1 || indexB === -1 ){\n\t if( indexA === indexB ){ return []; }\n\t return ( indexA === -1 )?( [ viewB ] ):( [ viewA ] );\n\t }\n\t // reverse if indeces are\n\t //note: end inclusive\n\t return ( indexA < indexB )?\n\t this.views.slice( indexA, indexB + 1 ) :\n\t this.views.slice( indexB, indexA + 1 );\n\t },\n\t\n\t // ------------------------------------------------------------------------ searching\n\t /** render a search input for filtering datasets shown\n\t * (see SearchableMixin in base-mvc for implementation of the actual searching)\n\t * return will start the search\n\t * esc will clear the search\n\t * clicking the clear button will clear the search\n\t * uses searchInput in ui.js\n\t */\n\t _renderSearch : function( $where ){\n\t $where.find( '.controls .search-input' ).searchInput({\n\t placeholder : this.searchPlaceholder,\n\t initialVal : this.searchFor,\n\t onfirstsearch : _.bind( this._firstSearch, this ),\n\t onsearch : _.bind( this.searchItems, this ),\n\t onclear : _.bind( this.clearSearch, this )\n\t });\n\t return $where;\n\t },\n\t\n\t /** What to do on the first search entered */\n\t _firstSearch : function( searchFor ){\n\t // override to load model details if necc.\n\t this.log( 'onFirstSearch', searchFor );\n\t return this.searchItems( searchFor );\n\t },\n\t\n\t /** filter view list to those that contain the searchFor terms */\n\t searchItems : function( searchFor, force ){\n\t this.log( 'searchItems', searchFor, this.searchFor, force );\n\t if( !force && this.searchFor === searchFor ){ return this; }\n\t this.searchFor = searchFor;\n\t this.renderItems();\n\t this.trigger( 'search:searching', searchFor, this );\n\t var $search = this.$( '> .controls .search-query' );\n\t if( $search.val() !== searchFor ){\n\t $search.val( searchFor );\n\t }\n\t return this;\n\t },\n\t\n\t /** clear the search filters and show all views that are normally shown */\n\t clearSearch : function( searchFor ){\n\t //this.log( 'onSearchClear', this );\n\t this.searchFor = '';\n\t this.trigger( 'search:clear', this );\n\t this.$( '> .controls .search-query' ).val( '' );\n\t this.renderItems();\n\t return this;\n\t },\n\t\n\t // ------------------------------------------------------------------------ selection\n\t /** @type Integer when the number of list item views is >= to this, don't animate selectors */\n\t THROTTLE_SELECTOR_FX_AT : 20,\n\t\n\t /** show selectors on all visible itemViews and associated controls */\n\t showSelectors : function( speed ){\n\t speed = ( speed !== undefined )?( speed ):( this.fxSpeed );\n\t this.selecting = true;\n\t this.$( '.list-actions' ).slideDown( speed );\n\t speed = this.views.length >= this.THROTTLE_SELECTOR_FX_AT? 0 : speed;\n\t _.each( this.views, function( view ){\n\t view.showSelector( speed );\n\t });\n\t //this.selected = [];\n\t //this.lastSelected = null;\n\t },\n\t\n\t /** hide selectors on all visible itemViews and associated controls */\n\t hideSelectors : function( speed ){\n\t speed = ( speed !== undefined )?( speed ):( this.fxSpeed );\n\t this.selecting = false;\n\t this.$( '.list-actions' ).slideUp( speed );\n\t speed = this.views.length >= this.THROTTLE_SELECTOR_FX_AT? 0 : speed;\n\t _.each( this.views, function( view ){\n\t view.hideSelector( speed );\n\t });\n\t this.selected = [];\n\t this.lastSelected = null;\n\t },\n\t\n\t /** show or hide selectors on all visible itemViews and associated controls */\n\t toggleSelectors : function(){\n\t if( !this.selecting ){\n\t this.showSelectors();\n\t } else {\n\t this.hideSelectors();\n\t }\n\t },\n\t\n\t /** select all visible items */\n\t selectAll : function( event ){\n\t _.each( this.views, function( view ){\n\t view.select( event );\n\t });\n\t },\n\t\n\t /** deselect all visible items */\n\t deselectAll : function( event ){\n\t this.lastSelected = null;\n\t _.each( this.views, function( view ){\n\t view.deselect( event );\n\t });\n\t },\n\t\n\t /** select a range of datasets between A and B */\n\t selectRange : function( viewA, viewB ){\n\t var range = this.viewRange( viewA, viewB );\n\t _.each( range, function( view ){\n\t view.select();\n\t });\n\t return range;\n\t },\n\t\n\t /** return an array of all currently selected itemViews */\n\t getSelectedViews : function(){\n\t return _.filter( this.views, function( v ){\n\t return v.selected;\n\t });\n\t },\n\t\n\t /** return a collection of the models of all currenly selected items */\n\t getSelectedModels : function(){\n\t // console.log( '(getSelectedModels)' );\n\t return new this.collection.constructor( _.map( this.getSelectedViews(), function( view ){\n\t return view.model;\n\t }));\n\t },\n\t\n\t // ------------------------------------------------------------------------ loading indicator\n\t /** hide the $el and display a loading indicator (in the $el's parent) when loading new data */\n\t _showLoadingIndicator : function( msg, speed, callback ){\n\t this.debug( '_showLoadingIndicator', this.indicator, msg, speed, callback );\n\t speed = ( speed !== undefined )?( speed ):( this.fxSpeed );\n\t if( !this.indicator ){\n\t this.indicator = new LoadingIndicator( this.$el );\n\t this.debug( '\\t created', this.indicator );\n\t }\n\t if( !this.$el.is( ':visible' ) ){\n\t this.indicator.show( 0, callback );\n\t } else {\n\t this.$el.fadeOut( speed );\n\t this.indicator.show( msg, speed, callback );\n\t }\n\t },\n\t\n\t /** hide the loading indicator */\n\t _hideLoadingIndicator : function( speed, callback ){\n\t this.debug( '_hideLoadingIndicator', this.indicator, speed, callback );\n\t speed = ( speed !== undefined )?( speed ):( this.fxSpeed );\n\t if( this.indicator ){\n\t this.indicator.hide( speed, callback );\n\t }\n\t },\n\t\n\t // ------------------------------------------------------------------------ scrolling\n\t /** get the current scroll position of the panel in its parent */\n\t scrollPosition : function(){\n\t return this.$scrollContainer().scrollTop();\n\t },\n\t\n\t /** set the current scroll position of the panel in its parent */\n\t scrollTo : function( pos, speed ){\n\t speed = speed || 0;\n\t this.$scrollContainer().animate({ scrollTop: pos }, speed );\n\t return this;\n\t },\n\t\n\t /** Scrolls the panel to the top. */\n\t scrollToTop : function( speed ){\n\t return this.scrollTo( 0, speed );\n\t },\n\t\n\t /** scroll to the given view in list-items */\n\t scrollToItem : function( view, speed ){\n\t if( !view ){ return this; }\n\t return this;\n\t },\n\t\n\t /** Scrolls the panel to show the content with the given id. */\n\t scrollToId : function( id, speed ){\n\t return this.scrollToItem( this.viewFromModelId( id ), speed );\n\t },\n\t\n\t // ------------------------------------------------------------------------ panel events\n\t /** event map */\n\t events : {\n\t 'click .select-all' : 'selectAll',\n\t 'click .deselect-all' : 'deselectAll'\n\t },\n\t\n\t // ------------------------------------------------------------------------ misc\n\t /** Return a string rep of the panel */\n\t toString : function(){\n\t return 'ListPanel(' + this.collection + ')';\n\t }\n\t});\n\t\n\t// ............................................................................ TEMPLATES\n\t/** underscore templates */\n\tListPanel.prototype.templates = (function(){\n\t\n\t var elTemplate = BASE_MVC.wrapTemplate([\n\t // temp container\n\t '
                        ',\n\t '
                        ',\n\t '
                        ',\n\t '
                        ',\n\t '
                        '\n\t ]);\n\t\n\t var controlsTemplate = BASE_MVC.wrapTemplate([\n\t '
                        ',\n\t '
                        ',\n\t '
                        <%- view.title %>
                        ',\n\t '
                        ',\n\t '
                        <%- view.subtitle %>
                        ',\n\t // buttons, controls go here\n\t '
                        ',\n\t // deleted msg, etc.\n\t '
                        ',\n\t\n\t '
                        ',\n\t '
                        ',\n\t '
                        ',\n\t\n\t // show when selectors are shown\n\t '
                        ',\n\t '
                        ',\n\t '',\n\t '',\n\t '
                        ',\n\t '
                        ',\n\t '
                        ',\n\t '
                        ',\n\t '
                        '\n\t ]);\n\t\n\t return {\n\t el : elTemplate,\n\t controls : controlsTemplate\n\t };\n\t}());\n\t\n\t\n\t//=============================================================================\n\t/** View for a model that has a sub-collection (e.g. History, DatasetCollection)\n\t * Allows:\n\t * the model to be reset\n\t * auto assign panel.collection to panel.model[ panel.modelCollectionKey ]\n\t *\n\t */\n\tvar ModelListPanel = ListPanel.extend({\n\t\n\t /** key of attribute in model to assign to this.collection */\n\t modelCollectionKey : 'contents',\n\t\n\t initialize : function( attributes ){\n\t ListPanel.prototype.initialize.call( this, attributes );\n\t this.selecting = ( attributes.selecting !== undefined )? attributes.selecting : false;\n\t\n\t this.setModel( this.model, attributes );\n\t },\n\t\n\t /** release/free/shutdown old models and set up panel for new models\n\t * @fires new-model with the panel as parameter\n\t */\n\t setModel : function( model, attributes ){\n\t attributes = attributes || {};\n\t this.debug( this + '.setModel:', model, attributes );\n\t\n\t this.freeModel();\n\t this.freeViews();\n\t\n\t if( model ){\n\t var oldModelId = this.model? this.model.get( 'id' ): null;\n\t\n\t // set up the new model with user, logger, storage, events\n\t this.model = model;\n\t if( this.logger ){\n\t this.model.logger = this.logger;\n\t }\n\t this._setUpModelListeners();\n\t\n\t //TODO: relation btwn model, collection becoming tangled here\n\t // free the collection, and assign the new collection to either\n\t // the model[ modelCollectionKey ], attributes.collection, or an empty vanilla collection\n\t this.stopListening( this.collection );\n\t this.collection = this.model[ this.modelCollectionKey ]\n\t || attributes.collection\n\t || this._createDefaultCollection();\n\t this._setUpCollectionListeners();\n\t\n\t if( oldModelId && model.get( 'id' ) !== oldModelId ){\n\t this.trigger( 'new-model', this );\n\t }\n\t }\n\t return this;\n\t },\n\t\n\t /** free the current model and all listeners for it, free any views for the model */\n\t freeModel : function(){\n\t // stop/release the previous model, and clear cache to sub-views\n\t if( this.model ){\n\t this.stopListening( this.model );\n\t //TODO: see base-mvc\n\t //this.model.free();\n\t //this.model = null;\n\t }\n\t return this;\n\t },\n\t\n\t // ------------------------------------------------------------------------ listening\n\t /** listening for model events */\n\t _setUpModelListeners : function(){\n\t // override\n\t this.log( this + '._setUpModelListeners', this.model );\n\t // bounce model errors up to the panel\n\t this.listenTo( this.model, 'error', function(){\n\t var args = Array.prototype.slice.call( arguments, 0 );\n\t //args.unshift( 'model:error' );\n\t args.unshift( 'error' );\n\t this.trigger.apply( this, args );\n\t }, this );\n\t\n\t // debugging\n\t if( this.logger ){\n\t this.listenTo( this.model, 'all', function( event ){\n\t this.info( this + '(model)', event, arguments );\n\t });\n\t }\n\t return this;\n\t },\n\t\n\t /** Build a temp div containing the new children for the view's $el.\n\t */\n\t _renderControls : function( $newRender ){\n\t this.debug( this + '(ModelListPanel)._renderControls' );\n\t var json = this.model? this.model.toJSON() : {},\n\t $controls = $( this.templates.controls( json, this ) );\n\t $newRender.find( '.controls' ).replaceWith( $controls );\n\t return $controls;\n\t },\n\t\n\t // ------------------------------------------------------------------------ misc\n\t /** Return a string rep of the panel */\n\t toString : function(){\n\t return 'ModelListPanel(' + this.model + ')';\n\t }\n\t});\n\t\n\t// ............................................................................ TEMPLATES\n\t/** underscore templates */\n\tModelListPanel.prototype.templates = (function(){\n\t\n\t var controlsTemplate = BASE_MVC.wrapTemplate([\n\t '
                        ',\n\t '
                        ',\n\t //TODO: this is really the only difference - consider factoring titlebar out\n\t '
                        <%- model.name %>
                        ',\n\t '
                        ',\n\t '
                        <%- view.subtitle %>
                        ',\n\t '
                        ',\n\t '
                        ',\n\t\n\t '
                        ',\n\t '
                        ',\n\t '
                        ',\n\t\n\t '
                        ',\n\t '
                        ',\n\t '',\n\t '',\n\t '
                        ',\n\t '
                        ',\n\t '
                        ',\n\t '
                        ',\n\t '
                        '\n\t ]);\n\t\n\t return _.extend( _.clone( ListPanel.prototype.templates ), {\n\t controls : controlsTemplate\n\t });\n\t}());\n\t\n\t\n\t//=============================================================================\n\t return {\n\t ListPanel : ListPanel,\n\t ModelListPanel : ModelListPanel\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(2), __webpack_require__(1)))\n\n/***/ },\n/* 77 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, _) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(6),\n\t __webpack_require__(5)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( baseMVC, _l ){\n\t// =============================================================================\n\t/** A view on any model that has a 'tags' attribute (a list of tag strings)\n\t * Incorporates the select2 jQuery plugin for tags display/editing:\n\t * http://ivaynberg.github.io/select2/\n\t */\n\tvar TagsEditor = Backbone.View\n\t .extend( baseMVC.LoggableMixin )\n\t .extend( baseMVC.HiddenUntilActivatedViewMixin ).extend({\n\t\n\t tagName : 'div',\n\t className : 'tags-display',\n\t\n\t /** Set up listeners, parse options */\n\t initialize : function( options ){\n\t //console.debug( this, options );\n\t // only listen to the model only for changes to tags - re-render\n\t this.listenTo( this.model, 'change:tags', function(){\n\t this.render();\n\t });\n\t this.hiddenUntilActivated( options.$activator, options );\n\t },\n\t\n\t /** Build the DOM elements, call select to on the created input, and set up behaviors */\n\t render : function(){\n\t var view = this;\n\t this.$el.html( this._template() );\n\t\n\t this.$input().select2({\n\t placeholder : 'Add tags',\n\t width : '100%',\n\t tags : function(){\n\t // initialize possible tags in the dropdown based on all the tags the user has used so far\n\t return view._getTagsUsed();\n\t }\n\t });\n\t\n\t this._setUpBehaviors();\n\t return this;\n\t },\n\t\n\t /** @returns {String} the html text used to build the view's DOM */\n\t _template : function(){\n\t return [\n\t //TODO: make prompt optional\n\t '',\n\t // set up initial tags by adding as CSV to input vals (necc. to init select2)\n\t ''\n\t ].join( '' );\n\t },\n\t\n\t /** @returns {String} the sorted, comma-separated tags from the model */\n\t tagsToCSV : function(){\n\t var tagsArray = this.model.get( 'tags' );\n\t if( !_.isArray( tagsArray ) || _.isEmpty( tagsArray ) ){\n\t return '';\n\t }\n\t return tagsArray.map( function( tag ){\n\t return _.escape( tag );\n\t }).sort().join( ',' );\n\t },\n\t\n\t /** @returns {jQuery} the input for this view */\n\t $input : function(){\n\t return this.$el.find( 'input.tags-input' );\n\t },\n\t\n\t /** @returns {String[]} all tags used by the current user */\n\t _getTagsUsed : function(){\n\t//TODO: global\n\t return Galaxy.user.get( 'tags_used' );\n\t },\n\t\n\t /** set up any event listeners on the view's DOM (mostly handled by select2) */\n\t _setUpBehaviors : function(){\n\t var view = this;\n\t this.$input().on( 'change', function( event ){\n\t // save the model's tags in either remove or added event\n\t view.model.save({ tags: event.val }, { silent: true });\n\t // if it's new, add the tag to the users tags\n\t if( event.added ){\n\t //??: solve weird behavior in FF on test.galaxyproject.org where\n\t // event.added.text is string object: 'String{ 0=\"o\", 1=\"n\", 2=\"e\" }'\n\t view._addNewTagToTagsUsed( event.added.text + '' );\n\t }\n\t });\n\t },\n\t\n\t /** add a new tag (if not already there) to the list of all tags used by the user\n\t * @param {String} newTag the tag to add to the list of used\n\t */\n\t _addNewTagToTagsUsed : function( newTag ){\n\t//TODO: global\n\t var tagsUsed = Galaxy.user.get( 'tags_used' );\n\t if( !_.contains( tagsUsed, newTag ) ){\n\t tagsUsed.push( newTag );\n\t tagsUsed.sort();\n\t Galaxy.user.set( 'tags_used', tagsUsed );\n\t }\n\t },\n\t\n\t /** shut down event listeners and remove this view's DOM */\n\t remove : function(){\n\t this.$input.off();\n\t this.stopListening( this.model );\n\t Backbone.View.prototype.remove.call( this );\n\t },\n\t\n\t /** string rep */\n\t toString : function(){ return [ 'TagsEditor(', this.model + '', ')' ].join(''); }\n\t});\n\t\n\t// =============================================================================\n\treturn {\n\t TagsEditor : TagsEditor\n\t};\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(2)))\n\n/***/ },\n/* 78 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function($, _) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(5)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( _l ){\n\t'use strict';\n\t\n\t//TODO: toastr is another possibility - I didn't see where I might add details, tho\n\t\n\t/* ============================================================================\n\tError modals meant to replace the o-so-easy alerts.\n\t\n\tThese are currently styled as errormessages but use the Galaxy.modal\n\tinfrastructure to be shown/closed. They're capable of showing details in a\n\ttogglable dropdown and the details are formatted in a pre.\n\t\n\tExample:\n\t errorModal( 'Heres a message', 'A Title', { some_details: 'here' });\n\t errorModal( 'Heres a message' ); // no details, title is 'Error'\n\t\n\tThere are three specialized forms:\n\t offlineErrorModal a canned response for when there's no connection\n\t badGatewayErrorModal canned response for when Galaxy is restarting\n\t ajaxErrorModal plugable into any Backbone class as an\n\t error event handler by accepting the error args: model, xhr, options\n\t\n\tExamples:\n\t if( navigator.offLine ){ offlineErrorModal(); }\n\t if( xhr.status === 502 ){ badGatewayErrorModal(); }\n\t this.listenTo( this.model, 'error', ajaxErrorModal );\n\t\n\t============================================================================ */\n\t\n\tvar CONTACT_MSG = _l( 'Please contact a Galaxy administrator if the problem persists.' );\n\tvar DEFAULT_AJAX_ERR_MSG = _l( 'An error occurred while updating information with the server.' );\n\tvar DETAILS_MSG = _l( 'The following information can assist the developers in finding the source of the error:' );\n\t\n\t/** private helper that builds the modal and handles adding details */\n\tfunction _errorModal( message, title, details ){\n\t // create and return the modal, adding details button only if needed\n\t Galaxy.modal.show({\n\t title : title,\n\t body : message,\n\t closing_events : true,\n\t buttons : { Ok: function(){ Galaxy.modal.hide(); } },\n\t });\n\t Galaxy.modal.$el.addClass( 'error-modal' );\n\t\n\t if( details ){\n\t Galaxy.modal.$( '.error-details' ).add( Galaxy.modal.$( 'button:contains(\"Details\")' ) ).remove();\n\t $( '
                        ' ).addClass( 'error-details' )\n\t .hide().appendTo( Galaxy.modal.$( '.modal-content' ) )\n\t .append([\n\t $( '

                        ' ).text( DETAILS_MSG ),\n\t $( '

                        ' ).text( JSON.stringify( details, null, '  ' ) )\n\t            ]);\n\t\n\t        $( '' )\n\t            .appendTo( Galaxy.modal.$( '.buttons' ) )\n\t            .click( function(){ Galaxy.modal.$( '.error-details' ).toggle(); });\n\t    }\n\t    return Galaxy.modal;\n\t}\n\t\n\t/** Display a modal showing an error message but fallback to alert if there's no modal */\n\tfunction errorModal( message, title, details ){\n\t    if( !message ){ return; }\n\t\n\t    message = _l( message );\n\t    title = _l( title ) || _l( 'Error:' );\n\t    if( window.Galaxy && Galaxy.modal ){\n\t        return _errorModal( message, title, details );\n\t    }\n\t\n\t    alert( title + '\\n\\n' + message );\n\t    console.log( 'error details:', JSON.stringify( details ) );\n\t}\n\t\n\t\n\t// ----------------------------------------------------------------------------\n\t/** display a modal when the user may be offline */\n\tfunction offlineErrorModal(){\n\t    return errorModal(\n\t        _l( 'You appear to be offline. Please check your connection and try again.' ),\n\t        _l( 'Offline?' )\n\t    );\n\t}\n\t\n\t\n\t// ----------------------------------------------------------------------------\n\t/** 502 messages that should be displayed when galaxy is restarting */\n\tfunction badGatewayErrorModal(){\n\t    return errorModal(\n\t        _l( 'Galaxy is currently unreachable. Please try again in a few minutes.' ) + ' ' + CONTACT_MSG,\n\t        _l( 'Cannot connect to Galaxy' )\n\t    );\n\t}\n\t\n\t\n\t// ----------------------------------------------------------------------------\n\t/** display a modal (with details) about a failed Backbone ajax operation */\n\tfunction ajaxErrorModal( model, xhr, options, message, title ){\n\t    message = message || DEFAULT_AJAX_ERR_MSG;\n\t    message += ' ' + CONTACT_MSG;\n\t    title = title || _l( 'An error occurred' );\n\t    var details = _ajaxDetails( model, xhr, options );\n\t    return errorModal( message, title, details );\n\t}\n\t\n\t/** build details which may help debugging the ajax call */\n\tfunction _ajaxDetails( model, xhr, options ){\n\t    return {\n\t//TODO: still can't manage Raven id\n\t        raven       : _.result( window.Raven, 'lastEventId' ),\n\t        userAgent   : navigator.userAgent,\n\t        onLine      : navigator.onLine,\n\t        version     : _.result( Galaxy.config, 'version_major' ),\n\t        xhr         : _.omit( xhr, _.functions( xhr ) ),\n\t        options     : _.omit( options, 'xhr' ),\n\t        // add ajax data from Galaxy object cache\n\t        url         : _.result( Galaxy.lastAjax, 'url' ),\n\t        data        : _.result( Galaxy.lastAjax, 'data' ),\n\t        // backbone stuff (auto-redacting email for user)\n\t        model       : _.result( model, 'toJSON' , model + '' ),\n\t        user        : _.omit( _.result( Galaxy.user, 'toJSON' ), 'email' ),\n\t    };\n\t}\n\t\n\t\n\t//=============================================================================\n\t    return {\n\t        errorModal          : errorModal,\n\t        offlineErrorModal   : offlineErrorModal,\n\t        badGatewayErrorModal: badGatewayErrorModal,\n\t        ajaxErrorModal      : ajaxErrorModal\n\t    };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(1), __webpack_require__(2)))\n\n/***/ },\n/* 79 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, $, _, jQuery) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t    //jquery\n\t    //backbone\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function(){\n\t// =============================================================================\n\t/**\n\t * view for a popup menu\n\t */\n\tvar PopupMenu = Backbone.View.extend({\n\t//TODO: maybe better as singleton off the Galaxy obj\n\t    /** Cache the desired button element and options, set up the button click handler\n\t     *  NOTE: attaches this view as HTML/jQ data on the button for later use.\n\t     */\n\t    initialize: function( $button, options ){\n\t        // default settings\n\t        this.$button = $button;\n\t        if( !this.$button.length ){\n\t            this.$button = $( '
                        ' );\n\t }\n\t this.options = options || [];\n\t this.$button.data( 'popupmenu', this );\n\t\n\t // set up button click -> open menu behavior\n\t var menu = this;\n\t this.$button.click( function( event ){\n\t // if there's already a menu open, remove it\n\t $( '.popmenu-wrapper' ).remove();\n\t menu._renderAndShow( event );\n\t return false;\n\t });\n\t },\n\t\n\t // render the menu, append to the page body at the click position, and set up the 'click-away' handlers, show\n\t _renderAndShow: function( clickEvent ){\n\t this.render();\n\t this.$el.appendTo( 'body' ).css( this._getShownPosition( clickEvent )).show();\n\t this._setUpCloseBehavior();\n\t },\n\t\n\t // render the menu\n\t // this menu doesn't attach itself to the DOM ( see _renderAndShow )\n\t render: function(){\n\t // render the menu body absolute and hidden, fill with template\n\t this.$el.addClass( 'popmenu-wrapper' ).hide()\n\t .css({ position : 'absolute' })\n\t .html( this.template( this.$button.attr( 'id' ), this.options ));\n\t\n\t // set up behavior on each link/anchor elem\n\t if( this.options.length ){\n\t var menu = this;\n\t //precondition: there should be one option per li\n\t this.$el.find( 'li' ).each( function( i, li ){\n\t var option = menu.options[i];\n\t\n\t // if the option has 'func', call that function when the anchor is clicked\n\t if( option.func ){\n\t $( this ).children( 'a.popupmenu-option' ).click( function( event ){\n\t option.func.call( menu, event, option );\n\t // We must preventDefault otherwise clicking \"cancel\"\n\t // on a purge or something still navigates and causes\n\t // the action.\n\t event.preventDefault();\n\t // bubble up so that an option click will call the close behavior\n\t });\n\t }\n\t });\n\t }\n\t return this;\n\t },\n\t\n\t template : function( id, options ){\n\t return [\n\t '
                          ', this._templateOptions( options ), '
                        '\n\t ].join( '' );\n\t },\n\t\n\t _templateOptions : function( options ){\n\t if( !options.length ){\n\t return '
                      • (no options)
                      • ';\n\t }\n\t return _.map( options, function( option ){\n\t if( option.divider ){\n\t return '
                      • ';\n\t } else if( option.header ){\n\t return [ '
                      • ', option.html, '
                      • ' ].join( '' );\n\t }\n\t var href = option.href || 'javascript:void(0);',\n\t target = ( option.target )?( ' target=\"' + option.target + '\"' ):( '' ),\n\t check = ( option.checked )?( '' ):( '' );\n\t return [\n\t '
                      • ',\n\t check, option.html,\n\t '
                      • '\n\t ].join( '' );\n\t }).join( '' );\n\t },\n\t\n\t // get the absolute position/offset for the menu\n\t _getShownPosition : function( clickEvent ){\n\t\n\t // display menu horiz. centered on click...\n\t var menuWidth = this.$el.width();\n\t var x = clickEvent.pageX - menuWidth / 2 ;\n\t\n\t // adjust to handle horiz. scroll and window dimensions ( draw entirely on visible screen area )\n\t x = Math.min( x, $( document ).scrollLeft() + $( window ).width() - menuWidth - 5 );\n\t x = Math.max( x, $( document ).scrollLeft() + 5 );\n\t return {\n\t top: clickEvent.pageY,\n\t left: x\n\t };\n\t },\n\t\n\t // bind an event handler to all available frames so that when anything is clicked\n\t // the menu is removed from the DOM and the event handler unbinds itself\n\t _setUpCloseBehavior: function(){\n\t var menu = this;\n\t//TODO: alternately: focus hack, blocking overlay, jquery.blockui\n\t\n\t // function to close popup and unbind itself\n\t function closePopup( event ){\n\t $( document ).off( 'click.close_popup' );\n\t if( window && window.parent !== window ){\n\t try {\n\t $( window.parent.document ).off( \"click.close_popup\" );\n\t } catch( err ){}\n\t } else {\n\t try {\n\t $( 'iframe#galaxy_main' ).contents().off( \"click.close_popup\" );\n\t } catch( err ){}\n\t }\n\t menu.remove();\n\t }\n\t\n\t $( 'html' ).one( \"click.close_popup\", closePopup );\n\t if( window && window.parent !== window ){\n\t try {\n\t $( window.parent.document ).find( 'html' ).one( \"click.close_popup\", closePopup );\n\t } catch( err ){}\n\t } else {\n\t try {\n\t $( 'iframe#galaxy_main' ).contents().one( \"click.close_popup\", closePopup );\n\t } catch( err ){}\n\t }\n\t },\n\t\n\t // add a menu option/item at the given index\n\t addItem: function( item, index ){\n\t // append to end if no index\n\t index = ( index >= 0 ) ? index : this.options.length;\n\t this.options.splice( index, 0, item );\n\t return this;\n\t },\n\t\n\t // remove a menu option/item at the given index\n\t removeItem: function( index ){\n\t if( index >=0 ){\n\t this.options.splice( index, 1 );\n\t }\n\t return this;\n\t },\n\t\n\t // search for a menu option by its html\n\t findIndexByHtml: function( html ){\n\t for( var i = 0; i < this.options.length; i++ ){\n\t if( _.has( this.options[i], 'html' ) && ( this.options[i].html === html )){\n\t return i;\n\t }\n\t }\n\t return null;\n\t },\n\t\n\t // search for a menu option by its html\n\t findItemByHtml: function( html ){\n\t return this.options[( this.findIndexByHtml( html ))];\n\t },\n\t\n\t // string representation\n\t toString: function(){\n\t return 'PopupMenu';\n\t }\n\t});\n\t/** shortcut to new for when you don't need to preserve the ref */\n\tPopupMenu.create = function _create( $button, options ){\n\t return new PopupMenu( $button, options );\n\t};\n\t\n\t// -----------------------------------------------------------------------------\n\t// the following class functions are bridges from the original make_popupmenu and make_popup_menus\n\t// to the newer backbone.js PopupMenu\n\t\n\t/** Create a PopupMenu from simple map initial_options activated by clicking button_element.\n\t * Converts initial_options to object array used by PopupMenu.\n\t * @param {jQuery|DOMElement} button_element element which, when clicked, activates menu\n\t * @param {Object} initial_options map of key -> values, where\n\t * key is option text, value is fn to call when option is clicked\n\t * @returns {PopupMenu} the PopupMenu created\n\t */\n\tPopupMenu.make_popupmenu = function( button_element, initial_options ){\n\t var convertedOptions = [];\n\t _.each( initial_options, function( optionVal, optionKey ){\n\t var newOption = { html: optionKey };\n\t\n\t // keys with null values indicate: header\n\t if( optionVal === null ){ // !optionVal? (null only?)\n\t newOption.header = true;\n\t\n\t // keys with function values indicate: a menu option\n\t } else if( jQuery.type( optionVal ) === 'function' ){\n\t newOption.func = optionVal;\n\t }\n\t //TODO:?? any other special optionVals?\n\t // there was no divider option originally\n\t convertedOptions.push( newOption );\n\t });\n\t return new PopupMenu( $( button_element ), convertedOptions );\n\t};\n\t\n\t/** Find all anchors in $parent (using selector) and covert anchors into a PopupMenu options map.\n\t * @param {jQuery} $parent the element that contains the links to convert to options\n\t * @param {String} selector jq selector string to find links\n\t * @returns {Object[]} the options array to initialize a PopupMenu\n\t */\n\t//TODO: lose parent and selector, pass in array of links, use map to return options\n\tPopupMenu.convertLinksToOptions = function( $parent, selector ){\n\t $parent = $( $parent );\n\t selector = selector || 'a';\n\t var options = [];\n\t $parent.find( selector ).each( function( elem, i ){\n\t var option = {}, $link = $( elem );\n\t\n\t // convert link text to the option text (html) and the href into the option func\n\t option.html = $link.text();\n\t if( $link.attr( 'href' ) ){\n\t var linkHref = $link.attr( 'href' ),\n\t linkTarget = $link.attr( 'target' ),\n\t confirmText = $link.attr( 'confirm' );\n\t\n\t option.func = function(){\n\t // if there's a \"confirm\" attribute, throw up a confirmation dialog, and\n\t // if the user cancels - do nothing\n\t if( ( confirmText ) && ( !confirm( confirmText ) ) ){ return; }\n\t\n\t // if there's no confirm attribute, or the user accepted the confirm dialog:\n\t switch( linkTarget ){\n\t // relocate the center panel\n\t case '_parent':\n\t window.parent.location = linkHref;\n\t break;\n\t\n\t // relocate the entire window\n\t case '_top':\n\t window.top.location = linkHref;\n\t break;\n\t\n\t // relocate this panel\n\t default:\n\t window.location = linkHref;\n\t }\n\t };\n\t }\n\t options.push( option );\n\t });\n\t return options;\n\t};\n\t\n\t/** Create a single popupmenu from existing DOM button and anchor elements\n\t * @param {jQuery} $buttonElement the element that when clicked will open the menu\n\t * @param {jQuery} $menuElement the element that contains the anchors to convert into a menu\n\t * @param {String} menuElementLinkSelector jq selector string used to find anchors to be made into menu options\n\t * @returns {PopupMenu} the PopupMenu (Backbone View) that can render, control the menu\n\t */\n\tPopupMenu.fromExistingDom = function( $buttonElement, $menuElement, menuElementLinkSelector ){\n\t $buttonElement = $( $buttonElement );\n\t $menuElement = $( $menuElement );\n\t var options = PopupMenu.convertLinksToOptions( $menuElement, menuElementLinkSelector );\n\t // we're done with the menu (having converted it to an options map)\n\t $menuElement.remove();\n\t return new PopupMenu( $buttonElement, options );\n\t};\n\t\n\t/** Create all popupmenus within a document or a more specific element\n\t * @param {DOMElement} parent the DOM element in which to search for popupmenus to build (defaults to document)\n\t * @param {String} menuSelector jq selector string to find popupmenu menu elements (defaults to \"div[popupmenu]\")\n\t * @param {Function} buttonSelectorBuildFn the function to build the jq button selector.\n\t * Will be passed $menuElement, parent.\n\t * (Defaults to return '#' + $menuElement.attr( 'popupmenu' ); )\n\t * @returns {PopupMenu[]} array of popupmenus created\n\t */\n\tPopupMenu.make_popup_menus = function( parent, menuSelector, buttonSelectorBuildFn ){\n\t parent = parent || document;\n\t // orig. Glx popupmenu menus have a (non-std) attribute 'popupmenu'\n\t // which contains the id of the button that activates the menu\n\t menuSelector = menuSelector || 'div[popupmenu]';\n\t // default to (orig. Glx) matching button to menu by using the popupmenu attr of the menu as the id of the button\n\t buttonSelectorBuildFn = buttonSelectorBuildFn || function( $menuElement, parent ){\n\t return '#' + $menuElement.attr( 'popupmenu' );\n\t };\n\t\n\t // aggregate and return all PopupMenus\n\t var popupMenusCreated = [];\n\t $( parent ).find( menuSelector ).each( function(){\n\t var $menuElement = $( this ),\n\t $buttonElement = $( parent ).find( buttonSelectorBuildFn( $menuElement, parent ) );\n\t popupMenusCreated.push( PopupMenu.fromDom( $buttonElement, $menuElement ) );\n\t $buttonElement.addClass( 'popup' );\n\t });\n\t return popupMenusCreated;\n\t};\n\t\n\t\n\t// =============================================================================\n\t return PopupMenu;\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(1), __webpack_require__(2), __webpack_require__(1)))\n\n/***/ },\n/* 80 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, $) {/** This renders the content of the ftp popup **/\n\t!(__WEBPACK_AMD_DEFINE_ARRAY__ = [ __webpack_require__(4) ], __WEBPACK_AMD_DEFINE_RESULT__ = function( Utils ) {\n\t return Backbone.View.extend({\n\t initialize: function( options ) {\n\t var self = this;\n\t this.options = Utils.merge( options, {\n\t class_add : 'upload-icon-button fa fa-square-o',\n\t class_remove : 'upload-icon-button fa fa-check-square-o',\n\t class_partial : 'upload-icon-button fa fa-minus-square-o',\n\t collection : null,\n\t onchange : function() {},\n\t onadd : function() {},\n\t onremove : function() {}\n\t } );\n\t this.collection = this.options.collection;\n\t this.setElement( this._template() );\n\t this.rows = [];\n\t Utils.get({\n\t url : Galaxy.root + 'api/remote_files',\n\t success : function( ftp_files ) { self._fill( ftp_files ) },\n\t error : function() { self._fill(); }\n\t });\n\t },\n\t\n\t /** Fill table with ftp entries */\n\t _fill: function( ftp_files ) {\n\t if ( ftp_files && ftp_files.length > 0 ) {\n\t this.$( '.upload-ftp-content' ).html( $( this._templateTable() ) );\n\t var size = 0;\n\t for ( index in ftp_files ) {\n\t this.rows.push( this._add( ftp_files[ index ] ) );\n\t size += ftp_files[ index ].size;\n\t }\n\t this.$( '.upload-ftp-number' ).html( ftp_files.length + ' files' );\n\t this.$( '.upload-ftp-disk' ).html( Utils.bytesToString ( size, true ) );\n\t if ( this.collection ) {\n\t var self = this;\n\t this.$( '._has_collection' ).show();\n\t this.$select_all = this.$( '.upload-selectall' ).addClass( this.options.class_add );\n\t this.$select_all.on( 'click', function() {\n\t var add = self.$select_all.hasClass( self.options.class_add );\n\t for ( index in ftp_files ) {\n\t var ftp_file = ftp_files[ index ];\n\t var model_index = self._find( ftp_file );\n\t if( !model_index && add || model_index && !add ) {\n\t self.rows[ index ].trigger( 'click' );\n\t }\n\t }\n\t });\n\t this._refresh();\n\t }\n\t } else {\n\t this.$( '.upload-ftp-content' ).html( $( this._templateInfo() ) );\n\t }\n\t this.$( '.upload-ftp-wait' ).hide();\n\t },\n\t\n\t /** Add file to table */\n\t _add: function( ftp_file ) {\n\t var self = this;\n\t var $it = $( this._templateRow( ftp_file ) );\n\t var $icon = $it.find( '.icon' );\n\t this.$( 'tbody' ).append( $it );\n\t if ( this.collection ) {\n\t $icon.addClass( this._find( ftp_file ) ? this.options.class_remove : this.options.class_add );\n\t $it.on('click', function() {\n\t var model_index = self._find( ftp_file );\n\t $icon.removeClass();\n\t if ( !model_index ) {\n\t self.options.onadd( ftp_file );\n\t $icon.addClass( self.options.class_remove );\n\t } else {\n\t self.options.onremove( model_index );\n\t $icon.addClass( self.options.class_add );\n\t }\n\t self._refresh();\n\t });\n\t } else {\n\t $it.on('click', function() { self.options.onchange( ftp_file ) } );\n\t }\n\t return $it;\n\t },\n\t\n\t /** Refresh select all button state */\n\t _refresh: function() {\n\t var filtered = this.collection.where( { file_mode: 'ftp', enabled: true } );\n\t this.$select_all.removeClass();\n\t if ( filtered.length == 0 ) {\n\t this.$select_all.addClass( this.options.class_add );\n\t } else {\n\t this.$select_all.addClass( filtered.length == this.rows.length ? this.options.class_remove : this.options.class_partial );\n\t }\n\t },\n\t\n\t /** Get model index */\n\t _find: function( ftp_file ) {\n\t var item = this.collection.findWhere({\n\t file_path : ftp_file.path,\n\t file_mode : 'ftp',\n\t enabled : true\n\t });\n\t return item && item.get('id');\n\t },\n\t\n\t /** Template of row */\n\t _templateRow: function( options ) {\n\t return '' +\n\t '
                        ' +\n\t '' + options.path + '' +\n\t '' + Utils.bytesToString( options.size ) + '' +\n\t '' + options.ctime + '' +\n\t '';\n\t },\n\t\n\t /** Template of table */\n\t _templateTable: function() {\n\t return 'Available files: ' +\n\t '' +\n\t '' +\n\t '  ' +\n\t '' +\n\t '' +\n\t '' +\n\t '' +\n\t '' +\n\t '' +\n\t '' +\n\t '' +\n\t '' +\n\t '' +\n\t '' +\n\t '' +\n\t '' +\n\t '
                        NameSizeCreated
                        ';\n\t },\n\t\n\t /** Template of info message */\n\t _templateInfo: function() {\n\t return '
                        ' +\n\t 'Your FTP directory does not contain any files.' +\n\t '
                        ';\n\t },\n\t\n\t /** Template of main view */\n\t _template: function() {\n\t return '
                        ' +\n\t '
                        ' +\n\t '
                        This Galaxy server allows you to upload files via FTP. To upload some files, log in to the FTP server at ' + this.options.ftp_upload_site + ' using your Galaxy credentials (email address and password).
                        ' +\n\t '
                        ' +\n\t '
                        ';\n\t }\n\t });\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(1)))\n\n/***/ },\n/* 81 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, $, _) {/** This renders the content of the settings popup, allowing users to specify flags i.e. for space-to-tab conversion **/\n\t!(__WEBPACK_AMD_DEFINE_ARRAY__ = [ __webpack_require__(4) ], __WEBPACK_AMD_DEFINE_RESULT__ = function( Utils ) {\n\t return Backbone.View.extend({\n\t options: {\n\t class_check : 'fa-check-square-o',\n\t class_uncheck : 'fa-square-o',\n\t parameters : [{\n\t id : 'space_to_tab',\n\t title : 'Convert spaces to tabs',\n\t },{\n\t id : 'to_posix_lines',\n\t title : 'Use POSIX standard'\n\t }]\n\t },\n\t\n\t initialize: function( options ) {\n\t var self = this;\n\t this.model = options.model;\n\t this.setElement( $( '
                        ' ).addClass( 'upload-settings' ) );\n\t this.$el.append( $( '
                        ' ).addClass( 'upload-settings-cover' ) );\n\t this.$el.append( $( '' ).addClass( 'upload-settings-table ui-table-striped' ).append( '' ) );\n\t this.$cover = this.$( '.upload-settings-cover' );\n\t this.$table = this.$( '.upload-settings-table > tbody' );\n\t this.listenTo ( this.model, 'change', this.render, this );\n\t this.model.trigger( 'change' );\n\t },\n\t\n\t render: function() {\n\t var self = this;\n\t this.$table.empty();\n\t _.each( this.options.parameters, function( parameter ) {\n\t var $checkbox = $( '
                        ' ).addClass( 'upload-' + parameter.id + ' upload-icon-button fa' )\n\t .addClass( self.model.get( parameter.id ) && self.options.class_check || self.options.class_uncheck )\n\t .on( 'click', function() {\n\t self.model.get( 'enabled' ) && self.model.set( parameter.id, !self.model.get( parameter.id ) )\n\t });\n\t self.$table.append( $( '
                        ' ).append( $( '' +\n\t '');\n wrapper.append($el);\n this.row.append(wrapper);\n },\n \n // header\n appendHeader: function() {\n // append header row\n this.$thead.append(this.row);\n\n // row\n this.row = $('');\n },\n \n // add row cell\n add: function($el, width, align) {\n var wrapper = $('');\n if (width) {\n wrapper.css('width', width);\n }\n if (align) {\n wrapper.css('text-align', align);\n }\n wrapper.append($el);\n this.row.append(wrapper);\n },\n \n // append\n append: function(id, fade) {\n this._commit(id, fade, false);\n },\n \n // prepend\n prepend: function(id, fade) {\n this._commit(id, fade, true);\n },\n \n // get element\n get: function(id) {\n return this.$el.find('#' + id);\n },\n \n // delete\n del: function(id) {\n var item = this.$tbody.find('#' + id);\n if (item.length > 0) {\n item.remove();\n this.row_count--;\n this._refresh();\n }\n },\n\n // delete all\n delAll: function() {\n this.$tbody.empty();\n this.row_count = 0;\n this._refresh();\n },\n \n // value\n value: function(new_value) {\n // get current id/value\n this.before = this.$tbody.find('.current').attr('id');\n \n // check if new_value is defined\n if (new_value !== undefined) {\n this.$tbody.find('tr').removeClass('current');\n if (new_value) {\n this.$tbody.find('#' + new_value).addClass('current');\n }\n }\n \n // get current id/value\n var after = this.$tbody.find('.current').attr('id');\n if(after === undefined) {\n return null;\n } else {\n // fire onchange\n if (after != this.before && this.options.onchange) {\n this.options.onchange(new_value);\n }\n \n // return current value\n return after;\n }\n },\n \n // size\n size: function() {\n return this.$tbody.find('tr').length;\n },\n \n // commit\n _commit: function(id, fade, prepend) {\n // remove previous item with same id\n this.del(id);\n \n // add\n this.row.attr('id', id);\n \n // add row\n if (prepend) {\n this.$tbody.prepend(this.row);\n } else {\n this.$tbody.append(this.row);\n }\n \n // fade mode\n if (fade) {\n this.row.hide();\n this.row.fadeIn();\n }\n \n // row\n this.row = this._row();\n \n // row count\n this.row_count++;\n this._refresh();\n },\n \n // create new row\n _row: function() {\n return $('');\n },\n \n // onclick\n _onclick: function(e) {\n // get values\n var old_value = this.value();\n var new_value = $(e.target).closest('tr').attr('id');\n if (new_value != ''){\n // check equality\n if (new_value && old_value != new_value) {\n if (this.options.onconfirm) {\n this.options.onconfirm(new_value);\n } else {\n this.value(new_value);\n }\n }\n }\n },\n\n // ondblclick\n _ondblclick: function(e) {\n var value = this.value();\n if (value && this.options.ondblclick) {\n this.options.ondblclick(value);\n }\n },\n \n // refresh\n _refresh: function() {\n if (this.row_count == 0) {\n this.$tmessage.show();\n } else {\n this.$tmessage.hide();\n }\n },\n \n // load html template\n _template: function(options) {\n return '
                        ' +\n '
                        ' ).append( $checkbox ) )\n\t .append( $( '' ).append( parameter.title ) ) )\n\t });\n\t this.$cover[ this.model.get( 'enabled' ) && 'hide' || 'show' ]();\n\t }\n\t });\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(1), __webpack_require__(2)))\n\n/***/ },\n/* 82 */,\n/* 83 */,\n/* 84 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(jQuery, $) {(function (factory) {\n\t if (true) {\n\t !(__WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t } else {\n\t // Browser globals\n\t factory(jQuery);\n\t }\n\t\n\t}(function () {\n\t//=============================================================================\n\t\n\t jQuery.fn.extend({\n\t hoverhighlight : function $hoverhighlight( scope, color ){\n\t scope = scope || 'body';\n\t if( !this.length ){ return this; }\n\t\n\t $( this ).each( function(){\n\t var $this = $( this ),\n\t targetSelector = $this.data( 'target' );\n\t\n\t if( targetSelector ){\n\t $this.mouseover( function( ev ){\n\t $( targetSelector, scope ).css({\n\t background: color\n\t });\n\t })\n\t .mouseout( function( ev ){\n\t $( targetSelector ).css({\n\t background: ''\n\t });\n\t });\n\t }\n\t });\n\t return this;\n\t }\n\t });\n\t}));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(1), __webpack_require__(1)))\n\n/***/ },\n/* 85 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function($, jQuery) {// from: https://raw.githubusercontent.com/umdjs/umd/master/jqueryPlugin.js\n\t// Uses AMD or browser globals to create a jQuery plugin.\n\t(function (factory) {\n\t if (true) {\n\t //TODO: So...this turns out to be an all or nothing thing. If I load jQuery in the define below, it will\n\t // (of course) wipe the old jquery *and all the plugins loaded into it*. So the define below *is still\n\t // relying on jquery being loaded globally* in order to preserve plugins.\n\t !(__WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t } else {\n\t // Browser globals\n\t factory(jQuery);\n\t }\n\t\n\t}(function () {\n\t var _l = window._l || function( s ){ return s; };\n\t\n\t //TODO: consolidate with tool menu functionality, use there\n\t\n\t /** searchInput: (jQuery plugin)\n\t * Creates a search input, a clear button, and loading indicator\n\t * within the selected node.\n\t *\n\t * When the user either presses return or enters some minimal number\n\t * of characters, a callback is called. Pressing ESC when the input\n\t * is focused will clear the input and call a separate callback.\n\t */\n\t function searchInput( parentNode, options ){\n\t var KEYCODE_ESC = 27,\n\t KEYCODE_RETURN = 13,\n\t $parentNode = $( parentNode ),\n\t firstSearch = true,\n\t defaults = {\n\t initialVal : '',\n\t name : 'search',\n\t placeholder : 'search',\n\t classes : '',\n\t onclear : function(){},\n\t onfirstsearch : null,\n\t onsearch : function( inputVal ){},\n\t minSearchLen : 0,\n\t escWillClear : true,\n\t oninit : function(){}\n\t };\n\t\n\t // .................................................................... input rendering and events\n\t // visually clear the search, trigger an event, and call the callback\n\t function clearSearchInput( event ){\n\t var $input = $( this ).parent().children( 'input' );\n\t $input.val( '' ).trigger( 'searchInput.clear' ).blur();\n\t options.onclear();\n\t }\n\t\n\t // search for searchTerms, trigger an event, call the appropo callback (based on whether this is the first)\n\t function search( event, searchTerms ){\n\t if( !searchTerms ){\n\t return clearSearchInput();\n\t }\n\t $( this ).trigger( 'search.search', searchTerms );\n\t if( typeof options.onfirstsearch === 'function' && firstSearch ){\n\t firstSearch = false;\n\t options.onfirstsearch( searchTerms );\n\t } else {\n\t options.onsearch( searchTerms );\n\t }\n\t }\n\t\n\t // .................................................................... input rendering and events\n\t function inputTemplate(){\n\t // class search-query is bootstrap 2.3 style that now lives in base.less\n\t return [ '' ].join( '' );\n\t }\n\t\n\t // the search input that responds to keyboard events and displays the search value\n\t function $input(){\n\t return $( inputTemplate() )\n\t // select all text on a focus\n\t .focus( function( event ){\n\t $( this ).select();\n\t })\n\t // attach behaviors to esc, return if desired, search on some min len string\n\t .keyup( function( event ){\n\t event.preventDefault();\n\t event.stopPropagation();\n\t\n\t // esc key will clear if desired\n\t if( event.which === KEYCODE_ESC && options.escWillClear ){\n\t clearSearchInput.call( this, event );\n\t\n\t } else {\n\t var searchTerms = $( this ).val();\n\t // return key or the search string len > minSearchLen (if not 0) triggers search\n\t if( ( event.which === KEYCODE_RETURN )\n\t || ( options.minSearchLen && searchTerms.length >= options.minSearchLen ) ){\n\t search.call( this, event, searchTerms );\n\t }\n\t }\n\t })\n\t .val( options.initialVal );\n\t }\n\t\n\t // .................................................................... clear button rendering and events\n\t // a button for clearing the search bar, placed on the right hand side\n\t function $clearBtn(){\n\t return $([ '' ].join('') )\n\t .tooltip({ placement: 'bottom' })\n\t .click( function( event ){\n\t clearSearchInput.call( this, event );\n\t });\n\t }\n\t\n\t // .................................................................... loadingIndicator rendering\n\t // a button for clearing the search bar, placed on the right hand side\n\t function $loadingIndicator(){\n\t return $([ '' ].join('') )\n\t .hide().tooltip({ placement: 'bottom' });\n\t }\n\t\n\t // .................................................................... commands\n\t // visually swap the load, clear buttons\n\t function toggleLoadingIndicator(){\n\t $parentNode.find( '.search-loading' ).toggle();\n\t $parentNode.find( '.search-clear' ).toggle();\n\t }\n\t\n\t // .................................................................... init\n\t // string command (not constructor)\n\t if( jQuery.type( options ) === 'string' ){\n\t if( options === 'toggle-loading' ){\n\t toggleLoadingIndicator();\n\t }\n\t return $parentNode;\n\t }\n\t\n\t // initial render\n\t if( jQuery.type( options ) === 'object' ){\n\t options = jQuery.extend( true, {}, defaults, options );\n\t }\n\t //NOTE: prepended\n\t return $parentNode.addClass( 'search-input' ).prepend([ $input(), $clearBtn(), $loadingIndicator() ]);\n\t }\n\t\n\t // as jq plugin\n\t jQuery.fn.extend({\n\t searchInput : function $searchInput( options ){\n\t return this.each( function(){\n\t return searchInput( this, options );\n\t });\n\t }\n\t });\n\t}));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(1), __webpack_require__(1)))\n\n/***/ },\n/* 86 */,\n/* 87 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;!(__WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_RESULT__ = function(){\n\t// Alphanumeric/natural sort fn\n\tfunction naturalSort(a, b) {\n\t // setup temp-scope variables for comparison evauluation\n\t var re = /(-?[0-9\\.]+)/g,\n\t x = a.toString().toLowerCase() || '',\n\t y = b.toString().toLowerCase() || '',\n\t nC = String.fromCharCode(0),\n\t xN = x.replace( re, nC + '$1' + nC ).split(nC),\n\t yN = y.replace( re, nC + '$1' + nC ).split(nC),\n\t xD = (new Date(x)).getTime(),\n\t yD = xD ? (new Date(y)).getTime() : null;\n\t // natural sorting of dates\n\t if ( yD ) {\n\t if ( xD < yD ) { return -1; }\n\t else if ( xD > yD ) { return 1; }\n\t }\n\t // natural sorting through split numeric strings and default strings\n\t var oFxNcL, oFyNcL;\n\t for ( var cLoc = 0, numS = Math.max(xN.length, yN.length); cLoc < numS; cLoc++ ) {\n\t oFxNcL = parseFloat(xN[cLoc]) || xN[cLoc];\n\t oFyNcL = parseFloat(yN[cLoc]) || yN[cLoc];\n\t if (oFxNcL < oFyNcL) { return -1; }\n\t else if (oFxNcL > oFyNcL) { return 1; }\n\t }\n\t return 0;\n\t}\n\t\n\treturn naturalSort;\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__))\n\n\n/***/ },\n/* 88 */\n/***/ function(module, exports, __webpack_require__) {\n\n\t/* WEBPACK VAR INJECTION */(function(jQuery, _) {/*\n\t galaxy upload plugins - requires FormData and XMLHttpRequest\n\t*/\n\t;(function($){\n\t // add event properties\n\t jQuery.event.props.push(\"dataTransfer\");\n\t\n\t /**\n\t Posts file data to the API\n\t */\n\t $.uploadpost = function (config) {\n\t // parse options\n\t var cnf = $.extend({}, {\n\t data : {},\n\t success : function() {},\n\t error : function() {},\n\t progress : function() {},\n\t url : null,\n\t maxfilesize : 2048,\n\t error_filesize : 'File exceeds 2GB. Please use a FTP client.',\n\t error_default : 'Please make sure the file is available.',\n\t error_server : 'Upload request failed.',\n\t error_login : 'Uploads require you to log in.'\n\t }, config);\n\t\n\t // link data\n\t var data = cnf.data;\n\t\n\t // check errors\n\t if (data.error_message) {\n\t cnf.error(data.error_message);\n\t return;\n\t }\n\t\n\t // construct form data\n\t var form = new FormData();\n\t for (var key in data.payload) {\n\t form.append(key, data.payload[key]);\n\t }\n\t\n\t // add files to submission\n\t var sizes = 0;\n\t for (var key in data.files) {\n\t var d = data.files[key];\n\t form.append(d.name, d.file, d.file.name);\n\t sizes += d.file.size;\n\t }\n\t\n\t // check file size, unless it's an ftp file\n\t if (sizes > 1048576 * cnf.maxfilesize) {\n\t cnf.error(cnf.error_filesize);\n\t return;\n\t }\n\t\n\t // prepare request\n\t xhr = new XMLHttpRequest();\n\t xhr.open('POST', cnf.url, true);\n\t xhr.setRequestHeader('Accept', 'application/json');\n\t xhr.setRequestHeader('Cache-Control', 'no-cache');\n\t xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');\n\t\n\t // captures state changes\n\t xhr.onreadystatechange = function() {\n\t // check for request completed, server connection closed\n\t if (xhr.readyState == xhr.DONE) {\n\t // parse response\n\t var response = null;\n\t if (xhr.responseText) {\n\t try {\n\t response = jQuery.parseJSON(xhr.responseText);\n\t } catch (e) {\n\t response = xhr.responseText;\n\t }\n\t }\n\t // pass any error to the error option\n\t if (xhr.status < 200 || xhr.status > 299) {\n\t var text = xhr.statusText;\n\t if (xhr.status == 403) {\n\t text = cnf.error_login;\n\t } else if (xhr.status == 0) {\n\t text = cnf.error_server;\n\t } else if (!text) {\n\t text = cnf.error_default;\n\t }\n\t cnf.error(text + ' (' + xhr.status + ')');\n\t } else {\n\t cnf.success(response);\n\t }\n\t }\n\t }\n\t\n\t // prepare upload progress\n\t xhr.upload.addEventListener('progress', function(e) {\n\t if (e.lengthComputable) {\n\t cnf.progress(Math.round((e.loaded * 100) / e.total));\n\t }\n\t }, false);\n\t\n\t // send request\n\t Galaxy.emit.debug('uploadbox::uploadpost()', 'Posting following data.', cnf);\n\t xhr.send(form);\n\t }\n\t\n\t /**\n\t Handles the upload events drag/drop etc.\n\t */\n\t $.fn.uploadinput = function(options) {\n\t // initialize\n\t var el = this;\n\t var opts = $.extend({}, {\n\t ondragover : function() {},\n\t ondragleave : function() {},\n\t onchange : function() {},\n\t multiple : false\n\t }, options);\n\t\n\t // append hidden upload field\n\t var $input = $('');\n\t el.append($input.change(function (e) {\n\t opts.onchange(e.target.files);\n\t $(this).val('');\n\t }));\n\t\n\t // drag/drop events\n\t el.on('drop', function (e) {\n\t opts.ondragleave(e);\n\t if(e.dataTransfer) {\n\t opts.onchange(e.dataTransfer.files);\n\t e.preventDefault();\n\t }\n\t });\n\t el.on('dragover', function (e) {\n\t e.preventDefault();\n\t opts.ondragover(e);\n\t });\n\t el.on('dragleave', function (e) {\n\t e.stopPropagation();\n\t opts.ondragleave(e);\n\t });\n\t\n\t // exports\n\t return {\n\t dialog: function () {\n\t $input.trigger('click');\n\t }\n\t }\n\t }\n\t\n\t /**\n\t Handles the upload queue and events such as drag/drop etc.\n\t */\n\t $.fn.uploadbox = function(options) {\n\t // parse options\n\t var opts = $.extend({}, {\n\t dragover : function() {},\n\t dragleave : function() {},\n\t announce : function(d) {},\n\t initialize : function(d) {},\n\t progress : function(d, m) {},\n\t success : function(d, m) {},\n\t error : function(d, m) { alert(m); },\n\t complete : function() {}\n\t }, options);\n\t\n\t // file queue\n\t var queue = {};\n\t\n\t // queue index/length counter\n\t var queue_index = 0;\n\t var queue_length = 0;\n\t\n\t // indicates if queue is currently running\n\t var queue_running = false;\n\t var queue_stop = false;\n\t\n\t // element\n\t var uploadinput = $(this).uploadinput({\n\t multiple : true,\n\t onchange : function(files) { add(files); },\n\t ondragover : options.ondragover,\n\t ondragleave : options.ondragleave\n\t });\n\t\n\t // add new files to upload queue\n\t function add(files) {\n\t if (files && files.length && !queue_running) {\n\t var current_index = queue_index;\n\t _.each(files, function(file, key) {\n\t if (file.mode !== 'new' && _.filter(queue, function(f) {\n\t return f.name === file.name && f.size === file.size;\n\t }).length) {\n\t file.duplicate = true;\n\t }\n\t });\n\t _.each(files, function(file) {\n\t if (!file.duplicate) {\n\t var index = String(queue_index++);\n\t queue[index] = file;\n\t opts.announce(index, queue[index]);\n\t queue_length++;\n\t }\n\t });\n\t return current_index;\n\t }\n\t }\n\t\n\t // remove file from queue\n\t function remove(index) {\n\t if (queue[index]) {\n\t delete queue[index];\n\t queue_length--;\n\t }\n\t }\n\t\n\t // process an upload, recursive\n\t function process() {\n\t // validate\n\t if (queue_length == 0 || queue_stop) {\n\t queue_stop = false;\n\t queue_running = false;\n\t opts.complete();\n\t return;\n\t } else {\n\t queue_running = true;\n\t }\n\t\n\t // get an identifier from the queue\n\t var index = -1;\n\t for (var key in queue) {\n\t index = key;\n\t break;\n\t }\n\t\n\t // get current file from queue\n\t var file = queue[index];\n\t\n\t // remove from queue\n\t remove(index)\n\t\n\t // create and submit data\n\t $.uploadpost({\n\t url : opts.url,\n\t data : opts.initialize(index),\n\t success : function(message) { opts.success(index, message); process();},\n\t error : function(message) { opts.error(index, message); process();},\n\t progress : function(percentage) { opts.progress(index, percentage); }\n\t });\n\t }\n\t\n\t /*\n\t public interface\n\t */\n\t\n\t // open file browser for selection\n\t function select() {\n\t uploadinput.dialog();\n\t }\n\t\n\t // remove all entries from queue\n\t function reset(index) {\n\t for (index in queue) {\n\t remove(index);\n\t }\n\t }\n\t\n\t // initiate upload process\n\t function start() {\n\t if (!queue_running) {\n\t queue_running = true;\n\t process();\n\t }\n\t }\n\t\n\t // stop upload process\n\t function stop() {\n\t queue_stop = true;\n\t }\n\t\n\t // set options\n\t function configure(options) {\n\t opts = $.extend({}, opts, options);\n\t return opts;\n\t }\n\t\n\t // verify browser compatibility\n\t function compatible() {\n\t return window.File && window.FormData && window.XMLHttpRequest && window.FileList;\n\t }\n\t\n\t // export functions\n\t return {\n\t 'select' : select,\n\t 'add' : add,\n\t 'remove' : remove,\n\t 'start' : start,\n\t 'stop' : stop,\n\t 'reset' : reset,\n\t 'configure' : configure,\n\t 'compatible' : compatible\n\t };\n\t }\n\t})(jQuery);\n\t\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(1), __webpack_require__(2)))\n\n/***/ },\n/* 89 */\n/***/ function(module, exports, __webpack_require__) {\n\n\t/* WEBPACK VAR INJECTION */(function(_) {var RightPanel = __webpack_require__( 10 ).RightPanel,\n\t Ui = __webpack_require__( 7 ),\n\t historyOptionsMenu = __webpack_require__( 116 );\n\t CurrentHistoryView = __webpack_require__( 113 ).CurrentHistoryView,\n\t _l = __webpack_require__( 5 );\n\t\n\t/** the right hand panel in the analysis page that shows the current history */\n\tvar HistoryPanel = RightPanel.extend({\n\t\n\t title : _l( 'History' ),\n\t\n\t initialize : function( options ){\n\t RightPanel.prototype.initialize.call( this, options );\n\t this.options = _.pick( options, 'userIsAnonymous', 'allow_user_dataset_purge', 'galaxyRoot' );\n\t\n\t // view of the current history\n\t this.historyView = new CurrentHistoryView({\n\t className : CurrentHistoryView.prototype.className + ' middle',\n\t purgeAllowed : options.allow_user_dataset_purge,\n\t linkTarget : 'galaxy_main'\n\t });\n\t },\n\t\n\t /** override to change footer selector */\n\t $toggleButton : function(){\n\t return this.$( '.footer > .panel-collapse' );\n\t },\n\t\n\t render : function(){\n\t RightPanel.prototype.render.call( this );\n\t this.optionsMenu = historyOptionsMenu( this.$( '#history-options-button' ), {\n\t anonymous : this.options.userIsAnonymous,\n\t purgeAllowed : this.options.allow_user_dataset_purge,\n\t root : this.options.galaxyRoot\n\t });\n\t this.$( '> .header .buttons [title]' ).tooltip({ placement: 'bottom' });\n\t this.historyView.setElement( this.$( '.history-panel' ) );\n\t this.$el.attr( 'class', 'history-right-panel' );\n\t },\n\t\n\t /** override to add buttons */\n\t _templateHeader: function( data ){\n\t var historyUrl = this.options.galaxyRoot + 'history';\n\t var multiUrl = this.options.galaxyRoot + 'history/view_multiple';\n\t return [\n\t '
                        ',\n\t '
                        ',\n\t // this button re-fetches the history and contents and re-renders the history panel\n\t '',\n\t // opens a drop down menu with history related functions (like view all, delete, share, etc.)\n\t '',\n\t !this.options.userIsAnonymous?\n\t [ '' ].join('') : '',\n\t '
                        ',\n\t '
                        ', _.escape( this.title ), '
                        ',\n\t '
                        ',\n\t ].join('');\n\t },\n\t\n\t /** add history view div */\n\t _templateBody : function( data ){\n\t return [\n\t '
                        ',\n\t ].join('');\n\t },\n\t\n\t /** override to use simplified selector */\n\t _templateFooter: function( data ){\n\t return [\n\t '
                        ',\n\t '
                        ',\n\t '
                        ',\n\t '
                        ',\n\t ].join('');\n\t },\n\t\n\t events : {\n\t 'click #history-refresh-button' : '_clickRefresh',\n\t // override to change footer selector\n\t 'mousedown .footer > .drag' : '_mousedownDragHandler',\n\t 'click .footer > .panel-collapse' : 'toggle'\n\t },\n\t\n\t _clickRefresh : function( ev ){\n\t ev.preventDefault();\n\t this.historyView.loadCurrentHistory();\n\t },\n\t\n\t toString : function(){ return 'HistoryPanel'; }\n\t});\n\t\n\tmodule.exports = HistoryPanel;\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(2)))\n\n/***/ },\n/* 90 */\n/***/ function(module, exports, __webpack_require__) {\n\n\t/* WEBPACK VAR INJECTION */(function($, _) {var LeftPanel = __webpack_require__( 10 ).LeftPanel,\n\t Tools = __webpack_require__( 44 ),\n\t Upload = __webpack_require__( 123 ),\n\t _l = __webpack_require__( 5 );\n\t\n\t/* Builds the tool menu panel on the left of the analysis page */\n\tvar ToolPanel = LeftPanel.extend({\n\t\n\t title : _l( 'Tools' ),\n\t\n\t initialize: function( options ){\n\t LeftPanel.prototype.initialize.call( this, options );\n\t this.log( this + '.initialize:', options );\n\t\n\t /** @type {Object[]} descriptions of user's workflows to be shown in the tool menu */\n\t this.stored_workflow_menu_entries = options.stored_workflow_menu_entries || [];\n\t\n\t // create tool search, tool panel, and tool panel view.\n\t var tool_search = new Tools.ToolSearch({\n\t search_url : options.search_url,\n\t hidden : false\n\t });\n\t var tools = new Tools.ToolCollection( options.toolbox );\n\t this.tool_panel = new Tools.ToolPanel({\n\t tool_search : tool_search,\n\t tools : tools,\n\t layout : options.toolbox_in_panel\n\t });\n\t this.tool_panel_view = new Tools.ToolPanelView({ model: this.tool_panel });\n\t\n\t // add upload modal\n\t this.uploadButton = new Upload({\n\t nginx_upload_path : options.nginx_upload_path,\n\t ftp_upload_site : options.ftp_upload_site,\n\t default_genome : options.default_genome,\n\t default_extension : options.default_extension,\n\t });\n\t },\n\t\n\t render : function(){\n\t var self = this;\n\t LeftPanel.prototype.render.call( self );\n\t self.$( '.panel-header-buttons' ).append( self.uploadButton.$el );\n\t\n\t // if there are tools, render panel and display everything\n\t if (self.tool_panel.get( 'layout' ).size() > 0) {\n\t self.tool_panel_view.render();\n\t //TODO: why the hide/show?\n\t self.$( '.toolMenu' ).show();\n\t }\n\t self.$( '.toolMenuContainer' ).prepend( self.tool_panel_view.$el );\n\t\n\t self._renderWorkflowMenu();\n\t\n\t // if a tool link has the minsizehint attribute, handle it here (gen. by hiding the tool panel)\n\t self.$( 'a[minsizehint]' ).click( function() {\n\t if ( parent.handle_minwidth_hint ) {\n\t parent.handle_minwidth_hint( $( self ).attr( 'minsizehint' ) );\n\t }\n\t });\n\t },\n\t\n\t /** build the dom for the workflow portion of the tool menu */\n\t _renderWorkflowMenu : function(){\n\t var self = this;\n\t // add internal workflow list\n\t self.$( '#internal-workflows' ).append( self._templateTool({\n\t title : _l( 'All workflows' ),\n\t href : 'workflow/list_for_run'\n\t }));\n\t _.each( self.stored_workflow_menu_entries, function( menu_entry ){\n\t self.$( '#internal-workflows' ).append( self._templateTool({\n\t title : menu_entry.stored_workflow.name,\n\t href : 'workflow/run?id=' + menu_entry.encoded_stored_workflow_id\n\t }));\n\t });\n\t },\n\t\n\t /** build a link to one tool */\n\t _templateTool: function( tool ) {\n\t return [\n\t '
                        ',\n\t // global\n\t '', tool.title, '',\n\t '
                        '\n\t ].join('');\n\t },\n\t\n\t /** override to include inital menu dom and workflow section */\n\t _templateBody : function(){\n\t return [\n\t '
                        ',\n\t '
                        ',\n\t '
                        ',\n\t '
                        ',\n\t '', _l( 'Search did not match any tools.' ), '',\n\t '
                        ',\n\t '
                        ',\n\t '
                        ',\n\t '
                        ',\n\t '
                        ',\n\t '', _l( 'Workflows' ), '',\n\t '
                        ',\n\t '
                        ',\n\t '
                        ',\n\t '
                        ',\n\t '
                        ',\n\t '
                        '\n\t ].join('');\n\t },\n\t\n\t toString : function(){ return 'ToolPanel'; }\n\t});\n\t\n\tmodule.exports = ToolPanel;\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(1), __webpack_require__(2)))\n\n/***/ },\n/* 91 */,\n/* 92 */,\n/* 93 */,\n/* 94 */,\n/* 95 */,\n/* 96 */,\n/* 97 */,\n/* 98 */,\n/* 99 */,\n/* 100 */,\n/* 101 */,\n/* 102 */,\n/* 103 */,\n/* 104 */,\n/* 105 */,\n/* 106 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(jQuery, _) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(29),\n\t __webpack_require__(69),\n\t __webpack_require__(6),\n\t __webpack_require__(5)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( DC_LI, DATASET_LI_EDIT, BASE_MVC, _l ){\n\t\n\t'use strict';\n\t//==============================================================================\n\tvar DCListItemView = DC_LI.DCListItemView;\n\t/** @class Edit view for DatasetCollection.\n\t */\n\tvar DCListItemEdit = DCListItemView.extend(\n\t/** @lends DCListItemEdit.prototype */{\n\t\n\t /** override to add linkTarget */\n\t initialize : function( attributes ){\n\t DCListItemView.prototype.initialize.call( this, attributes );\n\t },\n\t\n\t // ......................................................................... misc\n\t /** String representation */\n\t toString : function(){\n\t var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n\t return 'DCListItemEdit(' + modelString + ')';\n\t }\n\t});\n\t\n\t\n\t//==============================================================================\n\tvar DCEListItemView = DC_LI.DCEListItemView;\n\t/** @class Read only view for DatasetCollectionElement.\n\t */\n\tvar DCEListItemEdit = DCEListItemView.extend(\n\t/** @lends DCEListItemEdit.prototype */{\n\t//TODO: this might be expendable - compacted with HDAListItemView\n\t\n\t /** set up */\n\t initialize : function( attributes ){\n\t DCEListItemView.prototype.initialize.call( this, attributes );\n\t },\n\t\n\t // ......................................................................... misc\n\t /** String representation */\n\t toString : function(){\n\t var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n\t return 'DCEListItemEdit(' + modelString + ')';\n\t }\n\t});\n\t\n\t\n\t//==============================================================================\n\t// NOTE: this does not inherit from DatasetDCEListItemView as you would expect\n\t//TODO: but should - if we can find something simpler than using diamond\n\t/** @class Editable view for a DatasetCollectionElement that is also an DatasetAssociation\n\t * (a dataset contained in a dataset collection).\n\t */\n\tvar DatasetDCEListItemEdit = DATASET_LI_EDIT.DatasetListItemEdit.extend(\n\t/** @lends DatasetDCEListItemEdit.prototype */{\n\t\n\t /** set up */\n\t initialize : function( attributes ){\n\t DATASET_LI_EDIT.DatasetListItemEdit.prototype.initialize.call( this, attributes );\n\t },\n\t\n\t // NOTE: this does not inherit from DatasetDCEListItemView - so we duplicate this here\n\t //TODO: fix\n\t /** In this override, only get details if in the ready state.\n\t * Note: fetch with no 'change' event triggering to prevent automatic rendering.\n\t */\n\t _fetchModelDetails : function(){\n\t var view = this;\n\t if( view.model.inReadyState() && !view.model.hasDetails() ){\n\t return view.model.fetch({ silent: true });\n\t }\n\t return jQuery.when();\n\t },\n\t\n\t /** Override to remove delete button */\n\t _renderDeleteButton : function(){\n\t return null;\n\t },\n\t\n\t // ......................................................................... misc\n\t /** String representation */\n\t toString : function(){\n\t var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n\t return 'DatasetDCEListItemEdit(' + modelString + ')';\n\t }\n\t});\n\t\n\t// ............................................................................ TEMPLATES\n\t/** underscore templates */\n\tDatasetDCEListItemEdit.prototype.templates = (function(){\n\t\n\t return _.extend( {}, DATASET_LI_EDIT.DatasetListItemEdit.prototype.templates, {\n\t titleBar : DC_LI.DatasetDCEListItemView.prototype.templates.titleBar\n\t });\n\t}());\n\t\n\t\n\t//==============================================================================\n\t/** @class Read only view for a DatasetCollectionElement that is also a DatasetCollection\n\t * (a nested DC).\n\t */\n\tvar NestedDCDCEListItemEdit = DC_LI.NestedDCDCEListItemView.extend(\n\t/** @lends NestedDCDCEListItemEdit.prototype */{\n\t\n\t /** String representation */\n\t toString : function(){\n\t var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n\t return 'NestedDCDCEListItemEdit(' + modelString + ')';\n\t }\n\t});\n\t\n\t\n\t//==============================================================================\n\t return {\n\t DCListItemEdit : DCListItemEdit,\n\t DCEListItemEdit : DCEListItemEdit,\n\t DatasetDCEListItemEdit : DatasetDCEListItemEdit,\n\t NestedDCDCEListItemEdit : NestedDCDCEListItemEdit\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(1), __webpack_require__(2)))\n\n/***/ },\n/* 107 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(68),\n\t __webpack_require__(30),\n\t __webpack_require__(106),\n\t __webpack_require__(6),\n\t __webpack_require__(5),\n\t __webpack_require__(15),\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( DC_VIEW, DC_MODEL, DC_EDIT, BASE_MVC, _l ){\n\t\n\t'use strict';\n\t/* =============================================================================\n\tTODO:\n\t\n\t============================================================================= */\n\t/** @class editable View/Controller for a dataset collection.\n\t */\n\tvar _super = DC_VIEW.CollectionView;\n\tvar CollectionViewEdit = _super.extend(\n\t/** @lends CollectionView.prototype */{\n\t //MODEL is either a DatasetCollection (or subclass) or a DatasetCollectionElement (list of pairs)\n\t\n\t /** logger used to record this.log messages, commonly set to console */\n\t //logger : console,\n\t\n\t /** sub view class used for datasets */\n\t DatasetDCEViewClass : DC_EDIT.DatasetDCEListItemEdit,\n\t /** sub view class used for nested collections */\n\t NestedDCDCEViewClass: DC_EDIT.NestedDCDCEListItemEdit,\n\t\n\t // ......................................................................... SET UP\n\t /** Set up the view, set up storage, bind listeners to HistoryContents events\n\t * @param {Object} attributes optional settings for the panel\n\t */\n\t initialize : function( attributes ){\n\t _super.prototype.initialize.call( this, attributes );\n\t },\n\t\n\t /** In this override, make the collection name editable\n\t */\n\t _setUpBehaviors : function( $where ){\n\t $where = $where || this.$el;\n\t _super.prototype._setUpBehaviors.call( this, $where );\n\t if( !this.model ){ return; }\n\t\n\t // anon users shouldn't have access to any of the following\n\t if( !Galaxy.user || Galaxy.user.isAnonymous() ){\n\t return;\n\t }\n\t\n\t //TODO: extract\n\t var panel = this,\n\t nameSelector = '> .controls .name';\n\t $where.find( nameSelector )\n\t .attr( 'title', _l( 'Click to rename collection' ) )\n\t .tooltip({ placement: 'bottom' })\n\t .make_text_editable({\n\t on_finish: function( newName ){\n\t var previousName = panel.model.get( 'name' );\n\t if( newName && newName !== previousName ){\n\t panel.$el.find( nameSelector ).text( newName );\n\t panel.model.save({ name: newName })\n\t .fail( function(){\n\t panel.$el.find( nameSelector ).text( panel.model.previous( 'name' ) );\n\t });\n\t } else {\n\t panel.$el.find( nameSelector ).text( previousName );\n\t }\n\t }\n\t });\n\t },\n\t\n\t // ........................................................................ misc\n\t /** string rep */\n\t toString : function(){\n\t return 'CollectionViewEdit(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';\n\t }\n\t});\n\t\n\t\n\t// =============================================================================\n\t/** @class non-editable, read-only View/Controller for a dataset collection. */\n\tvar ListCollectionViewEdit = CollectionViewEdit.extend(\n\t/** @lends ListCollectionView.prototype */{\n\t\n\t //TODO: not strictly needed - due to switch in CollectionView._getContentClass\n\t /** sub view class used for datasets */\n\t DatasetDCEViewClass : DC_EDIT.DatasetDCEListItemEdit,\n\t\n\t // ........................................................................ misc\n\t /** string rep */\n\t toString : function(){\n\t return 'ListCollectionViewEdit(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';\n\t }\n\t});\n\t\n\t\n\t// =============================================================================\n\t/** @class Editable, read-only View/Controller for a dataset collection. */\n\tvar PairCollectionViewEdit = ListCollectionViewEdit.extend(\n\t/** @lends PairCollectionViewEdit.prototype */{\n\t\n\t // ........................................................................ misc\n\t /** string rep */\n\t toString : function(){\n\t return 'PairCollectionViewEdit(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';\n\t }\n\t});\n\t\n\t\n\t// =============================================================================\n\t/** @class Editable (roughly since these collections are immutable),\n\t * View/Controller for a dataset collection.\n\t */\n\tvar NestedPairCollectionViewEdit = PairCollectionViewEdit.extend(\n\t/** @lends NestedPairCollectionViewEdit.prototype */{\n\t\n\t /** Override to remove the editable text from the name/identifier - these collections are considered immutable */\n\t _setUpBehaviors : function( $where ){\n\t _super.prototype._setUpBehaviors.call( this, $where );\n\t },\n\t\n\t // ........................................................................ misc\n\t /** string rep */\n\t toString : function(){\n\t return 'NestedPairCollectionViewEdit(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';\n\t }\n\t});\n\t\n\t\n\t// =============================================================================\n\t/** @class editable, View/Controller for a list of pairs dataset collection. */\n\tvar ListOfPairsCollectionViewEdit = CollectionViewEdit.extend(\n\t/** @lends ListOfPairsCollectionView.prototype */{\n\t\n\t //TODO: not strictly needed - due to switch in CollectionView._getContentClass\n\t /** sub view class used for nested collections */\n\t NestedDCDCEViewClass : DC_EDIT.NestedDCDCEListItemEdit.extend({\n\t foldoutPanelClass : NestedPairCollectionViewEdit\n\t }),\n\t\n\t // ........................................................................ misc\n\t /** string rep */\n\t toString : function(){\n\t return 'ListOfPairsCollectionViewEdit(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';\n\t }\n\t});\n\t\n\t\n\t// =============================================================================\n\t/** @class View/Controller for a list of lists dataset collection. */\n\tvar ListOfListsCollectionViewEdit = CollectionViewEdit.extend(\n\t/** @lends ListOfListsCollectionView.prototype */{\n\t\n\t //TODO: not strictly needed - due to switch in CollectionView._getContentClass\n\t /** sub view class used for nested collections */\n\t NestedDCDCEViewClass : DC_EDIT.NestedDCDCEListItemEdit.extend({\n\t foldoutPanelClass : NestedPairCollectionViewEdit\n\t }),\n\t\n\t // ........................................................................ misc\n\t /** string rep */\n\t toString : function(){\n\t return 'ListOfListsCollectionViewEdit(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';\n\t }\n\t});\n\t\n\t\n\t//==============================================================================\n\t return {\n\t CollectionViewEdit : CollectionViewEdit,\n\t ListCollectionViewEdit : ListCollectionViewEdit,\n\t PairCollectionViewEdit : PairCollectionViewEdit,\n\t ListOfPairsCollectionViewEdit : ListOfPairsCollectionViewEdit,\n\t ListOfListsCollectionViewEdit : ListOfListsCollectionViewEdit\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\n\n/***/ },\n/* 108 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, _, jQuery, $) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(131),\n\t __webpack_require__(87),\n\t __webpack_require__(31),\n\t __webpack_require__(6),\n\t __webpack_require__(5),\n\t __webpack_require__(84)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( levenshteinDistance, naturalSort, LIST_COLLECTION_CREATOR, baseMVC, _l ){\n\t\n\t'use strict';\n\t\n\tvar logNamespace = 'collections';\n\t/* ============================================================================\n\tTODO:\n\t\n\t\n\tPROGRAMMATICALLY:\n\tcurrPanel.once( 'rendered', function(){\n\t currPanel.showSelectors();\n\t currPanel.selectAll();\n\t _.last( currPanel.actionsPopup.options ).func();\n\t});\n\t\n\t============================================================================ */\n\t/** A view for paired datasets in the collections creator.\n\t */\n\tvar PairView = Backbone.View.extend( baseMVC.LoggableMixin ).extend({\n\t _logNamespace : logNamespace,\n\t\n\t tagName : 'li',\n\t className : 'dataset paired',\n\t\n\t initialize : function( attributes ){\n\t this.pair = attributes.pair || {};\n\t },\n\t\n\t template : _.template([\n\t '<%- pair.forward.name %>',\n\t '',\n\t '<%- pair.name %>',\n\t '',\n\t '<%- pair.reverse.name %>'\n\t ].join('')),\n\t\n\t render : function(){\n\t this.$el\n\t .attr( 'draggable', true )\n\t .data( 'pair', this.pair )\n\t .html( this.template({ pair: this.pair }) )\n\t .addClass( 'flex-column-container' );\n\t return this;\n\t },\n\t\n\t events : {\n\t 'dragstart' : '_dragstart',\n\t 'dragend' : '_dragend',\n\t 'dragover' : '_sendToParent',\n\t 'drop' : '_sendToParent'\n\t },\n\t\n\t /** dragging pairs for re-ordering */\n\t _dragstart : function( ev ){\n\t ev.currentTarget.style.opacity = '0.4';\n\t if( ev.originalEvent ){ ev = ev.originalEvent; }\n\t\n\t ev.dataTransfer.effectAllowed = 'move';\n\t ev.dataTransfer.setData( 'text/plain', JSON.stringify( this.pair ) );\n\t\n\t this.$el.parent().trigger( 'pair.dragstart', [ this ] );\n\t },\n\t\n\t /** dragging pairs for re-ordering */\n\t _dragend : function( ev ){\n\t ev.currentTarget.style.opacity = '1.0';\n\t this.$el.parent().trigger( 'pair.dragend', [ this ] );\n\t },\n\t\n\t /** manually bubble up an event to the parent/container */\n\t _sendToParent : function( ev ){\n\t this.$el.parent().trigger( ev );\n\t },\n\t\n\t /** string rep */\n\t toString : function(){\n\t return 'PairView(' + this.pair.name + ')';\n\t }\n\t});\n\t\n\t\n\t// ============================================================================\n\t/** returns an autopair function that uses the provided options.match function */\n\tfunction autoPairFnBuilder( options ){\n\t options = options || {};\n\t options.createPair = options.createPair || function _defaultCreatePair( params ){\n\t params = params || {};\n\t var a = params.listA.splice( params.indexA, 1 )[0],\n\t b = params.listB.splice( params.indexB, 1 )[0],\n\t aInBIndex = params.listB.indexOf( a ),\n\t bInAIndex = params.listA.indexOf( b );\n\t if( aInBIndex !== -1 ){ params.listB.splice( aInBIndex, 1 ); }\n\t if( bInAIndex !== -1 ){ params.listA.splice( bInAIndex, 1 ); }\n\t return this._pair( a, b, { silent: true });\n\t };\n\t // compile these here outside of the loop\n\t var _regexps = [];\n\t function getRegExps(){\n\t if( !_regexps.length ){\n\t _regexps = [\n\t new RegExp( this.filters[0] ),\n\t new RegExp( this.filters[1] )\n\t ];\n\t }\n\t return _regexps;\n\t }\n\t // mangle params as needed\n\t options.preprocessMatch = options.preprocessMatch || function _defaultPreprocessMatch( params ){\n\t var regexps = getRegExps.call( this );\n\t return _.extend( params, {\n\t matchTo : params.matchTo.name.replace( regexps[0], '' ),\n\t possible : params.possible.name.replace( regexps[1], '' )\n\t });\n\t };\n\t\n\t return function _strategy( params ){\n\t this.debug( 'autopair _strategy ---------------------------' );\n\t params = params || {};\n\t var listA = params.listA,\n\t listB = params.listB,\n\t indexA = 0, indexB,\n\t bestMatch = {\n\t score : 0.0,\n\t index : null\n\t },\n\t paired = [];\n\t //console.debug( 'params:', JSON.stringify( params, null, ' ' ) );\n\t this.debug( 'starting list lens:', listA.length, listB.length );\n\t this.debug( 'bestMatch (starting):', JSON.stringify( bestMatch, null, ' ' ) );\n\t\n\t while( indexA < listA.length ){\n\t var matchTo = listA[ indexA ];\n\t bestMatch.score = 0.0;\n\t\n\t for( indexB=0; indexB= scoreThreshold ){\n\t //console.debug( 'autoPairFnBuilder.strategy', listA[ indexA ].name, listB[ bestMatch.index ].name );\n\t paired.push( options.createPair.call( this, {\n\t listA : listA,\n\t indexA : indexA,\n\t listB : listB,\n\t indexB : bestMatch.index\n\t }));\n\t //console.debug( 'list lens now:', listA.length, listB.length );\n\t } else {\n\t indexA += 1;\n\t }\n\t if( !listA.length || !listB.length ){\n\t return paired;\n\t }\n\t }\n\t this.debug( 'paired:', JSON.stringify( paired, null, ' ' ) );\n\t this.debug( 'autopair _strategy ---------------------------' );\n\t return paired;\n\t };\n\t}\n\t\n\t\n\t// ============================================================================\n\t/** An interface for building collections of paired datasets.\n\t */\n\tvar PairedCollectionCreator = Backbone.View.extend( baseMVC.LoggableMixin ).extend({\n\t _logNamespace : logNamespace,\n\t\n\t className: 'list-of-pairs-collection-creator collection-creator flex-row-container',\n\t\n\t /** set up initial options, instance vars, behaviors, and autopair (if set to do so) */\n\t initialize : function( attributes ){\n\t this.metric( 'PairedCollectionCreator.initialize', attributes );\n\t //this.debug( '-- PairedCollectionCreator:', attributes );\n\t\n\t attributes = _.defaults( attributes, {\n\t datasets : [],\n\t filters : this.DEFAULT_FILTERS,\n\t automaticallyPair : true,\n\t strategy : 'lcs',\n\t matchPercentage : 0.9,\n\t twoPassAutopairing : true\n\t });\n\t\n\t /** unordered, original list */\n\t this.initialList = attributes.datasets;\n\t\n\t /** is this from a history? if so, what's its id? */\n\t this.historyId = attributes.historyId;\n\t\n\t /** which filters should be used initially? (String[2] or name in commonFilters) */\n\t this.filters = this.commonFilters[ attributes.filters ] || this.commonFilters[ this.DEFAULT_FILTERS ];\n\t if( _.isArray( attributes.filters ) ){\n\t this.filters = attributes.filters;\n\t }\n\t\n\t /** try to auto pair the unpaired datasets on load? */\n\t this.automaticallyPair = attributes.automaticallyPair;\n\t\n\t /** what method to use for auto pairing (will be passed aggression level) */\n\t this.strategy = this.strategies[ attributes.strategy ] || this.strategies[ this.DEFAULT_STRATEGY ];\n\t if( _.isFunction( attributes.strategy ) ){\n\t this.strategy = attributes.strategy;\n\t }\n\t\n\t /** distance/mismatch level allowed for autopairing */\n\t this.matchPercentage = attributes.matchPercentage;\n\t\n\t /** try to autopair using simple first, then this.strategy on the remainder */\n\t this.twoPassAutopairing = attributes.twoPassAutopairing;\n\t\n\t /** remove file extensions (\\.*) from created pair names? */\n\t this.removeExtensions = true;\n\t //this.removeExtensions = false;\n\t\n\t /** fn to call when the cancel button is clicked (scoped to this) - if falsy, no btn is displayed */\n\t this.oncancel = attributes.oncancel;\n\t /** fn to call when the collection is created (scoped to this) */\n\t this.oncreate = attributes.oncreate;\n\t\n\t /** fn to call when the cancel button is clicked (scoped to this) - if falsy, no btn is displayed */\n\t this.autoscrollDist = attributes.autoscrollDist || 24;\n\t\n\t /** is the unpaired panel shown? */\n\t this.unpairedPanelHidden = false;\n\t /** is the paired panel shown? */\n\t this.pairedPanelHidden = false;\n\t\n\t /** DOM elements currently being dragged */\n\t this.$dragging = null;\n\t\n\t /** Used for blocking UI events during ajax/operations (don't post twice) */\n\t this.blocking = false;\n\t\n\t this._setUpBehaviors();\n\t this._dataSetUp();\n\t },\n\t\n\t /** map of common filter pairs by name */\n\t commonFilters : {\n\t illumina : [ '_1', '_2' ],\n\t Rs : [ '_R1', '_R2' ]\n\t },\n\t /** which commonFilter to use by default */\n\t DEFAULT_FILTERS : 'illumina',\n\t\n\t /** map of name->fn for autopairing */\n\t strategies : {\n\t 'simple' : 'autopairSimple',\n\t 'lcs' : 'autopairLCS',\n\t 'levenshtein' : 'autopairLevenshtein'\n\t },\n\t /** default autopair strategy name */\n\t DEFAULT_STRATEGY : 'lcs',\n\t\n\t // ------------------------------------------------------------------------ process raw list\n\t /** set up main data: cache initialList, sort, and autopair */\n\t _dataSetUp : function(){\n\t //this.debug( '-- _dataSetUp' );\n\t\n\t this.paired = [];\n\t this.unpaired = [];\n\t\n\t this.selectedIds = [];\n\t\n\t // sort initial list, add ids if needed, and save new working copy to unpaired\n\t this._sortInitialList();\n\t this._ensureIds();\n\t this.unpaired = this.initialList.slice( 0 );\n\t\n\t if( this.automaticallyPair ){\n\t this.autoPair();\n\t this.once( 'rendered:initial', function(){\n\t this.trigger( 'autopair' );\n\t });\n\t }\n\t },\n\t\n\t /** sort initial list */\n\t _sortInitialList : function(){\n\t //this.debug( '-- _sortInitialList' );\n\t this._sortDatasetList( this.initialList );\n\t },\n\t\n\t /** sort a list of datasets */\n\t _sortDatasetList : function( list ){\n\t // currently only natural sort by name\n\t list.sort( function( a, b ){ return naturalSort( a.name, b.name ); });\n\t return list;\n\t },\n\t\n\t /** add ids to dataset objs in initial list if none */\n\t _ensureIds : function(){\n\t this.initialList.forEach( function( dataset ){\n\t if( !dataset.hasOwnProperty( 'id' ) ){\n\t dataset.id = _.uniqueId();\n\t }\n\t });\n\t return this.initialList;\n\t },\n\t\n\t /** split initial list into two lists, those that pass forward filters & those passing reverse */\n\t _splitByFilters : function(){\n\t var regexFilters = this.filters.map( function( stringFilter ){\n\t return new RegExp( stringFilter );\n\t }),\n\t split = [ [], [] ];\n\t\n\t function _filter( unpaired, filter ){\n\t return filter.test( unpaired.name );\n\t //return dataset.name.indexOf( filter ) >= 0;\n\t }\n\t this.unpaired.forEach( function _filterEach( unpaired ){\n\t // 90% of the time this seems to work, but:\n\t //TODO: this treats *all* strings as regex which may confuse people - possibly check for // surrounding?\n\t // would need explanation in help as well\n\t regexFilters.forEach( function( filter, i ){\n\t if( _filter( unpaired, filter ) ){\n\t split[i].push( unpaired );\n\t }\n\t });\n\t });\n\t return split;\n\t },\n\t\n\t /** add a dataset to the unpaired list in it's proper order */\n\t _addToUnpaired : function( dataset ){\n\t // currently, unpaired is natural sorted by name, use binary search to find insertion point\n\t var binSearchSortedIndex = function( low, hi ){\n\t if( low === hi ){ return low; }\n\t\n\t var mid = Math.floor( ( hi - low ) / 2 ) + low,\n\t compared = naturalSort( dataset.name, this.unpaired[ mid ].name );\n\t\n\t if( compared < 0 ){\n\t return binSearchSortedIndex( low, mid );\n\t } else if( compared > 0 ){\n\t return binSearchSortedIndex( mid + 1, hi );\n\t }\n\t // walk the equal to find the last\n\t while( this.unpaired[ mid ] && this.unpaired[ mid ].name === dataset.name ){ mid++; }\n\t return mid;\n\t\n\t }.bind( this );\n\t\n\t this.unpaired.splice( binSearchSortedIndex( 0, this.unpaired.length ), 0, dataset );\n\t },\n\t\n\t // ------------------------------------------------------------------------ auto pairing\n\t /** two passes to automatically create pairs:\n\t * use both simpleAutoPair, then the fn mentioned in strategy\n\t */\n\t autoPair : function( strategy ){\n\t // split first using exact matching\n\t var split = this._splitByFilters(),\n\t paired = [];\n\t if( this.twoPassAutopairing ){\n\t paired = this.autopairSimple({\n\t listA : split[0],\n\t listB : split[1]\n\t });\n\t split = this._splitByFilters();\n\t }\n\t\n\t // uncomment to see printlns while running tests\n\t //this.debug = function(){ console.log.apply( console, arguments ); };\n\t\n\t // then try the remainder with something less strict\n\t strategy = strategy || this.strategy;\n\t split = this._splitByFilters();\n\t paired = paired.concat( this[ strategy ].call( this, {\n\t listA : split[0],\n\t listB : split[1]\n\t }));\n\t return paired;\n\t },\n\t\n\t /** autopair by exact match */\n\t autopairSimple : autoPairFnBuilder({\n\t scoreThreshold: function(){ return 1.0; },\n\t match : function _match( params ){\n\t params = params || {};\n\t if( params.matchTo === params.possible ){\n\t return {\n\t index: params.index,\n\t score: 1.0\n\t };\n\t }\n\t return params.bestMatch;\n\t }\n\t }),\n\t\n\t /** autopair by levenshtein edit distance scoring */\n\t autopairLevenshtein : autoPairFnBuilder({\n\t scoreThreshold: function(){ return this.matchPercentage; },\n\t match : function _matches( params ){\n\t params = params || {};\n\t var distance = levenshteinDistance( params.matchTo, params.possible ),\n\t score = 1.0 - ( distance / ( Math.max( params.matchTo.length, params.possible.length ) ) );\n\t if( score > params.bestMatch.score ){\n\t return {\n\t index: params.index,\n\t score: score\n\t };\n\t }\n\t return params.bestMatch;\n\t }\n\t }),\n\t\n\t /** autopair by longest common substrings scoring */\n\t autopairLCS : autoPairFnBuilder({\n\t scoreThreshold: function(){ return this.matchPercentage; },\n\t match : function _matches( params ){\n\t params = params || {};\n\t var match = this._naiveStartingAndEndingLCS( params.matchTo, params.possible ).length,\n\t score = match / ( Math.max( params.matchTo.length, params.possible.length ) );\n\t if( score > params.bestMatch.score ){\n\t return {\n\t index: params.index,\n\t score: score\n\t };\n\t }\n\t return params.bestMatch;\n\t }\n\t }),\n\t\n\t /** return the concat'd longest common prefix and suffix from two strings */\n\t _naiveStartingAndEndingLCS : function( s1, s2 ){\n\t var fwdLCS = '',\n\t revLCS = '',\n\t i = 0, j = 0;\n\t while( i < s1.length && i < s2.length ){\n\t if( s1[ i ] !== s2[ i ] ){\n\t break;\n\t }\n\t fwdLCS += s1[ i ];\n\t i += 1;\n\t }\n\t if( i === s1.length ){ return s1; }\n\t if( i === s2.length ){ return s2; }\n\t\n\t i = ( s1.length - 1 );\n\t j = ( s2.length - 1 );\n\t while( i >= 0 && j >= 0 ){\n\t if( s1[ i ] !== s2[ j ] ){\n\t break;\n\t }\n\t revLCS = [ s1[ i ], revLCS ].join( '' );\n\t i -= 1;\n\t j -= 1;\n\t }\n\t return fwdLCS + revLCS;\n\t },\n\t\n\t // ------------------------------------------------------------------------ pairing / unpairing\n\t /** create a pair from fwd and rev, removing them from unpaired, and placing the new pair in paired */\n\t _pair : function( fwd, rev, options ){\n\t options = options || {};\n\t this.debug( '_pair:', fwd, rev );\n\t var pair = this._createPair( fwd, rev, options.name );\n\t this.paired.push( pair );\n\t this.unpaired = _.without( this.unpaired, fwd, rev );\n\t if( !options.silent ){\n\t this.trigger( 'pair:new', pair );\n\t }\n\t return pair;\n\t },\n\t\n\t /** create a pair Object from fwd and rev, adding the name attribute (will guess if not given) */\n\t _createPair : function( fwd, rev, name ){\n\t // ensure existance and don't pair something with itself\n\t if( !( fwd && rev ) || ( fwd === rev ) ){\n\t throw new Error( 'Bad pairing: ' + [ JSON.stringify( fwd ), JSON.stringify( rev ) ] );\n\t }\n\t name = name || this._guessNameForPair( fwd, rev );\n\t return { forward : fwd, name : name, reverse : rev };\n\t },\n\t\n\t /** try to find a good pair name for the given fwd and rev datasets */\n\t _guessNameForPair : function( fwd, rev, removeExtensions ){\n\t removeExtensions = ( removeExtensions !== undefined )?( removeExtensions ):( this.removeExtensions );\n\t var fwdName = fwd.name,\n\t revName = rev.name,\n\t lcs = this._naiveStartingAndEndingLCS(\n\t fwdName.replace( new RegExp( this.filters[0] ), '' ),\n\t revName.replace( new RegExp( this.filters[1] ), '' )\n\t );\n\t if( removeExtensions ){\n\t var lastDotIndex = lcs.lastIndexOf( '.' );\n\t if( lastDotIndex > 0 ){\n\t var extension = lcs.slice( lastDotIndex, lcs.length );\n\t lcs = lcs.replace( extension, '' );\n\t fwdName = fwdName.replace( extension, '' );\n\t revName = revName.replace( extension, '' );\n\t }\n\t }\n\t return lcs || ( fwdName + ' & ' + revName );\n\t },\n\t\n\t /** unpair a pair, removing it from paired, and adding the fwd,rev datasets back into unpaired */\n\t _unpair : function( pair, options ){\n\t options = options || {};\n\t if( !pair ){\n\t throw new Error( 'Bad pair: ' + JSON.stringify( pair ) );\n\t }\n\t this.paired = _.without( this.paired, pair );\n\t this._addToUnpaired( pair.forward );\n\t this._addToUnpaired( pair.reverse );\n\t\n\t if( !options.silent ){\n\t this.trigger( 'pair:unpair', [ pair ] );\n\t }\n\t return pair;\n\t },\n\t\n\t /** unpair all paired datasets */\n\t unpairAll : function(){\n\t var pairs = [];\n\t while( this.paired.length ){\n\t pairs.push( this._unpair( this.paired[ 0 ], { silent: true }) );\n\t }\n\t this.trigger( 'pair:unpair', pairs );\n\t },\n\t\n\t // ------------------------------------------------------------------------ API\n\t /** convert a pair into JSON compatible with the collections API */\n\t _pairToJSON : function( pair, src ){\n\t src = src || 'hda';\n\t //TODO: consider making this the pair structure when created instead\n\t return {\n\t collection_type : 'paired',\n\t src : 'new_collection',\n\t name : pair.name,\n\t element_identifiers : [{\n\t name : 'forward',\n\t id : pair.forward.id,\n\t src : src\n\t }, {\n\t name : 'reverse',\n\t id : pair.reverse.id,\n\t src : src\n\t }]\n\t };\n\t },\n\t\n\t /** create the collection via the API\n\t * @returns {jQuery.xhr Object} the jquery ajax request\n\t */\n\t createList : function( name ){\n\t var creator = this,\n\t url = Galaxy.root + 'api/histories/' + this.historyId + '/contents/dataset_collections';\n\t\n\t //TODO: use ListPairedCollection.create()\n\t var ajaxData = {\n\t type : 'dataset_collection',\n\t collection_type : 'list:paired',\n\t name : _.escape( name || creator.$( '.collection-name' ).val() ),\n\t element_identifiers : creator.paired.map( function( pair ){\n\t return creator._pairToJSON( pair );\n\t })\n\t\n\t };\n\t //this.debug( JSON.stringify( ajaxData ) );\n\t creator.blocking = true;\n\t return jQuery.ajax( url, {\n\t type : 'POST',\n\t contentType : 'application/json',\n\t dataType : 'json',\n\t data : JSON.stringify( ajaxData )\n\t })\n\t .always( function(){\n\t creator.blocking = false;\n\t })\n\t .fail( function( xhr, status, message ){\n\t creator._ajaxErrHandler( xhr, status, message );\n\t })\n\t .done( function( response, message, xhr ){\n\t //this.info( 'ok', response, message, xhr );\n\t creator.trigger( 'collection:created', response, message, xhr );\n\t creator.metric( 'collection:created', response );\n\t if( typeof creator.oncreate === 'function' ){\n\t creator.oncreate.call( this, response, message, xhr );\n\t }\n\t });\n\t },\n\t\n\t /** handle ajax errors with feedback and details to the user (if available) */\n\t _ajaxErrHandler : function( xhr, status, message ){\n\t this.error( xhr, status, message );\n\t var content = _l( 'An error occurred while creating this collection' );\n\t if( xhr ){\n\t if( xhr.readyState === 0 && xhr.status === 0 ){\n\t content += ': ' + _l( 'Galaxy could not be reached and may be updating.' )\n\t + _l( ' Try again in a few minutes.' );\n\t } else if( xhr.responseJSON ){\n\t content += '
                        ' + JSON.stringify( xhr.responseJSON ) + '
                        ';\n\t } else {\n\t content += ': ' + message;\n\t }\n\t }\n\t creator._showAlert( content, 'alert-danger' );\n\t },\n\t\n\t // ------------------------------------------------------------------------ rendering\n\t /** render the entire interface */\n\t render : function( speed, callback ){\n\t //this.debug( '-- _render' );\n\t //this.$el.empty().html( PairedCollectionCreator.templates.main() );\n\t this.$el.empty().html( PairedCollectionCreator.templates.main() );\n\t this._renderHeader( speed );\n\t this._renderMiddle( speed );\n\t this._renderFooter( speed );\n\t this._addPluginComponents();\n\t this.trigger( 'rendered', this );\n\t return this;\n\t },\n\t\n\t /** render the header section */\n\t _renderHeader : function( speed, callback ){\n\t //this.debug( '-- _renderHeader' );\n\t var $header = this.$( '.header' ).empty().html( PairedCollectionCreator.templates.header() )\n\t .find( '.help-content' ).prepend( $( PairedCollectionCreator.templates.helpContent() ) );\n\t\n\t this._renderFilters();\n\t return $header;\n\t },\n\t /** fill the filter inputs with the filter values */\n\t _renderFilters : function(){\n\t return this.$( '.forward-column .column-header input' ).val( this.filters[0] )\n\t .add( this.$( '.reverse-column .column-header input' ).val( this.filters[1] ) );\n\t },\n\t\n\t /** render the middle including unpaired and paired sections (which may be hidden) */\n\t _renderMiddle : function( speed, callback ){\n\t var $middle = this.$( '.middle' ).empty().html( PairedCollectionCreator.templates.middle() );\n\t\n\t // (re-) hide the un/paired panels based on instance vars\n\t if( this.unpairedPanelHidden ){\n\t this.$( '.unpaired-columns' ).hide();\n\t } else if( this.pairedPanelHidden ){\n\t this.$( '.paired-columns' ).hide();\n\t }\n\t\n\t this._renderUnpaired();\n\t this._renderPaired();\n\t return $middle;\n\t },\n\t /** render the unpaired section, showing datasets accrd. to filters, update the unpaired counts */\n\t _renderUnpaired : function( speed, callback ){\n\t //this.debug( '-- _renderUnpaired' );\n\t var creator = this,\n\t $fwd, $rev, $prd = [],\n\t split = this._splitByFilters();\n\t // update unpaired counts\n\t this.$( '.forward-column .title' )\n\t .text([ split[0].length, _l( 'unpaired forward' ) ].join( ' ' ));\n\t this.$( '.forward-column .unpaired-info' )\n\t .text( this._renderUnpairedDisplayStr( this.unpaired.length - split[0].length ) );\n\t this.$( '.reverse-column .title' )\n\t .text([ split[1].length, _l( 'unpaired reverse' ) ].join( ' ' ));\n\t this.$( '.reverse-column .unpaired-info' )\n\t .text( this._renderUnpairedDisplayStr( this.unpaired.length - split[1].length ) );\n\t\n\t this.$( '.unpaired-columns .column-datasets' ).empty();\n\t\n\t // show/hide the auto pair button if any unpaired are left\n\t this.$( '.autopair-link' ).toggle( this.unpaired.length !== 0 );\n\t if( this.unpaired.length === 0 ){\n\t this._renderUnpairedEmpty();\n\t return;\n\t }\n\t\n\t // create the dataset dom arrays\n\t $rev = split[1].map( function( dataset, i ){\n\t // if there'll be a fwd dataset across the way, add a button to pair the row\n\t if( ( split[0][ i ] !== undefined )\n\t && ( split[0][ i ] !== dataset ) ){\n\t $prd.push( creator._renderPairButton() );\n\t }\n\t return creator._renderUnpairedDataset( dataset );\n\t });\n\t $fwd = split[0].map( function( dataset ){\n\t return creator._renderUnpairedDataset( dataset );\n\t });\n\t\n\t if( !$fwd.length && !$rev.length ){\n\t this._renderUnpairedNotShown();\n\t return;\n\t }\n\t // add to appropo cols\n\t //TODO: not the best way to render - consider rendering the entire unpaired-columns section in a fragment\n\t // and swapping out that\n\t this.$( '.unpaired-columns .forward-column .column-datasets' ).append( $fwd )\n\t .add( this.$( '.unpaired-columns .paired-column .column-datasets' ).append( $prd ) )\n\t .add( this.$( '.unpaired-columns .reverse-column .column-datasets' ).append( $rev ) );\n\t this._adjUnpairedOnScrollbar();\n\t },\n\t /** return a string to display the count of filtered out datasets */\n\t _renderUnpairedDisplayStr : function( numFiltered ){\n\t return [ '(', numFiltered, ' ', _l( 'filtered out' ), ')' ].join('');\n\t },\n\t /** return an unattached jQuery DOM element to represent an unpaired dataset */\n\t _renderUnpairedDataset : function( dataset ){\n\t //TODO: to underscore template\n\t return $( '
                      • ')\n\t .attr( 'id', 'dataset-' + dataset.id )\n\t .addClass( 'dataset unpaired' )\n\t .attr( 'draggable', true )\n\t .addClass( dataset.selected? 'selected': '' )\n\t .append( $( '' ).addClass( 'dataset-name' ).text( dataset.name ) )\n\t //??\n\t .data( 'dataset', dataset );\n\t },\n\t /** render the button that may go between unpaired datasets, allowing the user to pair a row */\n\t _renderPairButton : function(){\n\t //TODO: *not* a dataset - don't pretend like it is\n\t return $( '
                      • ').addClass( 'dataset unpaired' )\n\t .append( $( '' ).addClass( 'dataset-name' ).text( _l( 'Pair these datasets' ) ) );\n\t },\n\t /** a message to display when no unpaired left */\n\t _renderUnpairedEmpty : function(){\n\t //this.debug( '-- renderUnpairedEmpty' );\n\t var $msg = $( '
                        ' )\n\t .text( '(' + _l( 'no remaining unpaired datasets' ) + ')' );\n\t this.$( '.unpaired-columns .paired-column .column-datasets' ).empty().prepend( $msg );\n\t return $msg;\n\t },\n\t /** a message to display when no unpaired can be shown with the current filters */\n\t _renderUnpairedNotShown : function(){\n\t //this.debug( '-- renderUnpairedEmpty' );\n\t var $msg = $( '
                        ' )\n\t .text( '(' + _l( 'no datasets were found matching the current filters' ) + ')' );\n\t this.$( '.unpaired-columns .paired-column .column-datasets' ).empty().prepend( $msg );\n\t return $msg;\n\t },\n\t /** try to detect if the unpaired section has a scrollbar and adjust left column for better centering of all */\n\t _adjUnpairedOnScrollbar : function(){\n\t var $unpairedColumns = this.$( '.unpaired-columns' ).last(),\n\t $firstDataset = this.$( '.unpaired-columns .reverse-column .dataset' ).first();\n\t if( !$firstDataset.length ){ return; }\n\t var ucRight = $unpairedColumns.offset().left + $unpairedColumns.outerWidth(),\n\t dsRight = $firstDataset.offset().left + $firstDataset.outerWidth(),\n\t rightDiff = Math.floor( ucRight ) - Math.floor( dsRight );\n\t //this.debug( 'rightDiff:', ucRight, '-', dsRight, '=', rightDiff );\n\t this.$( '.unpaired-columns .forward-column' )\n\t .css( 'margin-left', ( rightDiff > 0 )? rightDiff: 0 );\n\t },\n\t\n\t /** render the paired section and update counts of paired datasets */\n\t _renderPaired : function( speed, callback ){\n\t //this.debug( '-- _renderPaired' );\n\t this.$( '.paired-column-title .title' ).text([ this.paired.length, _l( 'paired' ) ].join( ' ' ) );\n\t // show/hide the unpair all link\n\t this.$( '.unpair-all-link' ).toggle( this.paired.length !== 0 );\n\t if( this.paired.length === 0 ){\n\t this._renderPairedEmpty();\n\t return;\n\t //TODO: would be best to return here (the $columns)\n\t } else {\n\t // show/hide 'remove extensions link' when any paired and they seem to have extensions\n\t this.$( '.remove-extensions-link' ).show();\n\t }\n\t\n\t this.$( '.paired-columns .column-datasets' ).empty();\n\t var creator = this;\n\t this.paired.forEach( function( pair, i ){\n\t //TODO: cache these?\n\t var pairView = new PairView({ pair: pair });\n\t creator.$( '.paired-columns .column-datasets' )\n\t .append( pairView.render().$el )\n\t .append([\n\t ''\n\t ].join( '' ));\n\t });\n\t },\n\t /** a message to display when none paired */\n\t _renderPairedEmpty : function(){\n\t var $msg = $( '
                        ' )\n\t .text( '(' + _l( 'no paired datasets yet' ) + ')' );\n\t this.$( '.paired-columns .column-datasets' ).empty().prepend( $msg );\n\t return $msg;\n\t },\n\t\n\t /** render the footer, completion controls, and cancel controls */\n\t _renderFooter : function( speed, callback ){\n\t var $footer = this.$( '.footer' ).empty().html( PairedCollectionCreator.templates.footer() );\n\t this.$( '.remove-extensions' ).prop( 'checked', this.removeExtensions );\n\t if( typeof this.oncancel === 'function' ){\n\t this.$( '.cancel-create.btn' ).show();\n\t }\n\t return $footer;\n\t },\n\t\n\t /** add any jQuery/bootstrap/custom plugins to elements rendered */\n\t _addPluginComponents : function(){\n\t this._chooseFiltersPopover( '.choose-filters-link' );\n\t this.$( '.help-content i' ).hoverhighlight( '.collection-creator', 'rgba( 64, 255, 255, 1.0 )' );\n\t },\n\t\n\t /** build a filter selection popover allowing selection of common filter pairs */\n\t _chooseFiltersPopover : function( selector ){\n\t function filterChoice( val1, val2 ){\n\t return [\n\t ''\n\t ].join('');\n\t }\n\t var $popoverContent = $( _.template([\n\t '
                        ',\n\t '
                        ',\n\t _l( 'Choose from the following filters to change which unpaired reads are shown in the display' ),\n\t ':
                        ',\n\t _.values( this.commonFilters ).map( function( filterSet ){\n\t return filterChoice( filterSet[0], filterSet[1] );\n\t }).join( '' ),\n\t '
                        '\n\t ].join(''))({}));\n\t\n\t return this.$( selector ).popover({\n\t container : '.collection-creator',\n\t placement : 'bottom',\n\t html : true,\n\t //animation : false,\n\t content : $popoverContent\n\t });\n\t },\n\t\n\t /** add (or clear if clear is truthy) a validation warning to what */\n\t _validationWarning : function( what, clear ){\n\t var VALIDATION_CLASS = 'validation-warning';\n\t if( what === 'name' ){\n\t what = this.$( '.collection-name' ).add( this.$( '.collection-name-prompt' ) );\n\t this.$( '.collection-name' ).focus().select();\n\t }\n\t if( clear ){\n\t what = what || this.$( '.' + VALIDATION_CLASS );\n\t what.removeClass( VALIDATION_CLASS );\n\t } else {\n\t what.addClass( VALIDATION_CLASS );\n\t }\n\t },\n\t\n\t // ------------------------------------------------------------------------ events\n\t /** set up event handlers on self */\n\t _setUpBehaviors : function(){\n\t this.once( 'rendered', function(){\n\t this.trigger( 'rendered:initial', this );\n\t });\n\t\n\t this.on( 'pair:new', function(){\n\t //TODO: ideally only re-render the columns (or even elements) involved\n\t this._renderUnpaired();\n\t this._renderPaired();\n\t\n\t // scroll to bottom where new pairs are added\n\t //TODO: this doesn't seem to work - innerHeight sticks at 133...\n\t // may have to do with improper flex columns\n\t //var $pairedView = this.$( '.paired-columns' );\n\t //$pairedView.scrollTop( $pairedView.innerHeight() );\n\t //this.debug( $pairedView.height() )\n\t this.$( '.paired-columns' ).scrollTop( 8000000 );\n\t });\n\t this.on( 'pair:unpair', function( pairs ){\n\t //TODO: ideally only re-render the columns (or even elements) involved\n\t this._renderUnpaired();\n\t this._renderPaired();\n\t this.splitView();\n\t });\n\t\n\t this.on( 'filter-change', function(){\n\t this.filters = [\n\t this.$( '.forward-unpaired-filter input' ).val(),\n\t this.$( '.reverse-unpaired-filter input' ).val()\n\t ];\n\t this.metric( 'filter-change', this.filters );\n\t this._renderFilters();\n\t this._renderUnpaired();\n\t });\n\t\n\t this.on( 'autopair', function(){\n\t this._renderUnpaired();\n\t this._renderPaired();\n\t\n\t var message, msgClass = null;\n\t if( this.paired.length ){\n\t msgClass = 'alert-success';\n\t message = this.paired.length + ' ' + _l( 'pairs created' );\n\t if( !this.unpaired.length ){\n\t message += ': ' + _l( 'all datasets have been successfully paired' );\n\t this.hideUnpaired();\n\t this.$( '.collection-name' ).focus();\n\t }\n\t } else {\n\t message = _l([\n\t 'Could not automatically create any pairs from the given dataset names.',\n\t 'You may want to choose or enter different filters and try auto-pairing again.',\n\t 'Close this message using the X on the right to view more help.'\n\t ].join( ' ' ));\n\t }\n\t this._showAlert( message, msgClass );\n\t });\n\t\n\t //this.on( 'all', function(){\n\t // this.info( arguments );\n\t //});\n\t return this;\n\t },\n\t\n\t events : {\n\t // header\n\t 'click .more-help' : '_clickMoreHelp',\n\t 'click .less-help' : '_clickLessHelp',\n\t 'click .header .alert button' : '_hideAlert',\n\t 'click .forward-column .column-title' : '_clickShowOnlyUnpaired',\n\t 'click .reverse-column .column-title' : '_clickShowOnlyUnpaired',\n\t 'click .unpair-all-link' : '_clickUnpairAll',\n\t //TODO: this seems kinda backasswards - re-sending jq event as a backbone event, can we listen directly?\n\t 'change .forward-unpaired-filter input' : function( ev ){ this.trigger( 'filter-change' ); },\n\t 'focus .forward-unpaired-filter input' : function( ev ){ $( ev.currentTarget ).select(); },\n\t 'click .autopair-link' : '_clickAutopair',\n\t 'click .choose-filters .filter-choice' : '_clickFilterChoice',\n\t 'click .clear-filters-link' : '_clearFilters',\n\t 'change .reverse-unpaired-filter input' : function( ev ){ this.trigger( 'filter-change' ); },\n\t 'focus .reverse-unpaired-filter input' : function( ev ){ $( ev.currentTarget ).select(); },\n\t // unpaired\n\t 'click .forward-column .dataset.unpaired' : '_clickUnpairedDataset',\n\t 'click .reverse-column .dataset.unpaired' : '_clickUnpairedDataset',\n\t 'click .paired-column .dataset.unpaired' : '_clickPairRow',\n\t 'click .unpaired-columns' : 'clearSelectedUnpaired',\n\t 'mousedown .unpaired-columns .dataset' : '_mousedownUnpaired',\n\t // divider\n\t 'click .paired-column-title' : '_clickShowOnlyPaired',\n\t 'mousedown .flexible-partition-drag' : '_startPartitionDrag',\n\t // paired\n\t 'click .paired-columns .dataset.paired' : 'selectPair',\n\t 'click .paired-columns' : 'clearSelectedPaired',\n\t 'click .paired-columns .pair-name' : '_clickPairName',\n\t 'click .unpair-btn' : '_clickUnpair',\n\t // paired - drop target\n\t //'dragenter .paired-columns' : '_dragenterPairedColumns',\n\t //'dragleave .paired-columns .column-datasets': '_dragleavePairedColumns',\n\t 'dragover .paired-columns .column-datasets' : '_dragoverPairedColumns',\n\t 'drop .paired-columns .column-datasets' : '_dropPairedColumns',\n\t\n\t 'pair.dragstart .paired-columns .column-datasets' : '_pairDragstart',\n\t 'pair.dragend .paired-columns .column-datasets' : '_pairDragend',\n\t\n\t // footer\n\t 'change .remove-extensions' : function( ev ){ this.toggleExtensions(); },\n\t 'change .collection-name' : '_changeName',\n\t 'keydown .collection-name' : '_nameCheckForEnter',\n\t 'click .cancel-create' : function( ev ){\n\t if( typeof this.oncancel === 'function' ){\n\t this.oncancel.call( this );\n\t }\n\t },\n\t 'click .create-collection' : '_clickCreate'//,\n\t },\n\t\n\t // ........................................................................ header\n\t /** expand help */\n\t _clickMoreHelp : function( ev ){\n\t this.$( '.main-help' ).addClass( 'expanded' );\n\t this.$( '.more-help' ).hide();\n\t },\n\t /** collapse help */\n\t _clickLessHelp : function( ev ){\n\t this.$( '.main-help' ).removeClass( 'expanded' );\n\t this.$( '.more-help' ).show();\n\t },\n\t\n\t /** show an alert on the top of the interface containing message (alertClass is bootstrap's alert-*)*/\n\t _showAlert : function( message, alertClass ){\n\t alertClass = alertClass || 'alert-danger';\n\t this.$( '.main-help' ).hide();\n\t this.$( '.header .alert' ).attr( 'class', 'alert alert-dismissable' ).addClass( alertClass ).show()\n\t .find( '.alert-message' ).html( message );\n\t },\n\t /** hide the alerts at the top */\n\t _hideAlert : function( message ){\n\t this.$( '.main-help' ).show();\n\t this.$( '.header .alert' ).hide();\n\t },\n\t\n\t /** toggle between showing only unpaired and split view */\n\t _clickShowOnlyUnpaired : function( ev ){\n\t //this.debug( 'click unpaired', ev.currentTarget );\n\t if( this.$( '.paired-columns' ).is( ':visible' ) ){\n\t this.hidePaired();\n\t } else {\n\t this.splitView();\n\t }\n\t },\n\t /** toggle between showing only paired and split view */\n\t _clickShowOnlyPaired : function( ev ){\n\t //this.debug( 'click paired' );\n\t if( this.$( '.unpaired-columns' ).is( ':visible' ) ){\n\t this.hideUnpaired();\n\t } else {\n\t this.splitView();\n\t }\n\t },\n\t\n\t /** hide unpaired, show paired */\n\t hideUnpaired : function( speed, callback ){\n\t this.unpairedPanelHidden = true;\n\t this.pairedPanelHidden = false;\n\t this._renderMiddle( speed, callback );\n\t },\n\t /** hide paired, show unpaired */\n\t hidePaired : function( speed, callback ){\n\t this.unpairedPanelHidden = false;\n\t this.pairedPanelHidden = true;\n\t this._renderMiddle( speed, callback );\n\t },\n\t /** show both paired and unpaired (splitting evenly) */\n\t splitView : function( speed, callback ){\n\t this.unpairedPanelHidden = this.pairedPanelHidden = false;\n\t this._renderMiddle( speed, callback );\n\t return this;\n\t },\n\t\n\t /** unpair all paired and do other super neat stuff which I'm not really sure about yet... */\n\t _clickUnpairAll : function( ev ){\n\t this.metric( 'unpairAll' );\n\t this.unpairAll();\n\t },\n\t\n\t /** attempt to autopair */\n\t _clickAutopair : function( ev ){\n\t var paired = this.autoPair();\n\t this.metric( 'autopair', paired.length, this.unpaired.length );\n\t this.trigger( 'autopair' );\n\t },\n\t\n\t /** set the filters based on the data attributes of the button click target */\n\t _clickFilterChoice : function( ev ){\n\t var $selected = $( ev.currentTarget );\n\t this.$( '.forward-unpaired-filter input' ).val( $selected.data( 'forward' ) );\n\t this.$( '.reverse-unpaired-filter input' ).val( $selected.data( 'reverse' ) );\n\t this._hideChooseFilters();\n\t this.trigger( 'filter-change' );\n\t },\n\t\n\t /** hide the choose filters popover */\n\t _hideChooseFilters : function(){\n\t //TODO: update bootstrap and remove the following hack\n\t // see also: https://github.com/twbs/bootstrap/issues/10260\n\t this.$( '.choose-filters-link' ).popover( 'hide' );\n\t this.$( '.popover' ).css( 'display', 'none' );\n\t },\n\t\n\t /** clear both filters */\n\t _clearFilters : function( ev ){\n\t this.$( '.forward-unpaired-filter input' ).val( '' );\n\t this.$( '.reverse-unpaired-filter input' ).val( '' );\n\t this.trigger( 'filter-change' );\n\t },\n\t\n\t // ........................................................................ unpaired\n\t /** select an unpaired dataset */\n\t _clickUnpairedDataset : function( ev ){\n\t ev.stopPropagation();\n\t return this.toggleSelectUnpaired( $( ev.currentTarget ) );\n\t },\n\t\n\t /** Toggle the selection of an unpaired dataset representation.\n\t * @param [jQuery] $dataset the unpaired dataset dom rep to select\n\t * @param [Boolean] options.force if defined, force selection based on T/F; otherwise, toggle\n\t */\n\t toggleSelectUnpaired : function( $dataset, options ){\n\t options = options || {};\n\t var dataset = $dataset.data( 'dataset' ),\n\t select = options.force !== undefined? options.force: !$dataset.hasClass( 'selected' );\n\t //this.debug( id, options.force, $dataset, dataset );\n\t if( !$dataset.length || dataset === undefined ){ return $dataset; }\n\t\n\t if( select ){\n\t $dataset.addClass( 'selected' );\n\t if( !options.waitToPair ){\n\t this.pairAllSelected();\n\t }\n\t\n\t } else {\n\t $dataset.removeClass( 'selected' );\n\t //delete dataset.selected;\n\t }\n\t return $dataset;\n\t },\n\t\n\t /** pair all the currently selected unpaired datasets */\n\t pairAllSelected : function( options ){\n\t options = options || {};\n\t var creator = this,\n\t fwds = [],\n\t revs = [],\n\t pairs = [];\n\t creator.$( '.unpaired-columns .forward-column .dataset.selected' ).each( function(){\n\t fwds.push( $( this ).data( 'dataset' ) );\n\t });\n\t creator.$( '.unpaired-columns .reverse-column .dataset.selected' ).each( function(){\n\t revs.push( $( this ).data( 'dataset' ) );\n\t });\n\t fwds.length = revs.length = Math.min( fwds.length, revs.length );\n\t //this.debug( fwds );\n\t //this.debug( revs );\n\t fwds.forEach( function( fwd, i ){\n\t try {\n\t pairs.push( creator._pair( fwd, revs[i], { silent: true }) );\n\t\n\t } catch( err ){\n\t //TODO: preserve selected state of those that couldn't be paired\n\t //TODO: warn that some could not be paired\n\t creator.error( err );\n\t }\n\t });\n\t if( pairs.length && !options.silent ){\n\t this.trigger( 'pair:new', pairs );\n\t }\n\t return pairs;\n\t },\n\t\n\t /** clear the selection on all unpaired datasets */\n\t clearSelectedUnpaired : function(){\n\t this.$( '.unpaired-columns .dataset.selected' ).removeClass( 'selected' );\n\t },\n\t\n\t /** when holding down the shift key on a click, 'paint' the moused over datasets as selected */\n\t _mousedownUnpaired : function( ev ){\n\t if( ev.shiftKey ){\n\t var creator = this,\n\t $startTarget = $( ev.target ).addClass( 'selected' ),\n\t moveListener = function( ev ){\n\t creator.$( ev.target ).filter( '.dataset' ).addClass( 'selected' );\n\t };\n\t $startTarget.parent().on( 'mousemove', moveListener );\n\t\n\t // on any mouseup, stop listening to the move and try to pair any selected\n\t $( document ).one( 'mouseup', function( ev ){\n\t $startTarget.parent().off( 'mousemove', moveListener );\n\t creator.pairAllSelected();\n\t });\n\t }\n\t },\n\t\n\t /** attempt to pair two datasets directly across from one another */\n\t _clickPairRow : function( ev ){\n\t //if( !ev.currentTarget ){ return true; }\n\t var rowIndex = $( ev.currentTarget ).index(),\n\t fwd = $( '.unpaired-columns .forward-column .dataset' ).eq( rowIndex ).data( 'dataset' ),\n\t rev = $( '.unpaired-columns .reverse-column .dataset' ).eq( rowIndex ).data( 'dataset' );\n\t //this.debug( 'row:', rowIndex, fwd, rev );\n\t this._pair( fwd, rev );\n\t },\n\t\n\t // ........................................................................ divider/partition\n\t /** start dragging the visible divider/partition between unpaired and paired panes */\n\t _startPartitionDrag : function( ev ){\n\t var creator = this,\n\t startingY = ev.pageY;\n\t //this.debug( 'partition drag START:', ev );\n\t $( 'body' ).css( 'cursor', 'ns-resize' );\n\t creator.$( '.flexible-partition-drag' ).css( 'color', 'black' );\n\t\n\t function endDrag( ev ){\n\t //creator.debug( 'partition drag STOP:', ev );\n\t // doing this by an added class didn't really work well - kept flashing still\n\t creator.$( '.flexible-partition-drag' ).css( 'color', '' );\n\t $( 'body' ).css( 'cursor', '' ).unbind( 'mousemove', trackMouse );\n\t }\n\t function trackMouse( ev ){\n\t var offset = ev.pageY - startingY;\n\t //creator.debug( 'partition:', startingY, offset );\n\t if( !creator.adjPartition( offset ) ){\n\t //creator.debug( 'mouseup triggered' );\n\t $( 'body' ).trigger( 'mouseup' );\n\t }\n\t creator._adjUnpairedOnScrollbar();\n\t startingY += offset;\n\t }\n\t $( 'body' ).mousemove( trackMouse );\n\t $( 'body' ).one( 'mouseup', endDrag );\n\t },\n\t\n\t /** adjust the parition up/down +/-adj pixels */\n\t adjPartition : function( adj ){\n\t var $unpaired = this.$( '.unpaired-columns' ),\n\t $paired = this.$( '.paired-columns' ),\n\t unpairedHi = parseInt( $unpaired.css( 'height' ), 10 ),\n\t pairedHi = parseInt( $paired.css( 'height' ), 10 );\n\t //this.debug( adj, 'hi\\'s:', unpairedHi, pairedHi, unpairedHi + adj, pairedHi - adj );\n\t\n\t unpairedHi = Math.max( 10, unpairedHi + adj );\n\t pairedHi = pairedHi - adj;\n\t\n\t var movingUpwards = adj < 0;\n\t // when the divider gets close to the top - lock into hiding the unpaired section\n\t if( movingUpwards ){\n\t if( this.unpairedPanelHidden ){\n\t return false;\n\t } else if( unpairedHi <= 10 ){\n\t this.hideUnpaired();\n\t return false;\n\t }\n\t } else {\n\t if( this.unpairedPanelHidden ){\n\t $unpaired.show();\n\t this.unpairedPanelHidden = false;\n\t }\n\t }\n\t\n\t // when the divider gets close to the bottom - lock into hiding the paired section\n\t if( !movingUpwards ){\n\t if( this.pairedPanelHidden ){\n\t return false;\n\t } else if( pairedHi <= 15 ){\n\t this.hidePaired();\n\t return false;\n\t }\n\t\n\t } else {\n\t if( this.pairedPanelHidden ){\n\t $paired.show();\n\t this.pairedPanelHidden = false;\n\t }\n\t }\n\t\n\t $unpaired.css({\n\t height : unpairedHi + 'px',\n\t flex : '0 0 auto'\n\t });\n\t return true;\n\t },\n\t\n\t // ........................................................................ paired\n\t /** select a pair when clicked */\n\t selectPair : function( ev ){\n\t ev.stopPropagation();\n\t $( ev.currentTarget ).toggleClass( 'selected' );\n\t },\n\t\n\t /** deselect all pairs */\n\t clearSelectedPaired : function( ev ){\n\t this.$( '.paired-columns .dataset.selected' ).removeClass( 'selected' );\n\t },\n\t\n\t /** rename a pair when the pair name is clicked */\n\t _clickPairName : function( ev ){\n\t ev.stopPropagation();\n\t var $name = $( ev.currentTarget ),\n\t $pair = $name.parent().parent(),\n\t index = $pair.index( '.dataset.paired' ),\n\t pair = this.paired[ index ],\n\t response = prompt( 'Enter a new name for the pair:', pair.name );\n\t if( response ){\n\t pair.name = response;\n\t // set a flag (which won't be passed in json creation) for manual naming so we don't overwrite these\n\t // when adding/removing extensions\n\t //hackish\n\t pair.customizedName = true;\n\t $name.text( pair.name );\n\t }\n\t },\n\t\n\t /** unpair this pair */\n\t _clickUnpair : function( ev ){\n\t //if( !ev.currentTarget ){ return true; }\n\t var pairIndex = Math.floor( $( ev.currentTarget ).index( '.unpair-btn' ) );\n\t //this.debug( 'pair:', pairIndex );\n\t this._unpair( this.paired[ pairIndex ] );\n\t },\n\t\n\t // ........................................................................ paired - drag and drop re-ordering\n\t //_dragenterPairedColumns : function( ev ){\n\t // this.debug( '_dragenterPairedColumns:', ev );\n\t //},\n\t //_dragleavePairedColumns : function( ev ){\n\t // //this.debug( '_dragleavePairedColumns:', ev );\n\t //},\n\t /** track the mouse drag over the paired list adding a placeholder to show where the drop would occur */\n\t _dragoverPairedColumns : function( ev ){\n\t //this.debug( '_dragoverPairedColumns:', ev );\n\t ev.preventDefault();\n\t\n\t var $list = this.$( '.paired-columns .column-datasets' );\n\t this._checkForAutoscroll( $list, ev.originalEvent.clientY );\n\t //this.debug( ev.originalEvent.clientX, ev.originalEvent.clientY );\n\t var $nearest = this._getNearestPairedDatasetLi( ev.originalEvent.clientY );\n\t\n\t $( '.element-drop-placeholder' ).remove();\n\t var $placeholder = $( '
                        ' );\n\t if( !$nearest.length ){\n\t $list.append( $placeholder );\n\t } else {\n\t $nearest.before( $placeholder );\n\t }\n\t },\n\t\n\t /** If the mouse is near enough to the list's top or bottom, scroll the list */\n\t _checkForAutoscroll : function( $element, y ){\n\t var AUTOSCROLL_SPEED = 2;\n\t var offset = $element.offset(),\n\t scrollTop = $element.scrollTop(),\n\t upperDist = y - offset.top,\n\t lowerDist = ( offset.top + $element.outerHeight() ) - y;\n\t //this.debug( '_checkForAutoscroll:', scrollTop, upperDist, lowerDist );\n\t if( upperDist >= 0 && upperDist < this.autoscrollDist ){\n\t $element.scrollTop( scrollTop - AUTOSCROLL_SPEED );\n\t } else if( lowerDist >= 0 && lowerDist < this.autoscrollDist ){\n\t $element.scrollTop( scrollTop + AUTOSCROLL_SPEED );\n\t }\n\t },\n\t\n\t /** get the nearest *previous* paired dataset PairView based on the mouse's Y coordinate.\n\t * If the y is at the end of the list, return an empty jQuery object.\n\t */\n\t _getNearestPairedDatasetLi : function( y ){\n\t var WIGGLE = 4,\n\t lis = this.$( '.paired-columns .column-datasets li' ).toArray();\n\t for( var i=0; i y && top - halfHeight < y ){\n\t //this.debug( y, top + halfHeight, top - halfHeight )\n\t return $li;\n\t }\n\t }\n\t return $();\n\t },\n\t /** drop (dragged/selected PairViews) onto the list, re-ordering both the DOM and the internal array of pairs */\n\t _dropPairedColumns : function( ev ){\n\t // both required for firefox\n\t ev.preventDefault();\n\t ev.dataTransfer.dropEffect = 'move';\n\t\n\t var $nearest = this._getNearestPairedDatasetLi( ev.originalEvent.clientY );\n\t if( $nearest.length ){\n\t this.$dragging.insertBefore( $nearest );\n\t\n\t } else {\n\t // no nearest before - insert after last element (unpair button)\n\t this.$dragging.insertAfter( this.$( '.paired-columns .unpair-btn' ).last() );\n\t }\n\t // resync the creator's list of paired based on the new DOM order\n\t this._syncPairsToDom();\n\t return false;\n\t },\n\t /** resync the creator's list of paired based on the DOM order of pairs */\n\t _syncPairsToDom : function(){\n\t var newPaired = [];\n\t //TODO: doesn't seem wise to use the dom to store these - can't we sync another way?\n\t this.$( '.paired-columns .dataset.paired' ).each( function(){\n\t newPaired.push( $( this ).data( 'pair' ) );\n\t });\n\t //this.debug( newPaired );\n\t this.paired = newPaired;\n\t this._renderPaired();\n\t },\n\t /** drag communication with pair sub-views: dragstart */\n\t _pairDragstart : function( ev, pair ){\n\t //this.debug( '_pairDragstart', ev, pair )\n\t // auto select the pair causing the event and move all selected\n\t pair.$el.addClass( 'selected' );\n\t var $selected = this.$( '.paired-columns .dataset.selected' );\n\t this.$dragging = $selected;\n\t },\n\t /** drag communication with pair sub-views: dragend - remove the placeholder */\n\t _pairDragend : function( ev, pair ){\n\t //this.debug( '_pairDragend', ev, pair )\n\t $( '.element-drop-placeholder' ).remove();\n\t this.$dragging = null;\n\t },\n\t\n\t // ........................................................................ footer\n\t toggleExtensions : function( force ){\n\t var creator = this;\n\t creator.removeExtensions = ( force !== undefined )?( force ):( !creator.removeExtensions );\n\t\n\t _.each( creator.paired, function( pair ){\n\t // don't overwrite custom names\n\t if( pair.customizedName ){ return; }\n\t pair.name = creator._guessNameForPair( pair.forward, pair.reverse );\n\t });\n\t\n\t creator._renderPaired();\n\t creator._renderFooter();\n\t },\n\t\n\t /** handle a collection name change */\n\t _changeName : function( ev ){\n\t this._validationWarning( 'name', !!this._getName() );\n\t },\n\t\n\t /** check for enter key press when in the collection name and submit */\n\t _nameCheckForEnter : function( ev ){\n\t if( ev.keyCode === 13 && !this.blocking ){\n\t this._clickCreate();\n\t }\n\t },\n\t\n\t /** get the current collection name */\n\t _getName : function(){\n\t return _.escape( this.$( '.collection-name' ).val() );\n\t },\n\t\n\t /** attempt to create the current collection */\n\t _clickCreate : function( ev ){\n\t var name = this._getName();\n\t if( !name ){\n\t this._validationWarning( 'name' );\n\t } else if( !this.blocking ){\n\t this.createList();\n\t }\n\t },\n\t\n\t // ------------------------------------------------------------------------ misc\n\t /** debug a dataset list */\n\t _printList : function( list ){\n\t var creator = this;\n\t _.each( list, function( e ){\n\t if( list === creator.paired ){\n\t creator._printPair( e );\n\t } else {\n\t //creator.debug( e );\n\t }\n\t });\n\t },\n\t\n\t /** print a pair Object */\n\t _printPair : function( pair ){\n\t this.debug( pair.forward.name, pair.reverse.name, ': ->', pair.name );\n\t },\n\t\n\t /** string rep */\n\t toString : function(){ return 'PairedCollectionCreator'; }\n\t});\n\t\n\t\n\t//TODO: move to require text plugin and load these as text\n\t//TODO: underscore currently unnecc. bc no vars are used\n\t//TODO: better way of localizing text-nodes in long strings\n\t/** underscore template fns attached to class */\n\tPairedCollectionCreator.templates = PairedCollectionCreator.templates || {\n\t\n\t /** the skeleton */\n\t main : _.template([\n\t '
                        ',\n\t '
                        ',\n\t '
                        '\n\t ].join('')),\n\t\n\t /** the header (not including help text) */\n\t header : _.template([\n\t '
                        ',\n\t '', _l( 'More help' ), '',\n\t '
                        ',\n\t '', _l( 'Less' ), '',\n\t '
                        ',\n\t '
                        ',\n\t '
                        ',\n\t '',\n\t '',\n\t '
                        ',\n\t\n\t '
                        ',\n\t '
                        ',\n\t '
                        ',\n\t '
                        ',\n\t '', _l( 'Unpaired forward' ), '',\n\t '',\n\t '
                        ',\n\t '
                        ',\n\t '',\n\t '
                        ',\n\t '
                        ',\n\t '
                        ',\n\t '',\n\t '
                        ',\n\t '
                        ',\n\t '
                        ',\n\t '', _l( 'Unpaired reverse' ), '',\n\t '',\n\t '
                        ',\n\t '
                        ',\n\t '',\n\t '
                        ',\n\t '
                        ',\n\t '
                        ',\n\t '
                        '\n\t ].join('')),\n\t\n\t /** the middle: unpaired, divider, and paired */\n\t middle : _.template([\n\t // contains two flex rows (rows that fill available space) and a divider btwn\n\t '
                        ',\n\t '
                        ',\n\t '
                          ',\n\t '
                          ',\n\t '
                          ',\n\t '
                            ',\n\t '
                            ',\n\t '
                            ',\n\t '
                              ',\n\t '
                              ',\n\t '
                              ',\n\t '
                              ',\n\t '
                              ',\n\t '
                              ',\n\t '
                              ',\n\t '',\n\t '
                              ',\n\t '',\n\t _l( 'Unpair all' ),\n\t '',\n\t '
                              ',\n\t '
                              ',\n\t '
                              ',\n\t '
                                ',\n\t '
                                '\n\t ].join('')),\n\t\n\t /** creation and cancel controls */\n\t footer : _.template([\n\t '
                                ',\n\t '
                                ',\n\t '',\n\t '
                                ',\n\t '
                                ',\n\t '',\n\t '
                                ', _l( 'Name' ), ':
                                ',\n\t '
                                ',\n\t '
                                ',\n\t\n\t '
                                ',\n\t '
                                ',\n\t '',\n\t '
                                ',\n\t '',\n\t '',\n\t '
                                ',\n\t '
                                ',\n\t\n\t '
                                ',\n\t '',\n\t '
                                ',\n\t '
                                '\n\t ].join('')),\n\t\n\t /** help content */\n\t helpContent : _.template([\n\t '

                                ', _l([\n\t 'Collections of paired datasets are ordered lists of dataset pairs (often forward and reverse reads). ',\n\t 'These collections can be passed to tools and workflows in order to have analyses done on each member of ',\n\t 'the entire group. This interface allows you to create a collection, choose which datasets are paired, ',\n\t 'and re-order the final collection.'\n\t ].join( '' )), '

                                ',\n\t '

                                ', _l([\n\t 'Unpaired datasets are shown in the unpaired section ',\n\t '(hover over the underlined words to highlight below). ',\n\t 'Paired datasets are shown in the paired section.',\n\t '

                                  To pair datasets, you can:',\n\t '
                                • Click a dataset in the ',\n\t 'forward column ',\n\t 'to select it then click a dataset in the ',\n\t 'reverse column.',\n\t '
                                • ',\n\t '
                                • Click one of the \"Pair these datasets\" buttons in the ',\n\t 'middle column ',\n\t 'to pair the datasets in a particular row.',\n\t '
                                • ',\n\t '
                                • Click \"Auto-pair\" ',\n\t 'to have your datasets automatically paired based on name.',\n\t '
                                • ',\n\t '
                                '\n\t ].join( '' )), '

                                ',\n\t '

                                ', _l([\n\t '

                                  You can filter what is shown in the unpaired sections by:',\n\t '
                                • Entering partial dataset names in either the ',\n\t 'forward filter or ',\n\t 'reverse filter.',\n\t '
                                • ',\n\t '
                                • Choosing from a list of preset filters by clicking the ',\n\t '\"Choose filters\" link.',\n\t '
                                • ',\n\t '
                                • Entering regular expressions to match dataset names. See: ',\n\t 'MDN\\'s JavaScript Regular Expression Tutorial. ',\n\t 'Note: forward slashes (\\\\) are not needed.',\n\t '
                                • ',\n\t '
                                • Clearing the filters by clicking the ',\n\t '\"Clear filters\" link.',\n\t '
                                • ',\n\t '
                                '\n\t ].join( '' )), '

                                ',\n\t '

                                ', _l([\n\t 'To unpair individual dataset pairs, click the ',\n\t 'unpair buttons ( ). ',\n\t 'Click the \"Unpair all\" link to unpair all pairs.'\n\t ].join( '' )), '

                                ',\n\t '

                                ', _l([\n\t 'You can include or remove the file extensions (e.g. \".fastq\") from your pair names by toggling the ',\n\t '\"Remove file extensions from pair names?\" control.'\n\t ].join( '' )), '

                                ',\n\t '

                                ', _l([\n\t 'Once your collection is complete, enter a name and ',\n\t 'click \"Create list\". ',\n\t '(Note: you do not have to pair all unpaired datasets to finish.)'\n\t ].join( '' )), '

                                '\n\t ].join(''))\n\t};\n\t\n\t\n\t//=============================================================================\n\t/** a modal version of the paired collection creator */\n\tvar pairedCollectionCreatorModal = function _pairedCollectionCreatorModal( datasets, options ){\n\t\n\t var deferred = jQuery.Deferred(),\n\t creator;\n\t\n\t options = _.defaults( options || {}, {\n\t datasets : datasets,\n\t oncancel : function(){\n\t Galaxy.modal.hide();\n\t deferred.reject( 'cancelled' );\n\t },\n\t oncreate : function( creator, response ){\n\t Galaxy.modal.hide();\n\t deferred.resolve( response );\n\t }\n\t });\n\t\n\t if( !window.Galaxy || !Galaxy.modal ){\n\t throw new Error( 'Galaxy or Galaxy.modal not found' );\n\t }\n\t\n\t creator = new PairedCollectionCreator( options );\n\t Galaxy.modal.show({\n\t title : 'Create a collection of paired datasets',\n\t body : creator.$el,\n\t width : '80%',\n\t height : '800px',\n\t closing_events: true\n\t });\n\t creator.render();\n\t window.creator = creator;\n\t\n\t //TODO: remove modal header\n\t return deferred;\n\t};\n\t\n\t\n\t//=============================================================================\n\tfunction createListOfPairsCollection( collection ){\n\t var elements = collection.toJSON();\n\t//TODO: validate elements\n\t return pairedCollectionCreatorModal( elements, {\n\t historyId : collection.historyId\n\t });\n\t}\n\t\n\t\n\t//=============================================================================\n\t return {\n\t PairedCollectionCreator : PairedCollectionCreator,\n\t pairedCollectionCreatorModal : pairedCollectionCreatorModal,\n\t createListOfPairsCollection : createListOfPairsCollection\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(2), __webpack_require__(1), __webpack_require__(1)))\n\n/***/ },\n/* 109 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, _, jQuery) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(31),\n\t __webpack_require__(39),\n\t __webpack_require__(6),\n\t __webpack_require__(5)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( LIST_CREATOR, HDCA, BASE_MVC, _l ){\n\t\n\t'use strict';\n\t\n\tvar logNamespace = 'collections';\n\t/*==============================================================================\n\tTODO:\n\t the paired creator doesn't really mesh with the list creator as parent\n\t it may be better to make an abstract super class for both\n\t composites may inherit from this (or vis-versa)\n\t PairedDatasetCollectionElementView doesn't make a lot of sense\n\t\n\t==============================================================================*/\n\t/** */\n\tvar PairedDatasetCollectionElementView = Backbone.View.extend( BASE_MVC.LoggableMixin ).extend({\n\t _logNamespace : logNamespace,\n\t\n\t//TODO: use proper class (DatasetDCE or NestedDCDCE (or the union of both))\n\t tagName : 'li',\n\t className : 'collection-element',\n\t\n\t initialize : function( attributes ){\n\t this.element = attributes.element || {};\n\t this.identifier = attributes.identifier;\n\t },\n\t\n\t render : function(){\n\t this.$el\n\t .attr( 'data-element-id', this.element.id )\n\t .html( this.template({ identifier: this.identifier, element: this.element }) );\n\t return this;\n\t },\n\t\n\t //TODO: lots of unused space in the element - possibly load details and display them horiz.\n\t template : _.template([\n\t '<%- identifier %>',\n\t '<%- element.name %>',\n\t ].join('')),\n\t\n\t /** remove the DOM and any listeners */\n\t destroy : function(){\n\t this.off();\n\t this.$el.remove();\n\t },\n\t\n\t /** string rep */\n\t toString : function(){\n\t return 'DatasetCollectionElementView()';\n\t }\n\t});\n\t\n\t\n\t// ============================================================================\n\tvar _super = LIST_CREATOR.ListCollectionCreator;\n\t\n\t/** An interface for building collections.\n\t */\n\tvar PairCollectionCreator = _super.extend({\n\t\n\t /** the class used to display individual elements */\n\t elementViewClass : PairedDatasetCollectionElementView,\n\t /** the class this creator will create and save */\n\t collectionClass : HDCA.HistoryPairDatasetCollection,\n\t className : 'pair-collection-creator collection-creator flex-row-container',\n\t\n\t /** override to no-op */\n\t _mangleDuplicateNames : function(){},\n\t\n\t // TODO: this whole pattern sucks. There needs to be two classes of problem area:\n\t // bad inital choices and\n\t // when the user has painted his/her self into a corner during creation/use-of-the-creator\n\t /** render the entire interface */\n\t render : function( speed, callback ){\n\t if( this.workingElements.length === 2 ){\n\t return _super.prototype.render.call( this, speed, callback );\n\t }\n\t return this._renderInvalid( speed, callback );\n\t },\n\t\n\t // ------------------------------------------------------------------------ rendering elements\n\t /** render forward/reverse */\n\t _renderList : function( speed, callback ){\n\t //this.debug( '-- _renderList' );\n\t //precondition: there are two valid elements in workingElements\n\t var creator = this,\n\t $tmp = jQuery( '
                                ' ),\n\t $list = creator.$list();\n\t\n\t // lose the original views, create the new, append all at once, then call their renders\n\t _.each( this.elementViews, function( view ){\n\t view.destroy();\n\t creator.removeElementView( view );\n\t });\n\t $tmp.append( creator._createForwardElementView().$el );\n\t $tmp.append( creator._createReverseElementView().$el );\n\t $list.empty().append( $tmp.children() );\n\t _.invoke( creator.elementViews, 'render' );\n\t },\n\t\n\t /** create the forward element view */\n\t _createForwardElementView : function(){\n\t return this._createElementView( this.workingElements[0], { identifier: 'forward' } );\n\t },\n\t\n\t /** create the forward element view */\n\t _createReverseElementView : function(){\n\t return this._createElementView( this.workingElements[1], { identifier: 'reverse' } );\n\t },\n\t\n\t /** create an element view, cache in elementViews, and return */\n\t _createElementView : function( element, options ){\n\t var elementView = new this.elementViewClass( _.extend( options, {\n\t element : element,\n\t }));\n\t this.elementViews.push( elementView );\n\t return elementView;\n\t },\n\t\n\t /** swap the forward, reverse elements and re-render */\n\t swap : function(){\n\t this.workingElements = [\n\t this.workingElements[1],\n\t this.workingElements[0],\n\t ];\n\t this._renderList();\n\t },\n\t\n\t events : _.extend( _.clone( _super.prototype.events ), {\n\t 'click .swap' : 'swap',\n\t }),\n\t\n\t // ------------------------------------------------------------------------ templates\n\t //TODO: move to require text plugin and load these as text\n\t //TODO: underscore currently unnecc. bc no vars are used\n\t //TODO: better way of localizing text-nodes in long strings\n\t /** underscore template fns attached to class */\n\t templates : _.extend( _.clone( _super.prototype.templates ), {\n\t /** the middle: element list */\n\t middle : _.template([\n\t '',\n\t '
                                ',\n\t '
                                '\n\t ].join('')),\n\t\n\t /** help content */\n\t helpContent : _.template([\n\t '

                                ', _l([\n\t 'Pair collections are permanent collections containing two datasets: one forward and one reverse. ',\n\t 'Often these are forward and reverse reads. The pair collections can be passed to tools and ',\n\t 'workflows in order to have analyses done on both datasets. This interface allows ',\n\t 'you to create a pair, name it, and swap which is forward and which reverse.'\n\t ].join( '' )), '

                                ',\n\t '
                                  ',\n\t '
                                • ', _l([\n\t 'Click the \"Swap\" link to make your forward dataset the reverse ',\n\t 'and the reverse dataset forward.'\n\t ].join( '' )), '
                                • ',\n\t '
                                • ', _l([\n\t 'Click the \"Cancel\" button to exit the interface.'\n\t ].join( '' )), '
                                • ',\n\t '

                                ',\n\t '

                                ', _l([\n\t 'Once your collection is complete, enter a name and ',\n\t 'click \"Create list\".'\n\t ].join( '' )), '

                                '\n\t ].join('')),\n\t\n\t /** a simplified page communicating what went wrong and why the user needs to reselect something else */\n\t invalidInitial : _.template([\n\t '
                                ',\n\t '
                                ',\n\t '',\n\t '<% if( _.size( problems ) ){ %>',\n\t _l( 'The following selections could not be included due to problems' ),\n\t '
                                  <% _.each( problems, function( problem ){ %>',\n\t '
                                • <%- problem.element.name %>: <%- problem.text %>
                                • ',\n\t '<% }); %>
                                ',\n\t '<% } else if( _.size( elements ) === 0 ){ %>',\n\t _l( 'No datasets were selected' ), '.',\n\t '<% } else if( _.size( elements ) === 1 ){ %>',\n\t _l( 'Only one dataset was selected' ), ': <%- elements[0].name %>',\n\t '<% } else if( _.size( elements ) > 2 ){ %>',\n\t _l( 'Too many datasets were selected' ),\n\t ': <%- _.pluck( elements, \"name\" ).join( \", \") %>',\n\t '<% } %>',\n\t '
                                ',\n\t _l( 'Two (and only two) elements are needed for the pair' ), '. ',\n\t _l( 'You may need to ' ),\n\t '', _l( 'cancel' ), ' ',\n\t _l( 'and reselect new elements' ), '.',\n\t '
                                ',\n\t '
                                ',\n\t '
                                ',\n\t '
                                ',\n\t '
                                ',\n\t '
                                ',\n\t '',\n\t // _l( 'Create a different kind of collection' ),\n\t '
                                ',\n\t '
                                ',\n\t '
                                '\n\t ].join('')),\n\t }),\n\t\n\t // ------------------------------------------------------------------------ misc\n\t /** string rep */\n\t toString : function(){ return 'PairCollectionCreator'; }\n\t});\n\t\n\t\n\t//==============================================================================\n\t/** List collection flavor of collectionCreatorModal. */\n\tvar pairCollectionCreatorModal = function _pairCollectionCreatorModal( elements, options ){\n\t options = options || {};\n\t options.title = _l( 'Create a collection from a pair of datasets' );\n\t return LIST_CREATOR.collectionCreatorModal( elements, options, PairCollectionCreator );\n\t};\n\t\n\t\n\t//==============================================================================\n\t/** Use a modal to create a pair collection, then add it to the given history contents.\n\t * @returns {Deferred} resolved when the collection is added to the history.\n\t */\n\tfunction createPairCollection( contents ){\n\t var elements = contents.toJSON(),\n\t promise = pairCollectionCreatorModal( elements, {\n\t creationFn : function( elements, name ){\n\t elements = [\n\t { name: \"forward\", src: \"hda\", id: elements[0].id },\n\t { name: \"reverse\", src: \"hda\", id: elements[1].id }\n\t ];\n\t return contents.createHDCA( elements, 'paired', name );\n\t }\n\t });\n\t return promise;\n\t}\n\t\n\t//==============================================================================\n\t return {\n\t PairCollectionCreator : PairCollectionCreator,\n\t pairCollectionCreatorModal : pairCollectionCreatorModal,\n\t createPairCollection : createPairCollection,\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(2), __webpack_require__(1)))\n\n/***/ },\n/* 110 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(_, jQuery, $) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(8),\n\t __webpack_require__(78),\n\t __webpack_require__(5)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( MODAL, ERROR_MODAL, _l ){\n\t\n\t'use strict';\n\t\n\t//==============================================================================\n\t/**\n\t * A dialog/modal that allows copying a user history or 'importing' from user\n\t * another. Generally called via historyCopyDialog below.\n\t * @type {Object}\n\t */\n\tvar CopyDialog = {\n\t\n\t // language related strings/fns\n\t defaultName : _.template( \"Copy of '<%- name %>'\" ),\n\t title : _.template( _l( 'Copying history' ) + ' \"<%- name %>\"' ),\n\t submitLabel : _l( 'Copy' ),\n\t errorMessage : _l( 'History could not be copied.' ),\n\t progressive : _l( 'Copying history' ),\n\t activeLabel : _l( 'Copy only the active, non-deleted datasets' ),\n\t allLabel : _l( 'Copy all datasets including deleted ones' ),\n\t anonWarning : _l( 'As an anonymous user, unless you login or register, you will lose your current history ' ) +\n\t _l( 'after copying this history. ' ),\n\t\n\t // template for modal body\n\t _template : _.template([\n\t //TODO: remove inline styles\n\t // show a warning message for losing current to anon users\n\t '<% if( isAnon ){ %>',\n\t '
                                ',\n\t '<%- anonWarning %>',\n\t _l( 'You can' ),\n\t ' ', _l( 'login here' ), ' ', _l( 'or' ), ' ',\n\t ' ', _l( 'register here' ), '.',\n\t '
                                ',\n\t '<% } %>',\n\t '
                                ',\n\t '
                                ',\n\t // TODO: could use required here and the form validators\n\t // NOTE: use unescaped here if escaped in the modal function below\n\t '\" />',\n\t '

                                ',\n\t _l( 'Please enter a valid history title' ),\n\t '

                                ',\n\t // if allowAll, add the option to copy deleted datasets, too\n\t '<% if( allowAll ){ %>',\n\t '
                                ',\n\t '

                                ', _l( 'Choose which datasets from the original history to include:' ), '

                                ',\n\t // copy non-deleted is the default\n\t '/>',\n\t '',\n\t '
                                ',\n\t '/>',\n\t '',\n\t '<% } %>',\n\t '
                                '\n\t ].join( '' )),\n\t\n\t // empty modal body and let the user know the copy is happening\n\t _showAjaxIndicator : function _showAjaxIndicator(){\n\t var indicator = '

                                ' + this.progressive + '...

                                ';\n\t this.modal.$( '.modal-body' ).empty().append( indicator ).css({ 'margin-top': '8px' });\n\t },\n\t\n\t // (sorta) public interface - display the modal, render the form, and potentially copy the history\n\t // returns a jQuery.Deferred done->history copied, fail->user cancelled\n\t dialog : function _dialog( modal, history, options ){\n\t options = options || {};\n\t\n\t var dialog = this,\n\t deferred = jQuery.Deferred(),\n\t // TODO: getting a little byzantine here\n\t defaultCopyNameFn = options.nameFn || this.defaultName,\n\t defaultCopyName = defaultCopyNameFn({ name: history.get( 'name' ) }),\n\t // TODO: these two might be simpler as one 3 state option (all,active,no-choice)\n\t defaultCopyWhat = options.allDatasets? 'copy-all' : 'copy-non-deleted',\n\t allowAll = !_.isUndefined( options.allowAll )? options.allowAll : true,\n\t autoClose = !_.isUndefined( options.autoClose )? options.autoClose : true;\n\t\n\t this.modal = modal;\n\t\n\t\n\t // validate the name and copy if good\n\t function checkNameAndCopy(){\n\t var name = modal.$( '#copy-modal-title' ).val();\n\t if( !name ){\n\t modal.$( '.invalid-title' ).show();\n\t return;\n\t }\n\t // get further settings, shut down and indicate the ajax call, then hide and resolve/reject\n\t var copyAllDatasets = modal.$( 'input[name=\"copy-what\"]:checked' ).val() === 'copy-all';\n\t modal.$( 'button' ).prop( 'disabled', true );\n\t dialog._showAjaxIndicator();\n\t history.copy( true, name, copyAllDatasets )\n\t .done( function( response ){\n\t deferred.resolve( response );\n\t })\n\t .fail( function( xhr, status, message ){\n\t var options = { name: name, copyAllDatasets: copyAllDatasets };\n\t ERROR_MODAL.ajaxErrorModal( history, xhr, options, dialog.errorMessage );\n\t deferred.rejectWith( deferred, arguments );\n\t })\n\t .done( function(){\n\t if( autoClose ){ modal.hide(); }\n\t });\n\t }\n\t\n\t var originalClosingCallback = options.closing_callback;\n\t modal.show( _.extend( options, {\n\t title : this.title({ name: history.get( 'name' ) }),\n\t body : $( dialog._template({\n\t name : defaultCopyName,\n\t isAnon : Galaxy.user.isAnonymous(),\n\t allowAll : allowAll,\n\t copyWhat : defaultCopyWhat,\n\t activeLabel : this.activeLabel,\n\t allLabel : this.allLabel,\n\t anonWarning : this.anonWarning,\n\t })),\n\t buttons : _.object([\n\t [ _l( 'Cancel' ), function(){ modal.hide(); } ],\n\t [ this.submitLabel, checkNameAndCopy ]\n\t ]),\n\t height : 'auto',\n\t closing_events : true,\n\t closing_callback: function _historyCopyClose( cancelled ){\n\t if( cancelled ){\n\t deferred.reject({ cancelled : true });\n\t }\n\t if( originalClosingCallback ){\n\t originalClosingCallback( cancelled );\n\t }\n\t }\n\t }));\n\t\n\t // set the default dataset copy, autofocus the title, and set up for a simple return\n\t modal.$( '#copy-modal-title' ).focus().select();\n\t modal.$( '#copy-modal-title' ).on( 'keydown', function( ev ){\n\t if( ev.keyCode === 13 ){\n\t ev.preventDefault();\n\t checkNameAndCopy();\n\t }\n\t });\n\t\n\t return deferred;\n\t },\n\t};\n\t\n\t//==============================================================================\n\t// maintain the (slight) distinction between copy and import\n\t/**\n\t * Subclass CopyDialog to use the import language.\n\t */\n\tvar ImportDialog = _.extend( {}, CopyDialog, {\n\t defaultName : _.template( \"imported: <%- name %>\" ),\n\t title : _.template( _l( 'Importing history' ) + ' \"<%- name %>\"' ),\n\t submitLabel : _l( 'Import' ),\n\t errorMessage : _l( 'History could not be imported.' ),\n\t progressive : _l( 'Importing history' ),\n\t activeLabel : _l( 'Import only the active, non-deleted datasets' ),\n\t allLabel : _l( 'Import all datasets including deleted ones' ),\n\t anonWarning : _l( 'As an anonymous user, unless you login or register, you will lose your current history ' ) +\n\t _l( 'after importing this history. ' ),\n\t\n\t});\n\t\n\t//==============================================================================\n\t/**\n\t * Main interface for both history import and history copy dialogs.\n\t * @param {Backbone.Model} history the history to copy\n\t * @param {Object} options a hash\n\t * @return {jQuery.Deferred} promise that fails on close and succeeds on copy\n\t *\n\t * options:\n\t * (this object is also passed to the modal used to display the dialog and accepts modal options)\n\t * {Function} nameFn if defined, use this to build the default name shown to the user\n\t * (the fn is passed: {name: })\n\t * {bool} useImport if true, use the 'import' language (instead of Copy)\n\t * {bool} allowAll if true, allow the user to choose between copying all datasets and\n\t * only non-deleted datasets\n\t * {String} allDatasets default initial checked radio button: 'copy-all' or 'copy-non-deleted',\n\t */\n\tvar historyCopyDialog = function( history, options ){\n\t options = options || {};\n\t // create our own modal if Galaxy doesn't have one (mako tab without use_panels)\n\t var modal = window.parent.Galaxy.modal || new MODAL.View({});\n\t return options.useImport?\n\t ImportDialog.dialog( modal, history, options ):\n\t CopyDialog.dialog( modal, history, options );\n\t};\n\t\n\t\n\t//==============================================================================\n\t return historyCopyDialog;\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(2), __webpack_require__(1), __webpack_require__(1)))\n\n/***/ },\n/* 111 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(jQuery, _) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(69),\n\t __webpack_require__(71),\n\t __webpack_require__(6),\n\t __webpack_require__(5)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( DATASET_LI_EDIT, HDA_LI, BASE_MVC, _l ){\n\t\n\t'use strict';\n\t\n\t//==============================================================================\n\tvar _super = DATASET_LI_EDIT.DatasetListItemEdit;\n\t/** @class Editing view for HistoryDatasetAssociation.\n\t */\n\tvar HDAListItemEdit = _super.extend(\n\t/** @lends HDAListItemEdit.prototype */{\n\t\n\t className : _super.prototype.className + \" history-content\",\n\t\n\t /** In this override, only get details if in the ready state, get rerunnable if in other states.\n\t * Note: fetch with no 'change' event triggering to prevent automatic rendering.\n\t */\n\t _fetchModelDetails : function(){\n\t var view = this;\n\t if( view.model.inReadyState() && !view.model.hasDetails() ){\n\t return view.model.fetch({ silent: true });\n\t\n\t // special case the need for the rerunnable and creating_job attributes\n\t // needed for rendering re-run button on queued, running datasets\n\t } else if( !view.model.has( 'rerunnable' ) ){\n\t return view.model.fetch({ silent: true, data: {\n\t // only fetch rerunnable and creating_job to keep overhead down\n\t keys: [ 'rerunnable', 'creating_job' ].join(',')\n\t }});\n\t }\n\t return jQuery.when();\n\t },\n\t\n\t /** event map */\n\t events : _.extend( _.clone( _super.prototype.events ), {\n\t 'click .unhide-link' : function( ev ){ this.model.unhide(); return false; }\n\t }),\n\t\n\t /** string rep */\n\t toString : function(){\n\t var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n\t return 'HDAListItemEdit(' + modelString + ')';\n\t }\n\t});\n\t\n\t\n\t// ............................................................................ TEMPLATES\n\t/** underscore templates */\n\tHDAListItemEdit.prototype.templates = (function(){\n\t\n\t var warnings = _.extend( {}, _super.prototype.templates.warnings, {\n\t hidden : BASE_MVC.wrapTemplate([\n\t '<% if( !dataset.visible ){ %>',\n\t // add a link to unhide a dataset\n\t '
                                ',\n\t _l( 'This dataset has been hidden' ),\n\t '
                                ', _l( 'Unhide it' ), '',\n\t '
                                ',\n\t '<% } %>'\n\t ], 'dataset' )\n\t });\n\t\n\t return _.extend( {}, _super.prototype.templates, {\n\t //NOTE: *steal* the HDAListItemView titleBar\n\t titleBar : HDA_LI.HDAListItemView.prototype.templates.titleBar,\n\t warnings : warnings\n\t });\n\t}());\n\t\n\t\n\t//==============================================================================\n\t return {\n\t HDAListItemEdit : HDAListItemEdit\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(1), __webpack_require__(2)))\n\n/***/ },\n/* 112 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(73),\n\t __webpack_require__(107),\n\t __webpack_require__(22),\n\t __webpack_require__(5)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( HDCA_LI, DC_VIEW_EDIT, faIconButton, _l ){\n\t\n\t'use strict';\n\t\n\t//==============================================================================\n\tvar _super = HDCA_LI.HDCAListItemView;\n\t/** @class Editing view for HistoryDatasetCollectionAssociation.\n\t */\n\tvar HDCAListItemEdit = _super.extend(\n\t/** @lends HDCAListItemEdit.prototype */{\n\t\n\t /** logger used to record this.log messages, commonly set to console */\n\t //logger : console,\n\t\n\t /** Override to return editable versions of the collection panels */\n\t _getFoldoutPanelClass : function(){\n\t switch( this.model.get( 'collection_type' ) ){\n\t case 'list':\n\t return DC_VIEW_EDIT.ListCollectionViewEdit;\n\t case 'paired':\n\t return DC_VIEW_EDIT.PairCollectionViewEdit;\n\t case 'list:paired':\n\t return DC_VIEW_EDIT.ListOfPairsCollectionViewEdit;\n\t case 'list:list':\n\t return DC_VIEW_EDIT.ListOfListsCollectionViewEdit;\n\t }\n\t throw new TypeError( 'Uknown collection_type: ' + this.model.get( 'collection_type' ) );\n\t },\n\t\n\t // ......................................................................... delete\n\t /** In this override, add the delete button. */\n\t _renderPrimaryActions : function(){\n\t this.log( this + '._renderPrimaryActions' );\n\t // render the display, edit attr and delete icon-buttons\n\t return _super.prototype._renderPrimaryActions.call( this )\n\t .concat([\n\t this._renderDeleteButton()\n\t ]);\n\t },\n\t\n\t /** Render icon-button to delete this collection. */\n\t _renderDeleteButton : function(){\n\t var self = this,\n\t deleted = this.model.get( 'deleted' );\n\t return faIconButton({\n\t title : deleted? _l( 'Dataset collection is already deleted' ): _l( 'Delete' ),\n\t classes : 'delete-btn',\n\t faIcon : 'fa-times',\n\t disabled : deleted,\n\t onclick : function() {\n\t // ...bler... tooltips being left behind in DOM (hover out never called on deletion)\n\t self.$el.find( '.icon-btn.delete-btn' ).trigger( 'mouseout' );\n\t self.model[ 'delete' ]();\n\t }\n\t });\n\t },\n\t\n\t // ......................................................................... misc\n\t /** string rep */\n\t toString : function(){\n\t var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n\t return 'HDCAListItemEdit(' + modelString + ')';\n\t }\n\t});\n\t\n\t//==============================================================================\n\t return {\n\t HDCAListItemEdit : HDCAListItemEdit\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\n\n/***/ },\n/* 113 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(_, $, jQuery) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(75),\n\t __webpack_require__(114),\n\t __webpack_require__(6),\n\t __webpack_require__(5)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( HISTORY_MODEL, HISTORY_VIEW_EDIT, BASE_MVC, _l ){\n\t\n\t'use strict';\n\t\n\t// ============================================================================\n\t/** session storage for history panel preferences (and to maintain state)\n\t */\n\tvar HistoryViewPrefs = BASE_MVC.SessionStorageModel.extend(\n\t/** @lends HistoryViewPrefs.prototype */{\n\t defaults : {\n\t /** should the tags editor be shown or hidden initially? */\n\t tagsEditorShown : false,\n\t /** should the annotation editor be shown or hidden initially? */\n\t annotationEditorShown : false,\n\t ///** what is the currently focused content (dataset or collection) in the current history?\n\t // * (the history panel will highlight and scroll to the focused content view)\n\t // */\n\t //focusedContentId : null\n\t /** Current scroll position */\n\t scrollPosition : 0\n\t },\n\t toString : function(){\n\t return 'HistoryViewPrefs(' + JSON.stringify( this.toJSON() ) + ')';\n\t }\n\t});\n\t\n\t/** key string to store panel prefs (made accessible on class so you can access sessionStorage directly) */\n\tHistoryViewPrefs.storageKey = function storageKey(){\n\t return ( 'history-panel' );\n\t};\n\t\n\t/* =============================================================================\n\tTODO:\n\t\n\t============================================================================= */\n\tvar _super = HISTORY_VIEW_EDIT.HistoryViewEdit;\n\t// used in root/index.mako\n\t/** @class View/Controller for the user's current history model as used in the history\n\t * panel (current right hand panel) of the analysis page.\n\t *\n\t * The only history panel that:\n\t * will poll for updates.\n\t * displays datasets in reverse hid order.\n\t */\n\tvar CurrentHistoryView = _super.extend(/** @lends CurrentHistoryView.prototype */{\n\t\n\t className : _super.prototype.className + ' current-history-panel',\n\t\n\t /** override to use drilldown (and not foldout) for how collections are displayed */\n\t HDCAViewClass : _super.prototype.HDCAViewClass.extend({\n\t foldoutStyle : 'drilldown'\n\t }),\n\t\n\t emptyMsg : [\n\t _l( 'This history is empty' ), '. ',\n\t _l( 'You can ' ),\n\t '',\n\t _l( 'load your own data' ),\n\t '',\n\t _l( ' or ' ),\n\t '',\n\t _l( 'get data from an external source' ),\n\t ''\n\t ].join(''),\n\t\n\t // ......................................................................... SET UP\n\t /** Set up the view, set up storage, bind listeners to HistoryContents events */\n\t initialize : function( attributes ){\n\t attributes = attributes || {};\n\t\n\t // ---- persistent preferences\n\t /** maintain state / preferences over page loads */\n\t this.preferences = new HistoryViewPrefs( _.extend({\n\t id : HistoryViewPrefs.storageKey()\n\t }, _.pick( attributes, _.keys( HistoryViewPrefs.prototype.defaults ) )));\n\t\n\t _super.prototype.initialize.call( this, attributes );\n\t\n\t /** sub-views that will overlay this panel (collections) */\n\t this.panelStack = [];\n\t\n\t /** id of currently focused content */\n\t this.currentContentId = attributes.currentContentId || null;\n\t //NOTE: purposely not sent to localstorage since panel recreation roughly lines up with a reset of this value\n\t },\n\t\n\t /** Override to cache the current scroll position with a listener */\n\t _setUpListeners : function(){\n\t _super.prototype._setUpListeners.call( this );\n\t\n\t var panel = this;\n\t // reset scroll position when there's a new history\n\t this.on( 'new-model', function(){\n\t panel.preferences.set( 'scrollPosition', 0 );\n\t });\n\t },\n\t\n\t // ------------------------------------------------------------------------ loading history/item models\n\t // TODO: next three more appropriate moved to the app level\n\t /** (re-)loads the user's current history & contents w/ details */\n\t loadCurrentHistory : function(){\n\t return this.loadHistory( null, { url : Galaxy.root + 'history/current_history_json' });\n\t },\n\t\n\t /** loads a history & contents w/ details and makes them the current history */\n\t switchToHistory : function( historyId, attributes ){\n\t if( Galaxy.user.isAnonymous() ){\n\t this.trigger( 'error', _l( 'You must be logged in to switch histories' ), _l( 'Anonymous user' ) );\n\t return $.when();\n\t }\n\t return this.loadHistory( historyId, { url : Galaxy.root + 'history/set_as_current?id=' + historyId });\n\t },\n\t\n\t /** creates a new history on the server and sets it as the user's current history */\n\t createNewHistory : function( attributes ){\n\t if( Galaxy.user.isAnonymous() ){\n\t this.trigger( 'error', _l( 'You must be logged in to create histories' ), _l( 'Anonymous user' ) );\n\t return $.when();\n\t }\n\t return this.loadHistory( null, { url : Galaxy.root + 'history/create_new_current' });\n\t },\n\t\n\t /** release/free/shutdown old models and set up panel for new models */\n\t setModel : function( model, attributes, render ){\n\t _super.prototype.setModel.call( this, model, attributes, render );\n\t if( this.model && this.model.id ){\n\t this.log( 'checking for updates' );\n\t this.model.checkForUpdates();\n\t }\n\t return this;\n\t },\n\t\n\t // ------------------------------------------------------------------------ history/content event listening\n\t /** listening for history events */\n\t _setUpModelListeners : function(){\n\t _super.prototype._setUpModelListeners.call( this );\n\t // re-broadcast any model change events so that listeners don't have to re-bind to each history\n\t return this.listenTo( this.model, {\n\t 'change:nice_size change:size' : function(){\n\t this.trigger( 'history-size-change', this, this.model, arguments );\n\t },\n\t 'change:id' : function(){\n\t this.once( 'loading-done', function(){ this.model.checkForUpdates(); });\n\t }\n\t });\n\t },\n\t\n\t /** listening for collection events */\n\t _setUpCollectionListeners : function(){\n\t _super.prototype._setUpCollectionListeners.call( this );\n\t // if a hidden item is created (gen. by a workflow), moves thru the updater to the ready state,\n\t // then: remove it from the collection if the panel is set to NOT show hidden datasets\n\t this.listenTo( this.collection, 'state:ready', function( model, newState, oldState ){\n\t if( ( !model.get( 'visible' ) )\n\t && ( !this.collection.storage.includeHidden() ) ){\n\t this.removeItemView( model );\n\t }\n\t });\n\t },\n\t\n\t // ------------------------------------------------------------------------ panel rendering\n\t /** override to add a handler to capture the scroll position when the parent scrolls */\n\t _setUpBehaviors : function( $where ){\n\t $where = $where || this.$el;\n\t // console.log( '_setUpBehaviors', this.$scrollContainer( $where ).get(0), this.$list( $where ) );\n\t // we need to call this in _setUpBehaviors which is called after render since the $el\n\t // may not be attached to $el.parent and $scrollContainer() may not work\n\t var panel = this;\n\t _super.prototype._setUpBehaviors.call( panel, $where );\n\t\n\t // cache the handler to remove and re-add so we don't pile up the handlers\n\t if( !this._debouncedScrollCaptureHandler ){\n\t this._debouncedScrollCaptureHandler = _.debounce( function scrollCapture(){\n\t // cache the scroll position (only if visible)\n\t if( panel.$el.is( ':visible' ) ){\n\t panel.preferences.set( 'scrollPosition', $( this ).scrollTop() );\n\t }\n\t }, 40 );\n\t }\n\t\n\t panel.$scrollContainer( $where )\n\t .off( 'scroll', this._debouncedScrollCaptureHandler )\n\t .on( 'scroll', this._debouncedScrollCaptureHandler );\n\t return panel;\n\t },\n\t\n\t /** In this override, handle null models and move the search input to the top */\n\t _buildNewRender : function(){\n\t if( !this.model ){ return $(); }\n\t var $newRender = _super.prototype._buildNewRender.call( this );\n\t $newRender.find( '.search' ).prependTo( $newRender.find( '> .controls' ) );\n\t this._renderQuotaMessage( $newRender );\n\t return $newRender;\n\t },\n\t\n\t /** render the message displayed when a user is over quota and can't run jobs */\n\t _renderQuotaMessage : function( $whereTo ){\n\t $whereTo = $whereTo || this.$el;\n\t return $( this.templates.quotaMsg( {}, this ) ).prependTo( $whereTo.find( '.messages' ) );\n\t },\n\t\n\t /** In this override, get and set current panel preferences when editor is used */\n\t _renderTags : function( $where ){\n\t var panel = this;\n\t // render tags and show/hide based on preferences\n\t _super.prototype._renderTags.call( panel, $where );\n\t if( panel.preferences.get( 'tagsEditorShown' ) ){\n\t panel.tagsEditor.toggle( true );\n\t }\n\t // store preference when shown or hidden\n\t panel.listenTo( panel.tagsEditor, 'hiddenUntilActivated:shown hiddenUntilActivated:hidden',\n\t function( tagsEditor ){\n\t panel.preferences.set( 'tagsEditorShown', tagsEditor.hidden );\n\t }\n\t );\n\t },\n\t\n\t /** In this override, get and set current panel preferences when editor is used */\n\t _renderAnnotation : function( $where ){\n\t var panel = this;\n\t // render annotation and show/hide based on preferences\n\t _super.prototype._renderAnnotation.call( panel, $where );\n\t if( panel.preferences.get( 'annotationEditorShown' ) ){\n\t panel.annotationEditor.toggle( true );\n\t }\n\t // store preference when shown or hidden\n\t panel.listenTo( panel.annotationEditor, 'hiddenUntilActivated:shown hiddenUntilActivated:hidden',\n\t function( annotationEditor ){\n\t panel.preferences.set( 'annotationEditorShown', annotationEditor.hidden );\n\t }\n\t );\n\t },\n\t\n\t /** Override to scroll to cached position (in prefs) after swapping */\n\t _swapNewRender : function( $newRender ){\n\t _super.prototype._swapNewRender.call( this, $newRender );\n\t var panel = this;\n\t _.delay( function(){\n\t var pos = panel.preferences.get( 'scrollPosition' );\n\t if( pos ){\n\t panel.scrollTo( pos, 0 );\n\t }\n\t }, 10 );\n\t //TODO: is this enough of a delay on larger histories?\n\t\n\t return this;\n\t },\n\t\n\t // ------------------------------------------------------------------------ sub-views\n\t /** Override to add the current-content highlight class to currentContentId's view */\n\t _attachItems : function( $whereTo ){\n\t _super.prototype._attachItems.call( this, $whereTo );\n\t var panel = this;\n\t if( panel.currentContentId ){\n\t panel._setCurrentContentById( panel.currentContentId );\n\t }\n\t return this;\n\t },\n\t\n\t /** Override to remove any drill down panels */\n\t addItemView : function( model, collection, options ){\n\t var view = _super.prototype.addItemView.call( this, model, collection, options );\n\t if( !view ){ return view; }\n\t if( this.panelStack.length ){ return this._collapseDrilldownPanel(); }\n\t return view;\n\t },\n\t\n\t // ------------------------------------------------------------------------ collection sub-views\n\t /** In this override, add/remove expanded/collapsed model ids to/from web storage */\n\t _setUpItemViewListeners : function( view ){\n\t var panel = this;\n\t _super.prototype._setUpItemViewListeners.call( panel, view );\n\t // use pub-sub to: handle drilldown expansion and collapse\n\t return panel.listenTo( view, {\n\t 'expanded:drilldown' : function( v, drilldown ){\n\t this._expandDrilldownPanel( drilldown );\n\t },\n\t 'collapsed:drilldown' : function( v, drilldown ){\n\t this._collapseDrilldownPanel( drilldown );\n\t },\n\t });\n\t },\n\t\n\t /** display 'current content': add a visible highlight and store the id of a content item */\n\t setCurrentContent : function( view ){\n\t this.$( '.history-content.current-content' ).removeClass( 'current-content' );\n\t if( view ){\n\t view.$el.addClass( 'current-content' );\n\t this.currentContentId = view.model.id;\n\t } else {\n\t this.currentContentId = null;\n\t }\n\t },\n\t\n\t /** find the view with the id and then call setCurrentContent on it */\n\t _setCurrentContentById : function( id ){\n\t var view = this.viewFromModelId( id ) || null;\n\t this.setCurrentContent( view );\n\t },\n\t\n\t /** Handle drill down by hiding this panels list and controls and showing the sub-panel */\n\t _expandDrilldownPanel : function( drilldown ){\n\t this.panelStack.push( drilldown );\n\t // hide this panel's controls and list, set the name for back navigation, and attach to the $el\n\t this.$controls().add( this.$list() ).hide();\n\t drilldown.parentName = this.model.get( 'name' );\n\t drilldown.delegateEvents().render().$el.appendTo( this.$el );\n\t },\n\t\n\t /** Handle drilldown close by freeing the panel and re-rendering this panel */\n\t _collapseDrilldownPanel : function( drilldown ){\n\t this.panelStack.pop();\n\t //TODO: MEM: free the panel\n\t this.$controls().add( this.$list() ).show();\n\t },\n\t\n\t // ........................................................................ panel events\n\t /** event map */\n\t events : _.extend( _.clone( _super.prototype.events ), {\n\t // the two links in the empty message\n\t 'click .uploader-link' : function( ev ){ Galaxy.upload.show( ev ); },\n\t 'click .get-data-link' : function( ev ){\n\t var $toolMenu = $( '.toolMenuContainer' );\n\t $toolMenu.parent().scrollTop( 0 );\n\t $toolMenu.find( 'span:contains(\"Get Data\")' ).click();\n\t }\n\t }),\n\t\n\t // ........................................................................ external objects/MVC\n\t listenToGalaxy : function( galaxy ){\n\t this.listenTo( galaxy, {\n\t // when the galaxy_main iframe is loaded with a new page,\n\t // compare the url to the following list and if there's a match\n\t // pull the id from url and indicate in the history view that\n\t // the dataset with that id is the 'current'ly active dataset\n\t 'galaxy_main:load': function( data ){\n\t var pathToMatch = data.fullpath;\n\t var hdaId = null;\n\t var useToURLRegexMap = {\n\t 'display' : /datasets\\/([a-f0-9]+)\\/display/,\n\t 'edit' : /datasets\\/([a-f0-9]+)\\/edit/,\n\t 'report_error' : /dataset\\/errors\\?id=([a-f0-9]+)/,\n\t 'rerun' : /tool_runner\\/rerun\\?id=([a-f0-9]+)/,\n\t 'show_params' : /datasets\\/([a-f0-9]+)\\/show_params/,\n\t // no great way to do this here? (leave it in the dataset event handlers above?)\n\t // 'visualization' : 'visualization',\n\t };\n\t _.find( useToURLRegexMap, function( regex, use ){\n\t // grab the more specific match result (1), save, and use it as the find flag\n\t hdaId = _.result( pathToMatch.match( regex ), 1 );\n\t return hdaId;\n\t });\n\t // need to type mangle to go from web route to history contents\n\t this._setCurrentContentById( hdaId? ( 'dataset-' + hdaId ) : null );\n\t },\n\t // when the center panel is given a new view, clear the current indicator\n\t 'center-panel:load': function( view ){\n\t this._setCurrentContentById();\n\t }\n\t });\n\t },\n\t\n\t //TODO: remove quota meter from panel and remove this\n\t /** add listeners to an external quota meter (mvc/user/user-quotameter.js) */\n\t connectToQuotaMeter : function( quotaMeter ){\n\t if( !quotaMeter ){\n\t return this;\n\t }\n\t // show/hide the 'over quota message' in the history when the meter tells it to\n\t this.listenTo( quotaMeter, 'quota:over', this.showQuotaMessage );\n\t this.listenTo( quotaMeter, 'quota:under', this.hideQuotaMessage );\n\t\n\t // having to add this to handle re-render of hview while overquota (the above do not fire)\n\t this.on( 'rendered rendered:initial', function(){\n\t if( quotaMeter && quotaMeter.isOverQuota() ){\n\t this.showQuotaMessage();\n\t }\n\t });\n\t return this;\n\t },\n\t\n\t /** Override to preserve the quota message */\n\t clearMessages : function( ev ){\n\t var $target = !_.isUndefined( ev )?\n\t $( ev.currentTarget )\n\t :this.$messages().children( '[class$=\"message\"]' );\n\t $target = $target.not( '.quota-message' );\n\t $target.fadeOut( this.fxSpeed, function(){\n\t $( this ).remove();\n\t });\n\t return this;\n\t },\n\t\n\t /** Show the over quota message (which happens to be in the history panel).\n\t */\n\t showQuotaMessage : function(){\n\t var $msg = this.$( '.quota-message' );\n\t if( $msg.is( ':hidden' ) ){ $msg.slideDown( this.fxSpeed ); }\n\t },\n\t\n\t /** Hide the over quota message (which happens to be in the history panel).\n\t */\n\t hideQuotaMessage : function(){\n\t var $msg = this.$( '.quota-message' );\n\t if( !$msg.is( ':hidden' ) ){ $msg.slideUp( this.fxSpeed ); }\n\t },\n\t\n\t // ........................................................................ options menu\n\t //TODO: remove to batch\n\t /** unhide any hidden datasets */\n\t unhideHidden : function() {\n\t var self = this;\n\t if( confirm( _l( 'Really unhide all hidden datasets?' ) ) ){\n\t // get all hidden, regardless of deleted/purged\n\t return self.model.contents._filterAndUpdate(\n\t { visible: false, deleted: '', purged: '' },\n\t { visible : true }\n\t ).done( function(){\n\t // TODO: would be better to render these as they're unhidden instead of all at once\n\t if( !self.model.contents.includeHidden ){\n\t self.renderItems();\n\t }\n\t });\n\t }\n\t return jQuery.when();\n\t },\n\t\n\t /** delete any hidden datasets */\n\t deleteHidden : function() {\n\t var self = this;\n\t if( confirm( _l( 'Really delete all hidden datasets?' ) ) ){\n\t return self.model.contents._filterAndUpdate(\n\t // get all hidden, regardless of deleted/purged\n\t { visible: false, deleted: '', purged: '' },\n\t // both delete *and* unhide them\n\t { deleted : true, visible: true }\n\t );\n\t }\n\t return jQuery.when();\n\t },\n\t\n\t /** Return a string rep of the history */\n\t toString : function(){\n\t return 'CurrentHistoryView(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';\n\t }\n\t});\n\t\n\t\n\t//------------------------------------------------------------------------------ TEMPLATES\n\tCurrentHistoryView.prototype.templates = (function(){\n\t\n\t var quotaMsgTemplate = BASE_MVC.wrapTemplate([\n\t '
                                ',\n\t _l( 'You are over your disk quota' ), '. ',\n\t _l( 'Tool execution is on hold until your disk usage drops below your allocated quota' ), '.',\n\t '
                                '\n\t ], 'history' );\n\t return _.extend( _.clone( _super.prototype.templates ), {\n\t quotaMsg : quotaMsgTemplate\n\t });\n\t\n\t}());\n\t\n\t\n\t//==============================================================================\n\t return {\n\t CurrentHistoryView : CurrentHistoryView\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(2), __webpack_require__(1), __webpack_require__(1)))\n\n/***/ },\n/* 114 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(jQuery, _, $) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(115),\n\t __webpack_require__(40),\n\t __webpack_require__(12),\n\t __webpack_require__(72),\n\t __webpack_require__(111),\n\t __webpack_require__(112),\n\t __webpack_require__(77),\n\t __webpack_require__(66),\n\t __webpack_require__(31),\n\t __webpack_require__(109),\n\t __webpack_require__(108),\n\t __webpack_require__(22),\n\t __webpack_require__(79),\n\t __webpack_require__(6),\n\t __webpack_require__(5),\n\t __webpack_require__(15),\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function(\n\t HISTORY_VIEW,\n\t HISTORY_CONTENTS,\n\t STATES,\n\t HDA_MODEL,\n\t HDA_LI_EDIT,\n\t HDCA_LI_EDIT,\n\t TAGS,\n\t ANNOTATIONS,\n\t LIST_COLLECTION_CREATOR,\n\t PAIR_COLLECTION_CREATOR,\n\t LIST_OF_PAIRS_COLLECTION_CREATOR,\n\t faIconButton,\n\t PopupMenu,\n\t BASE_MVC,\n\t _l\n\t){\n\t\n\t'use strict';\n\t\n\t/* =============================================================================\n\tTODO:\n\t\n\t============================================================================= */\n\tvar _super = HISTORY_VIEW.HistoryView;\n\t// base class for history-view-edit-current and used as-is in history/view.mako\n\t/** @class Editable View/Controller for the history model.\n\t *\n\t * Allows:\n\t * (everything HistoryView allows)\n\t * changing the name\n\t * displaying and editing tags and annotations\n\t * multi-selection and operations on mulitple content items\n\t */\n\tvar HistoryViewEdit = _super.extend(\n\t/** @lends HistoryViewEdit.prototype */{\n\t\n\t /** class to use for constructing the HistoryDatasetAssociation views */\n\t HDAViewClass : HDA_LI_EDIT.HDAListItemEdit,\n\t /** class to use for constructing the HistoryDatasetCollectionAssociation views */\n\t HDCAViewClass : HDCA_LI_EDIT.HDCAListItemEdit,\n\t\n\t // ......................................................................... SET UP\n\t /** Set up the view, set up storage, bind listeners to HistoryContents events\n\t * @param {Object} attributes\n\t */\n\t initialize : function( attributes ){\n\t attributes = attributes || {};\n\t _super.prototype.initialize.call( this, attributes );\n\t\n\t // ---- set up instance vars\n\t /** editor for tags - sub-view */\n\t this.tagsEditor = null;\n\t /** editor for annotations - sub-view */\n\t this.annotationEditor = null;\n\t\n\t /** allow user purge of dataset files? */\n\t this.purgeAllowed = attributes.purgeAllowed || false;\n\t\n\t // states/modes the panel can be in\n\t /** is the panel currently showing the dataset selection controls? */\n\t this.annotationEditorShown = attributes.annotationEditorShown || false;\n\t this.tagsEditorShown = attributes.tagsEditorShown || false;\n\t },\n\t\n\t /** Override to handle history as drag-drop target */\n\t _setUpListeners : function(){\n\t _super.prototype._setUpListeners.call( this );\n\t return this.on({\n\t 'droptarget:drop': function( ev, data ){\n\t // process whatever was dropped and re-hide the drop target\n\t this.dataDropped( data );\n\t this.dropTargetOff();\n\t },\n\t 'view:attached view:removed': function(){\n\t this._renderCounts();\n\t },\n\t 'search:loading-progress': this._renderSearchProgress,\n\t 'search:searching': this._renderSearchFindings,\n\t });\n\t },\n\t\n\t // ------------------------------------------------------------------------ listeners\n\t /** listening for history and HDA events */\n\t _setUpModelListeners : function(){\n\t _super.prototype._setUpModelListeners.call( this );\n\t this.listenTo( this.model, 'change:size', this.updateHistoryDiskSize );\n\t return this;\n\t },\n\t\n\t /** listening for collection events */\n\t _setUpCollectionListeners : function(){\n\t _super.prototype._setUpCollectionListeners.call( this );\n\t this.listenTo( this.collection, {\n\t 'change:deleted': this._handleItemDeletedChange,\n\t 'change:visible': this._handleItemVisibleChange,\n\t 'change:purged' : function( model ){\n\t // hafta get the new nice-size w/o the purged model\n\t this.model.fetch();\n\t },\n\t // loading indicators for deleted/hidden\n\t 'fetching-deleted' : function( collection ){\n\t this.$( '> .controls .deleted-count' )\n\t .html( '' + _l( 'loading...' ) + '' );\n\t },\n\t 'fetching-hidden' : function( collection ){\n\t this.$( '> .controls .hidden-count' )\n\t .html( '' + _l( 'loading...' ) + '' );\n\t },\n\t 'fetching-deleted-done fetching-hidden-done' : this._renderCounts,\n\t });\n\t return this;\n\t },\n\t\n\t // ------------------------------------------------------------------------ panel rendering\n\t /** In this override, add tag and annotation editors and a btn to toggle the selectors */\n\t _buildNewRender : function(){\n\t // create a new render using a skeleton template, render title buttons, render body, and set up events, etc.\n\t var $newRender = _super.prototype._buildNewRender.call( this );\n\t if( !this.model ){ return $newRender; }\n\t\n\t if( Galaxy && Galaxy.user && Galaxy.user.id && Galaxy.user.id === this.model.get( 'user_id' ) ){\n\t this._renderTags( $newRender );\n\t this._renderAnnotation( $newRender );\n\t }\n\t return $newRender;\n\t },\n\t\n\t /** Update the history size display (curr. upper right of panel). */\n\t updateHistoryDiskSize : function(){\n\t this.$( '.history-size' ).text( this.model.get( 'nice_size' ) );\n\t },\n\t\n\t /** override to render counts when the items are rendered */\n\t renderItems : function( $whereTo ){\n\t var views = _super.prototype.renderItems.call( this, $whereTo );\n\t if( !this.searchFor ){ this._renderCounts( $whereTo ); }\n\t return views;\n\t },\n\t\n\t /** override to show counts, what's deleted/hidden, and links to toggle those */\n\t _renderCounts : function( $whereTo ){\n\t $whereTo = $whereTo instanceof jQuery? $whereTo : this.$el;\n\t var html = this.templates.counts( this.model.toJSON(), this );\n\t return $whereTo.find( '> .controls .subtitle' ).html( html );\n\t },\n\t\n\t /** render the tags sub-view controller */\n\t _renderTags : function( $where ){\n\t var panel = this;\n\t this.tagsEditor = new TAGS.TagsEditor({\n\t model : this.model,\n\t el : $where.find( '.controls .tags-display' ),\n\t onshowFirstTime : function(){ this.render(); },\n\t // show hide sub-view tag editors when this is shown/hidden\n\t onshow : function(){\n\t panel.toggleHDATagEditors( true, panel.fxSpeed );\n\t },\n\t onhide : function(){\n\t panel.toggleHDATagEditors( false, panel.fxSpeed );\n\t },\n\t $activator : faIconButton({\n\t title : _l( 'Edit history tags' ),\n\t classes : 'history-tag-btn',\n\t faIcon : 'fa-tags'\n\t }).appendTo( $where.find( '.controls .actions' ) )\n\t });\n\t },\n\t /** render the annotation sub-view controller */\n\t _renderAnnotation : function( $where ){\n\t var panel = this;\n\t this.annotationEditor = new ANNOTATIONS.AnnotationEditor({\n\t model : this.model,\n\t el : $where.find( '.controls .annotation-display' ),\n\t onshowFirstTime : function(){ this.render(); },\n\t // show hide sub-view view annotation editors when this is shown/hidden\n\t onshow : function(){\n\t panel.toggleHDAAnnotationEditors( true, panel.fxSpeed );\n\t },\n\t onhide : function(){\n\t panel.toggleHDAAnnotationEditors( false, panel.fxSpeed );\n\t },\n\t $activator : faIconButton({\n\t title : _l( 'Edit history annotation' ),\n\t classes : 'history-annotate-btn',\n\t faIcon : 'fa-comment'\n\t }).appendTo( $where.find( '.controls .actions' ) )\n\t });\n\t },\n\t\n\t /** Set up HistoryViewEdit js/widget behaviours\n\t * In this override, make the name editable\n\t */\n\t _setUpBehaviors : function( $where ){\n\t $where = $where || this.$el;\n\t _super.prototype._setUpBehaviors.call( this, $where );\n\t if( !this.model ){ return; }\n\t\n\t // anon users shouldn't have access to any of the following\n\t if( ( !Galaxy.user || Galaxy.user.isAnonymous() )\n\t || ( Galaxy.user.id !== this.model.get( 'user_id' ) ) ){\n\t return;\n\t }\n\t\n\t var panel = this,\n\t nameSelector = '> .controls .name';\n\t $where.find( nameSelector )\n\t .attr( 'title', _l( 'Click to rename history' ) )\n\t .tooltip({ placement: 'bottom' })\n\t .make_text_editable({\n\t on_finish: function( newName ){\n\t var previousName = panel.model.get( 'name' );\n\t if( newName && newName !== previousName ){\n\t panel.$el.find( nameSelector ).text( newName );\n\t panel.model.save({ name: newName })\n\t .fail( function(){\n\t panel.$el.find( nameSelector ).text( panel.model.previous( 'name' ) );\n\t });\n\t } else {\n\t panel.$el.find( nameSelector ).text( previousName );\n\t }\n\t }\n\t });\n\t },\n\t\n\t /** return a new popup menu for choosing a multi selection action\n\t * ajax calls made for multiple datasets are queued\n\t */\n\t multiselectActions : function(){\n\t var panel = this,\n\t actions = [\n\t { html: _l( 'Hide datasets' ), func: function(){\n\t var action = HDA_MODEL.HistoryDatasetAssociation.prototype.hide;\n\t panel.getSelectedModels().ajaxQueue( action );\n\t }\n\t },\n\t { html: _l( 'Unhide datasets' ), func: function(){\n\t var action = HDA_MODEL.HistoryDatasetAssociation.prototype.unhide;\n\t panel.getSelectedModels().ajaxQueue( action );\n\t }\n\t },\n\t { html: _l( 'Delete datasets' ), func: function(){\n\t var action = HDA_MODEL.HistoryDatasetAssociation.prototype['delete'];\n\t panel.getSelectedModels().ajaxQueue( action );\n\t }\n\t },\n\t { html: _l( 'Undelete datasets' ), func: function(){\n\t var action = HDA_MODEL.HistoryDatasetAssociation.prototype.undelete;\n\t panel.getSelectedModels().ajaxQueue( action );\n\t }\n\t }\n\t ];\n\t if( panel.purgeAllowed ){\n\t actions.push({\n\t html: _l( 'Permanently delete datasets' ), func: function(){\n\t if( confirm( _l( 'This will permanently remove the data in your datasets. Are you sure?' ) ) ){\n\t var action = HDA_MODEL.HistoryDatasetAssociation.prototype.purge;\n\t panel.getSelectedModels().ajaxQueue( action );\n\t }\n\t }\n\t });\n\t }\n\t actions = actions.concat( panel._collectionActions() );\n\t return actions;\n\t },\n\t\n\t /** */\n\t _collectionActions : function(){\n\t var panel = this;\n\t return [\n\t { html: _l( 'Build Dataset List' ), func: function() {\n\t LIST_COLLECTION_CREATOR.createListCollection( panel.getSelectedModels() )\n\t .done( function(){ panel.model.refresh(); });\n\t }\n\t },\n\t // TODO: Only show quick pair if two things selected.\n\t { html: _l( 'Build Dataset Pair' ), func: function() {\n\t PAIR_COLLECTION_CREATOR.createPairCollection( panel.getSelectedModels() )\n\t .done( function(){ panel.model.refresh(); });\n\t }\n\t },\n\t { html: _l( 'Build List of Dataset Pairs' ), func: function() {\n\t LIST_OF_PAIRS_COLLECTION_CREATOR.createListOfPairsCollection( panel.getSelectedModels() )\n\t .done( function(){ panel.model.refresh(); });\n\t }\n\t },\n\t ];\n\t },\n\t\n\t // ------------------------------------------------------------------------ sub-views\n\t /** In this override, add purgeAllowed and whether tags/annotation editors should be shown */\n\t _getItemViewOptions : function( model ){\n\t var options = _super.prototype._getItemViewOptions.call( this, model );\n\t _.extend( options, {\n\t purgeAllowed : this.purgeAllowed,\n\t tagsEditorShown : ( this.tagsEditor && !this.tagsEditor.hidden ),\n\t annotationEditorShown : ( this.annotationEditor && !this.annotationEditor.hidden )\n\t });\n\t return options;\n\t },\n\t\n\t /** If this item is deleted and we're not showing deleted items, remove the view\n\t * @param {Model} the item model to check\n\t */\n\t _handleItemDeletedChange : function( itemModel ){\n\t if( itemModel.get( 'deleted' ) ){\n\t this._handleItemDeletion( itemModel );\n\t } else {\n\t this._handleItemUndeletion( itemModel );\n\t }\n\t this._renderCounts();\n\t },\n\t\n\t _handleItemDeletion : function( itemModel ){\n\t var contentsShown = this.model.get( 'contents_active' );\n\t contentsShown.deleted += 1;\n\t contentsShown.active -= 1;\n\t if( !this.model.contents.includeDeleted ){\n\t this.removeItemView( itemModel );\n\t }\n\t this.model.set( 'contents_active', contentsShown );\n\t },\n\t\n\t _handleItemUndeletion : function( itemModel ){\n\t var contentsShown = this.model.get( 'contents_active' );\n\t contentsShown.deleted -= 1;\n\t if( !this.model.contents.includeDeleted ){\n\t contentsShown.active -= 1;\n\t }\n\t this.model.set( 'contents_active', contentsShown );\n\t },\n\t\n\t /** If this item is hidden and we're not showing hidden items, remove the view\n\t * @param {Model} the item model to check\n\t */\n\t _handleItemVisibleChange : function( itemModel ){\n\t if( itemModel.hidden() ){\n\t this._handleItemHidden( itemModel );\n\t } else {\n\t this._handleItemUnhidden( itemModel );\n\t }\n\t this._renderCounts();\n\t },\n\t\n\t _handleItemHidden : function( itemModel ){\n\t var contentsShown = this.model.get( 'contents_active' );\n\t contentsShown.hidden += 1;\n\t contentsShown.active -= 1;\n\t if( !this.model.contents.includeHidden ){\n\t this.removeItemView( itemModel );\n\t }\n\t this.model.set( 'contents_active', contentsShown );\n\t },\n\t\n\t _handleItemUnhidden : function( itemModel ){\n\t var contentsShown = this.model.get( 'contents_active' );\n\t contentsShown.hidden -= 1;\n\t if( !this.model.contents.includeHidden ){\n\t contentsShown.active -= 1;\n\t }\n\t this.model.set( 'contents_active', contentsShown );\n\t },\n\t\n\t /** toggle the visibility of each content's tagsEditor applying all the args sent to this function */\n\t toggleHDATagEditors : function( showOrHide, speed ){\n\t _.each( this.views, function( view ){\n\t if( view.tagsEditor ){\n\t view.tagsEditor.toggle( showOrHide, speed );\n\t }\n\t });\n\t },\n\t\n\t /** toggle the visibility of each content's annotationEditor applying all the args sent to this function */\n\t toggleHDAAnnotationEditors : function( showOrHide, speed ){\n\t _.each( this.views, function( view ){\n\t if( view.annotationEditor ){\n\t view.annotationEditor.toggle( showOrHide, speed );\n\t }\n\t });\n\t },\n\t\n\t // ------------------------------------------------------------------------ panel events\n\t /** event map */\n\t events : _.extend( _.clone( _super.prototype.events ), {\n\t 'click .show-selectors-btn' : 'toggleSelectors',\n\t 'click .toggle-deleted-link' : function( ev ){ this.toggleShowDeleted(); },\n\t 'click .toggle-hidden-link' : function( ev ){ this.toggleShowHidden(); }\n\t }),\n\t\n\t // ------------------------------------------------------------------------ search\n\t _renderSearchProgress : function( limit, offset ){\n\t var stop = limit + offset;\n\t return this.$( '> .controls .subtitle' ).html([\n\t '',\n\t _l( 'Searching ' ), stop, '/', this.model.contentsShown(),\n\t ''\n\t ].join(''));\n\t },\n\t\n\t /** override to display number found in subtitle */\n\t _renderSearchFindings : function(){\n\t this.$( '> .controls .subtitle' ).html([\n\t _l( 'Found' ), this.views.length\n\t ].join(' '));\n\t return this;\n\t },\n\t\n\t // ------------------------------------------------------------------------ as drop target\n\t /** turn all the drag and drop handlers on and add some help text above the drop area */\n\t dropTargetOn : function(){\n\t if( this.dropTarget ){ return this; }\n\t this.dropTarget = true;\n\t\n\t //TODO: to init\n\t var dropHandlers = {\n\t 'dragenter' : _.bind( this.dragenter, this ),\n\t 'dragover' : _.bind( this.dragover, this ),\n\t 'dragleave' : _.bind( this.dragleave, this ),\n\t 'drop' : _.bind( this.drop, this )\n\t };\n\t\n\t var $dropTarget = this._renderDropTarget();\n\t this.$list().before([ this._renderDropTargetHelp(), $dropTarget ]);\n\t for( var evName in dropHandlers ){\n\t if( dropHandlers.hasOwnProperty( evName ) ){\n\t //console.debug( evName, dropHandlers[ evName ] );\n\t $dropTarget.on( evName, dropHandlers[ evName ] );\n\t }\n\t }\n\t return this;\n\t },\n\t\n\t /** render a box to serve as a 'drop here' area on the history */\n\t _renderDropTarget : function(){\n\t this.$( '.history-drop-target' ).remove();\n\t return $( '
                                ' ).addClass( 'history-drop-target' );\n\t },\n\t\n\t /** tell the user how it works */\n\t _renderDropTargetHelp : function(){\n\t this.$( '.history-drop-target-help' ).remove();\n\t return $( '
                                ' ).addClass( 'history-drop-target-help' )\n\t .text( _l( 'Drag datasets here to copy them to the current history' ) );\n\t },\n\t\n\t /** shut down drag and drop event handlers and remove drop target */\n\t dropTargetOff : function(){\n\t if( !this.dropTarget ){ return this; }\n\t //this.log( 'dropTargetOff' );\n\t this.dropTarget = false;\n\t var dropTarget = this.$( '.history-drop-target' ).get(0);\n\t for( var evName in this._dropHandlers ){\n\t if( this._dropHandlers.hasOwnProperty( evName ) ){\n\t dropTarget.off( evName, this._dropHandlers[ evName ] );\n\t }\n\t }\n\t this.$( '.history-drop-target' ).remove();\n\t this.$( '.history-drop-target-help' ).remove();\n\t return this;\n\t },\n\t /** toggle the target on/off */\n\t dropTargetToggle : function(){\n\t if( this.dropTarget ){\n\t this.dropTargetOff();\n\t } else {\n\t this.dropTargetOn();\n\t }\n\t return this;\n\t },\n\t\n\t dragenter : function( ev ){\n\t //console.debug( 'dragenter:', this, ev );\n\t ev.preventDefault();\n\t ev.stopPropagation();\n\t this.$( '.history-drop-target' ).css( 'border', '2px solid black' );\n\t },\n\t dragover : function( ev ){\n\t ev.preventDefault();\n\t ev.stopPropagation();\n\t },\n\t dragleave : function( ev ){\n\t //console.debug( 'dragleave:', this, ev );\n\t ev.preventDefault();\n\t ev.stopPropagation();\n\t this.$( '.history-drop-target' ).css( 'border', '1px dashed black' );\n\t },\n\t /** when (text) is dropped try to parse as json and trigger an event */\n\t drop : function( ev ){\n\t ev.preventDefault();\n\t //ev.stopPropagation();\n\t\n\t var self = this;\n\t var dataTransfer = ev.originalEvent.dataTransfer;\n\t var data = dataTransfer.getData( \"text\" );\n\t\n\t dataTransfer.dropEffect = 'move';\n\t try {\n\t data = JSON.parse( data );\n\t } catch( err ){\n\t self.warn( 'error parsing JSON from drop:', data );\n\t }\n\t\n\t self.trigger( 'droptarget:drop', ev, data, self );\n\t return false;\n\t },\n\t\n\t /** handler that copies data into the contents */\n\t dataDropped : function( data ){\n\t var self = this;\n\t // HDA: dropping will copy it to the history\n\t if( _.isObject( data ) && data.model_class === 'HistoryDatasetAssociation' && data.id ){\n\t if( self.contents.currentPage !== 0 ){\n\t return self.contents.fetchPage( 0 )\n\t .then( function(){\n\t return self.model.contents.copy( data.id );\n\t });\n\t }\n\t return self.model.contents.copy( data.id );\n\t }\n\t return jQuery.when();\n\t },\n\t\n\t // ........................................................................ misc\n\t /** Return a string rep of the history */\n\t toString : function(){\n\t return 'HistoryViewEdit(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';\n\t }\n\t});\n\t\n\t//------------------------------------------------------------------------------ TEMPLATES\n\tHistoryViewEdit.prototype.templates = (function(){\n\t\n\t var countsTemplate = BASE_MVC.wrapTemplate([\n\t '<% var shown = Math.max( view.views.length, history.contents_active.active ) %>',\n\t '<% if( shown ){ %>',\n\t '',\n\t '<%- shown %> ', _l( 'shown' ),\n\t '',\n\t '<% } %>',\n\t\n\t '<% if( history.contents_active.deleted ){ %>',\n\t '',\n\t '<% if( view.model.contents.includeDeleted ){ %>',\n\t '',\n\t _l( 'hide deleted' ),\n\t '',\n\t '<% } else { %>',\n\t '<%- history.contents_active.deleted %> ',\n\t '',\n\t _l( 'deleted' ),\n\t '',\n\t '<% } %>',\n\t '',\n\t '<% } %>',\n\t\n\t '<% if( history.contents_active.hidden ){ %>',\n\t '',\n\t '<% if( view.model.contents.includeHidden ){ %>',\n\t '',\n\t _l( 'hide hidden' ),\n\t '',\n\t '<% } else { %>',\n\t '<%- history.contents_active.hidden %> ',\n\t '',\n\t _l( 'hidden' ),\n\t '',\n\t '<% } %>',\n\t '',\n\t '<% } %>',\n\t ], 'history' );\n\t\n\t return _.extend( _.clone( _super.prototype.templates ), {\n\t counts : countsTemplate\n\t });\n\t}());\n\t\n\t\n\t//==============================================================================\n\t return {\n\t HistoryViewEdit : HistoryViewEdit\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(1), __webpack_require__(2), __webpack_require__(1)))\n\n/***/ },\n/* 115 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(_, $) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(76),\n\t __webpack_require__(75),\n\t __webpack_require__(40),\n\t __webpack_require__(41),\n\t __webpack_require__(71),\n\t __webpack_require__(73),\n\t __webpack_require__(82),\n\t __webpack_require__(78),\n\t __webpack_require__(22),\n\t __webpack_require__(6),\n\t __webpack_require__(5),\n\t __webpack_require__(85)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function(\n\t LIST_VIEW,\n\t HISTORY_MODEL,\n\t HISTORY_CONTENTS,\n\t HISTORY_PREFS,\n\t HDA_LI,\n\t HDCA_LI,\n\t USER,\n\t ERROR_MODAL,\n\t faIconButton,\n\t BASE_MVC,\n\t _l\n\t){\n\t'use strict';\n\t\n\t/* =============================================================================\n\tTODO:\n\t\n\t============================================================================= */\n\t/** @class non-editable, read-only View/Controller for a history model.\n\t * Allows:\n\t * changing the loaded history\n\t * displaying data, info, and download\n\t * tracking history attrs: size, tags, annotations, name, etc.\n\t * Does not allow:\n\t * changing the name\n\t */\n\tvar _super = LIST_VIEW.ModelListPanel;\n\tvar HistoryView = _super.extend(\n\t/** @lends HistoryView.prototype */{\n\t _logNamespace : 'history',\n\t\n\t /** class to use for constructing the HDA views */\n\t HDAViewClass : HDA_LI.HDAListItemView,\n\t /** class to use for constructing the HDCA views */\n\t HDCAViewClass : HDCA_LI.HDCAListItemView,\n\t /** class to used for constructing collection of sub-view models */\n\t collectionClass : HISTORY_CONTENTS.HistoryContents,\n\t /** key of attribute in model to assign to this.collection */\n\t modelCollectionKey : 'contents',\n\t\n\t tagName : 'div',\n\t className : _super.prototype.className + ' history-panel',\n\t\n\t /** string to display when the collection is empty */\n\t emptyMsg : _l( 'This history is empty' ),\n\t /** displayed when no items match the search terms */\n\t noneFoundMsg : _l( 'No matching datasets found' ),\n\t /** string used for search placeholder */\n\t searchPlaceholder : _l( 'search datasets' ),\n\t\n\t /** @type {Number} ms to wait after history load to fetch/decorate hdcas with element_count */\n\t FETCH_COLLECTION_COUNTS_DELAY : 2000,\n\t\n\t // ......................................................................... SET UP\n\t /** Set up the view, bind listeners.\n\t * @param {Object} attributes optional settings for the panel\n\t */\n\t initialize : function( attributes ){\n\t _super.prototype.initialize.call( this, attributes );\n\t // ---- instance vars\n\t // control contents/behavior based on where (and in what context) the panel is being used\n\t /** where should pages from links be displayed? (default to new tab/window) */\n\t this.linkTarget = attributes.linkTarget || '_blank';\n\t },\n\t\n\t /** create and return a collection for when none is initially passed */\n\t _createDefaultCollection : function(){\n\t // override\n\t return new this.collectionClass([], { history: this.model });\n\t },\n\t\n\t /** In this override, clear the update timer on the model */\n\t freeModel : function(){\n\t _super.prototype.freeModel.call( this );\n\t if( this.model ){\n\t this.model.clearUpdateTimeout();\n\t }\n\t return this;\n\t },\n\t\n\t /** create any event listeners for the panel\n\t * @fires: rendered:initial on the first render\n\t * @fires: empty-history when switching to a history with no contents or creating a new history\n\t */\n\t _setUpListeners : function(){\n\t _super.prototype._setUpListeners.call( this );\n\t this.on({\n\t error : function( model, xhr, options, msg, details ){\n\t this.errorHandler( model, xhr, options, msg, details );\n\t },\n\t 'loading-done' : function(){\n\t var self = this;\n\t // after the initial load, decorate with more time consuming fields (like HDCA element_counts)\n\t _.delay( function(){\n\t self.model.contents.fetchCollectionCounts();\n\t }, self.FETCH_COLLECTION_COUNTS_DELAY );\n\t },\n\t 'views:ready view:attached view:removed' : function( view ){\n\t this._renderSelectButton();\n\t },\n\t 'view:attached' : function( view ){\n\t this.scrollTo(0);\n\t },\n\t });\n\t // this.on( 'all', function(){ console.debug( arguments ); });\n\t },\n\t\n\t // ------------------------------------------------------------------------ loading history/hda models\n\t /** load the history with the given id then it's contents, sending ajax options to both */\n\t loadHistory : function( historyId, options, contentsOptions ){\n\t contentsOptions = _.extend( contentsOptions || { silent: true });\n\t this.info( 'loadHistory:', historyId, options, contentsOptions );\n\t var self = this;\n\t self.setModel( new HISTORY_MODEL.History({ id : historyId }) );\n\t\n\t contentsOptions.silent = true;\n\t self.trigger( 'loading' );\n\t return self.model\n\t .fetchWithContents( options, contentsOptions )\n\t .always( function(){\n\t self.render();\n\t self.trigger( 'loading-done' );\n\t });\n\t },\n\t\n\t /** convenience alias to the model. Updates the item list only (not the history) */\n\t refreshContents : function( options ){\n\t if( this.model ){\n\t return this.model.refresh( options );\n\t }\n\t // may have callbacks - so return an empty promise\n\t return $.when();\n\t },\n\t\n\t /** Override to reset web storage when the id changes (since it needs the id) */\n\t _setUpCollectionListeners : function(){\n\t _super.prototype._setUpCollectionListeners.call( this );\n\t return this.listenTo( this.collection, {\n\t // 'all' : function(){ console.log( this.collection + ':', arguments ); },\n\t 'fetching-more' : function(){\n\t this._toggleContentsLoadingIndicator( true );\n\t this.$emptyMessage().hide();\n\t },\n\t 'fetching-more-done': function(){ this._toggleContentsLoadingIndicator( false ); },\n\t });\n\t },\n\t\n\t // ------------------------------------------------------------------------ panel rendering\n\t /** hide the $el and display a loading indicator (in the $el's parent) when loading new data */\n\t _showLoadingIndicator : function( msg, speed, callback ){\n\t var $indicator = $( '
                                ' );\n\t this.$el.html( $indicator.text( msg ).slideDown( !_.isUndefined( speed )? speed : this.fxSpeed ) );\n\t },\n\t\n\t /** hide the loading indicator */\n\t _hideLoadingIndicator : function( speed ){\n\t // make speed a bit slower to compensate for slow rendering of up to 500 contents\n\t this.$( '.loading-indicator' ).slideUp( !_.isUndefined( speed )? speed : ( this.fxSpeed + 200 ), function(){\n\t $( this ).remove();\n\t });\n\t },\n\t\n\t /** In this override, add a btn to toggle the selectors */\n\t _buildNewRender : function(){\n\t var $newRender = _super.prototype._buildNewRender.call( this );\n\t this._renderSelectButton( $newRender );\n\t return $newRender;\n\t },\n\t\n\t /** button for starting select mode */\n\t _renderSelectButton : function( $where ){\n\t $where = $where || this.$el;\n\t // do not render selector option if no actions\n\t if( !this.multiselectActions().length ){\n\t return null;\n\t }\n\t // do not render (and remove even) if nothing to select\n\t if( !this.views.length ){\n\t this.hideSelectors();\n\t $where.find( '.controls .actions .show-selectors-btn' ).remove();\n\t return null;\n\t }\n\t // don't bother rendering if there's one already\n\t var $existing = $where.find( '.controls .actions .show-selectors-btn' );\n\t if( $existing.length ){\n\t return $existing;\n\t }\n\t\n\t return faIconButton({\n\t title : _l( 'Operations on multiple datasets' ),\n\t classes : 'show-selectors-btn',\n\t faIcon : 'fa-check-square-o'\n\t }).prependTo( $where.find( '.controls .actions' ) );\n\t },\n\t\n\t /** override to avoid showing intial empty message using contents_active */\n\t _renderEmptyMessage : function( $whereTo ){\n\t var self = this;\n\t var $emptyMsg = self.$emptyMessage( $whereTo );\n\t\n\t var empty = self.model.get( 'contents_active' ).active <= 0;\n\t if( empty ){\n\t return $emptyMsg.empty().append( self.emptyMsg ).show();\n\t\n\t } else if( self.searchFor && self.model.contents.haveSearchDetails() && !self.views.length ){\n\t return $emptyMsg.empty().append( self.noneFoundMsg ).show();\n\t }\n\t $emptyMsg.hide();\n\t return $();\n\t },\n\t\n\t /** the scroll container for this panel - can be $el, $el.parent(), or grandparent depending on context */\n\t $scrollContainer : function( $where ){\n\t // override or set via attributes.$scrollContainer\n\t return this.$list( $where );\n\t },\n\t\n\t // ------------------------------------------------------------------------ subviews\n\t _toggleContentsLoadingIndicator : function( show ){\n\t if( !show ){\n\t this.$list().find( '.contents-loading-indicator' ).remove();\n\t } else {\n\t this.$list().html( '
                                '\n\t + '
                                ' );\n\t }\n\t },\n\t\n\t /** override to render pagination also */\n\t renderItems: function( $whereTo ){\n\t // console.log( this + '.renderItems-----------------', new Date() );\n\t $whereTo = $whereTo || this.$el;\n\t var self = this;\n\t var $list = self.$list( $whereTo );\n\t\n\t // TODO: bootstrap hack to remove orphaned tooltips\n\t $( '.tooltip' ).remove();\n\t\n\t $list.empty();\n\t self.views = [];\n\t\n\t var models = self._filterCollection();\n\t if( models.length ){\n\t self._renderPagination( $whereTo );\n\t self.views = self._renderSomeItems( models, $list );\n\t } else {\n\t // TODO: consolidate with _renderPagination above by (???) passing in models/length?\n\t $whereTo.find( '> .controls .list-pagination' ).empty();\n\t }\n\t self._renderEmptyMessage( $whereTo ).toggle( !models.length );\n\t\n\t self.trigger( 'views:ready', self.views );\n\t return self.views;\n\t },\n\t\n\t /** render pagination controls if not searching and contents says we're paginating */\n\t _renderPagination: function( $whereTo ){\n\t var $paginationControls = $whereTo.find( '> .controls .list-pagination' );\n\t if( this.searchFor || !this.model.contents.shouldPaginate() ) return $paginationControls.empty();\n\t\n\t $paginationControls.html( this.templates.pagination({\n\t // pagination is 1-based for the user\n\t current : this.model.contents.currentPage + 1,\n\t last : this.model.contents.getLastPage() + 1,\n\t }, this ));\n\t $paginationControls.find( 'select.pages' ).tooltip();\n\t return $paginationControls;\n\t },\n\t\n\t /** render a subset of the entire collection (client-side pagination) */\n\t _renderSomeItems: function( models, $list ){\n\t var self = this;\n\t var views = [];\n\t $list.append( models.map( function( m ){\n\t var view = self._createItemView( m );\n\t views.push( view );\n\t return self._renderItemView$el( view );\n\t }));\n\t return views;\n\t },\n\t\n\t // ------------------------------------------------------------------------ sub-views\n\t /** in this override, check if the contents would also display based on includeDeleted/hidden */\n\t _filterItem : function( model ){\n\t var self = this;\n\t var contents = self.model.contents;\n\t return ( contents.includeHidden || !model.hidden() )\n\t && ( contents.includeDeleted || !model.isDeletedOrPurged() )\n\t && ( _super.prototype._filterItem.call( self, model ) );\n\t },\n\t\n\t /** In this override, since history contents are mixed,\n\t * get the appropo view class based on history_content_type\n\t */\n\t _getItemViewClass : function( model ){\n\t var contentType = model.get( \"history_content_type\" );\n\t switch( contentType ){\n\t case 'dataset':\n\t return this.HDAViewClass;\n\t case 'dataset_collection':\n\t return this.HDCAViewClass;\n\t }\n\t throw new TypeError( 'Unknown history_content_type: ' + contentType );\n\t },\n\t\n\t /** in this override, add a linktarget, and expand if id is in web storage */\n\t _getItemViewOptions : function( model ){\n\t var options = _super.prototype._getItemViewOptions.call( this, model );\n\t return _.extend( options, {\n\t linkTarget : this.linkTarget,\n\t expanded : this.model.contents.storage.isExpanded( model.id ),\n\t hasUser : this.model.ownedByCurrUser()\n\t });\n\t },\n\t\n\t /** In this override, add/remove expanded/collapsed model ids to/from web storage */\n\t _setUpItemViewListeners : function( view ){\n\t var panel = this;\n\t _super.prototype._setUpItemViewListeners.call( panel, view );\n\t //TODO: send from content view: this.model.collection.storage.addExpanded\n\t // maintain a list of items whose bodies are expanded\n\t return panel.listenTo( view, {\n\t 'expanded': function( v ){\n\t panel.model.contents.storage.addExpanded( v.model );\n\t },\n\t 'collapsed': function( v ){\n\t panel.model.contents.storage.removeExpanded( v.model );\n\t }\n\t });\n\t },\n\t\n\t /** override to remove expandedIds from webstorage */\n\t collapseAll : function(){\n\t this.model.contents.storage.clearExpanded();\n\t _super.prototype.collapseAll.call( this );\n\t },\n\t\n\t // ------------------------------------------------------------------------ selection\n\t /** Override to correctly set the historyId of the new collection */\n\t getSelectedModels : function(){\n\t var collection = _super.prototype.getSelectedModels.call( this );\n\t collection.historyId = this.collection.historyId;\n\t return collection;\n\t },\n\t\n\t\n\t // ------------------------------------------------------------------------ panel events\n\t /** event map */\n\t events : _.extend( _.clone( _super.prototype.events ), {\n\t 'click .show-selectors-btn' : 'toggleSelectors',\n\t 'click > .controls .prev' : '_clickPrevPage',\n\t 'click > .controls .next' : '_clickNextPage',\n\t 'change > .controls .pages' : '_changePageSelect',\n\t // allow (error) messages to be clicked away\n\t 'click .messages [class$=message]' : 'clearMessages',\n\t }),\n\t\n\t _clickPrevPage : function( ev ){\n\t this.model.contents.fetchPrevPage();\n\t },\n\t\n\t _clickNextPage : function( ev ){\n\t this.model.contents.fetchNextPage();\n\t },\n\t\n\t _changePageSelect : function( ev ){\n\t var page = $( ev.currentTarget ).val();\n\t this.model.contents.fetchPage( page );\n\t },\n\t\n\t /** Toggle and store the deleted visibility and re-render items\n\t * @returns {Boolean} new setting\n\t */\n\t toggleShowDeleted : function( show, options ){\n\t show = ( show !== undefined )?( show ):( !this.model.contents.includeDeleted );\n\t var self = this;\n\t var contents = self.model.contents;\n\t contents.setIncludeDeleted( show, options );\n\t self.trigger( 'show-deleted', show );\n\t\n\t contents.fetchCurrentPage({ renderAll: true });\n\t return show;\n\t },\n\t\n\t /** Toggle and store whether to render explicity hidden contents\n\t * @returns {Boolean} new setting\n\t */\n\t toggleShowHidden : function( show, store, options ){\n\t // console.log( 'toggleShowHidden', show, store );\n\t show = ( show !== undefined )?( show ):( !this.model.contents.includeHidden );\n\t var self = this;\n\t var contents = self.model.contents;\n\t contents.setIncludeHidden( show, options );\n\t self.trigger( 'show-hidden', show );\n\t\n\t contents.fetchCurrentPage({ renderAll: true });\n\t return show;\n\t },\n\t\n\t /** On the first search, if there are no details - load them, then search */\n\t _firstSearch : function( searchFor ){\n\t var self = this;\n\t var inputSelector = '> .controls .search-input';\n\t this.log( 'onFirstSearch', searchFor );\n\t\n\t // if the contents already have enough details to search, search and return now\n\t if( self.model.contents.haveSearchDetails() ){\n\t self.searchItems( searchFor );\n\t return;\n\t }\n\t\n\t // otherwise, load the details progressively here\n\t self.$( inputSelector ).searchInput( 'toggle-loading' );\n\t // set this now so that only results will show during progress\n\t self.searchFor = searchFor;\n\t var xhr = self.model.contents.progressivelyFetchDetails({ silent: true })\n\t .progress( function( response, limit, offset ){\n\t self.renderItems();\n\t self.trigger( 'search:loading-progress', limit, offset );\n\t })\n\t .always( function(){\n\t self.$el.find( inputSelector ).searchInput( 'toggle-loading' );\n\t })\n\t .done( function(){\n\t self.searchItems( searchFor, 'force' );\n\t });\n\t },\n\t\n\t /** clear the search filters and show all views that are normally shown */\n\t clearSearch : function( searchFor ){\n\t var self = this;\n\t if( !self.searchFor ) return self;\n\t //self.log( 'onSearchClear', self );\n\t self.searchFor = '';\n\t self.trigger( 'search:clear', self );\n\t self.$( '> .controls .search-query' ).val( '' );\n\t // NOTE: silent + render prevents collection update event with merge only\n\t // - which causes an empty page due to event handler above\n\t self.model.contents.fetchCurrentPage({ silent: true })\n\t .done( function(){\n\t self.renderItems();\n\t });\n\t return self;\n\t },\n\t\n\t // ........................................................................ error handling\n\t /** Event handler for errors (from the panel, the history, or the history's contents)\n\t * Alternately use two strings for model and xhr to use custom message and title (respectively)\n\t * @param {Model or View} model the (Backbone) source of the error\n\t * @param {XMLHTTPRequest} xhr any ajax obj. assoc. with the error\n\t * @param {Object} options the options map commonly used with bbone ajax\n\t */\n\t errorHandler : function( model, xhr, options ){\n\t //TODO: to mixin or base model\n\t // interrupted ajax or no connection\n\t if( xhr && xhr.status === 0 && xhr.readyState === 0 ){\n\t // return ERROR_MODAL.offlineErrorModal();\n\t // fail silently\n\t return;\n\t }\n\t // otherwise, leave something to report in the console\n\t this.error( model, xhr, options );\n\t // and feedback to a modal\n\t // if sent two strings (and possibly details as 'options'), use those as message and title\n\t if( _.isString( model ) && _.isString( xhr ) ){\n\t var message = model;\n\t var title = xhr;\n\t return ERROR_MODAL.errorModal( message, title, options );\n\t }\n\t // bad gateway\n\t // TODO: possibly to global handler\n\t if( xhr && xhr.status === 502 ){\n\t return ERROR_MODAL.badGatewayErrorModal();\n\t }\n\t return ERROR_MODAL.ajaxErrorModal( model, xhr, options );\n\t },\n\t\n\t /** Remove all messages from the panel. */\n\t clearMessages : function( ev ){\n\t var $target = !_.isUndefined( ev )?\n\t $( ev.currentTarget )\n\t :this.$messages().children( '[class$=\"message\"]' );\n\t $target.fadeOut( this.fxSpeed, function(){\n\t $( this ).remove();\n\t });\n\t return this;\n\t },\n\t\n\t // ........................................................................ scrolling\n\t /** Scrolls the panel to show the content sub-view with the given hid.\n\t * @param {Integer} hid the hid of item to scroll into view\n\t * @returns {HistoryView} the panel\n\t */\n\t scrollToHid : function( hid ){\n\t return this.scrollToItem( _.first( this.viewsWhereModel({ hid: hid }) ) );\n\t },\n\t\n\t // ........................................................................ misc\n\t /** utility for adding -st, -nd, -rd, -th to numbers */\n\t ordinalIndicator : function( number ){\n\t var numStr = number + '';\n\t switch( numStr.charAt( numStr.length - 1 )){\n\t case '1': return numStr + 'st';\n\t case '2': return numStr + 'nd';\n\t case '3': return numStr + 'rd';\n\t default : return numStr + 'th';\n\t }\n\t },\n\t\n\t /** Return a string rep of the history */\n\t toString : function(){\n\t return 'HistoryView(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';\n\t }\n\t});\n\t\n\t\n\t//------------------------------------------------------------------------------ TEMPLATES\n\tHistoryView.prototype.templates = (function(){\n\t\n\t var mainTemplate = BASE_MVC.wrapTemplate([\n\t // temp container\n\t '
                                ',\n\t '
                                ',\n\t '
                                  ',\n\t '
                                  ',\n\t '
                                  '\n\t ]);\n\t\n\t var controlsTemplate = BASE_MVC.wrapTemplate([\n\t '
                                  ',\n\t '
                                  ',\n\t '
                                  <%- history.name %>
                                  ',\n\t '
                                  ',\n\t '
                                  ',\n\t '
                                  <%- history.nice_size %>
                                  ',\n\t\n\t '
                                  ',\n\t\n\t '
                                  ',\n\t '<% if( history.deleted && history.purged ){ %>',\n\t '
                                  ',\n\t _l( 'This history has been purged and deleted' ),\n\t '
                                  ',\n\t '<% } else if( history.deleted ){ %>',\n\t '
                                  ',\n\t _l( 'This history has been deleted' ),\n\t '
                                  ',\n\t '<% } else if( history.purged ){ %>',\n\t '
                                  ',\n\t _l( 'This history has been purged' ),\n\t '
                                  ',\n\t '<% } %>',\n\t\n\t '<% if( history.message ){ %>',\n\t // should already be localized\n\t '
                                  messagesmall\">',\n\t '<%= history.message.text %>',\n\t '
                                  ',\n\t '<% } %>',\n\t '
                                  ',\n\t\n\t // add tags and annotations\n\t '
                                  ',\n\t '
                                  ',\n\t\n\t '
                                  ',\n\t '
                                  ',\n\t '
                                  ',\n\t\n\t '
                                  ',\n\t '
                                  ',\n\t '',\n\t '',\n\t '
                                  ',\n\t '
                                  ',\n\t '
                                  ',\n\t '
                                  ',\n\t '
                                  ',\n\t '
                                  '\n\t ], 'history' );\n\t\n\t var paginationTemplate = BASE_MVC.wrapTemplate([\n\t '',\n\t '',\n\t '',\n\t ], 'pages' );\n\t\n\t return _.extend( _.clone( _super.prototype.templates ), {\n\t el : mainTemplate,\n\t controls : controlsTemplate,\n\t pagination : paginationTemplate,\n\t });\n\t}());\n\t\n\t\n\t//==============================================================================\n\t return {\n\t HistoryView: HistoryView\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(2), __webpack_require__(1)))\n\n/***/ },\n/* 116 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(_) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(79),\n\t __webpack_require__(110),\n\t __webpack_require__(6),\n\t __webpack_require__(5)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( PopupMenu, historyCopyDialog, BASE_MVC, _l ){\n\t\n\t'use strict';\n\t\n\t// ============================================================================\n\tvar menu = [\n\t {\n\t html : _l( 'History Lists' ),\n\t header : true\n\t },\n\t {\n\t html : _l( 'Saved Histories' ),\n\t href : 'history/list',\n\t },\n\t {\n\t html : _l( 'Histories Shared with Me' ),\n\t href : 'history/list_shared'\n\t },\n\t\n\t {\n\t html : _l( 'History Actions' ),\n\t header : true,\n\t anon : true\n\t },\n\t {\n\t html : _l( 'Create New' ),\n\t func : function(){ Galaxy.currHistoryPanel.createNewHistory(); }\n\t },\n\t {\n\t html : _l( 'Copy History' ),\n\t func : function(){\n\t historyCopyDialog( Galaxy.currHistoryPanel.model )\n\t .done( function(){\n\t Galaxy.currHistoryPanel.loadCurrentHistory();\n\t });\n\t },\n\t },\n\t {\n\t html : _l( 'Share or Publish' ),\n\t href : 'history/sharing',\n\t },\n\t {\n\t html : _l( 'Show Structure' ),\n\t href : 'history/display_structured',\n\t anon : true,\n\t },\n\t {\n\t html : _l( 'Extract Workflow' ),\n\t href : 'workflow/build_from_current_history',\n\t },\n\t {\n\t html : _l( 'Delete' ),\n\t anon : true,\n\t func : function() {\n\t if( Galaxy && Galaxy.currHistoryPanel && confirm( _l( 'Really delete the current history?' ) ) ){\n\t galaxy_main.window.location.href = 'history/delete?id=' + Galaxy.currHistoryPanel.model.id;\n\t }\n\t },\n\t },\n\t {\n\t html : _l( 'Delete Permanently' ),\n\t purge : true,\n\t anon : true,\n\t func : function() {\n\t if( Galaxy && Galaxy.currHistoryPanel\n\t && confirm( _l( 'Really delete the current history permanently? This cannot be undone.' ) ) ){\n\t galaxy_main.window.location.href = 'history/delete?purge=True&id=' + Galaxy.currHistoryPanel.model.id;\n\t }\n\t },\n\t },\n\t\n\t\n\t {\n\t html : _l( 'Dataset Actions' ),\n\t header : true,\n\t anon : true\n\t },\n\t {\n\t html : _l( 'Copy Datasets' ),\n\t href : 'dataset/copy_datasets',\n\t },\n\t {\n\t html : _l( 'Dataset Security' ),\n\t href : 'root/history_set_default_permissions',\n\t },\n\t {\n\t html : _l( 'Resume Paused Jobs' ),\n\t href : 'history/resume_paused_jobs?current=True',\n\t anon : true,\n\t },\n\t {\n\t html : _l( 'Collapse Expanded Datasets' ),\n\t func : function(){ Galaxy.currHistoryPanel.collapseAll(); }\n\t },\n\t {\n\t html : _l( 'Unhide Hidden Datasets' ),\n\t anon : true,\n\t func : function(){ Galaxy.currHistoryPanel.unhideHidden(); }\n\t },\n\t {\n\t html : _l( 'Delete Hidden Datasets' ),\n\t anon : true,\n\t func : function(){ Galaxy.currHistoryPanel.deleteHidden(); }\n\t },\n\t {\n\t html : _l( 'Purge Deleted Datasets' ),\n\t confirm : _l( 'Really delete all deleted datasets permanently? This cannot be undone.' ),\n\t href : 'history/purge_deleted_datasets',\n\t purge : true,\n\t anon : true,\n\t },\n\t\n\t {\n\t html : _l( 'Downloads' ),\n\t header : true\n\t },\n\t {\n\t html : _l( 'Export Tool Citations' ),\n\t href : 'history/citations',\n\t anon : true,\n\t },\n\t {\n\t html : _l( 'Export History to File' ),\n\t href : 'history/export_archive?preview=True',\n\t anon : true,\n\t },\n\t\n\t {\n\t html : _l( 'Other Actions' ),\n\t header : true\n\t },\n\t {\n\t html : _l( 'Import from File' ),\n\t href : 'history/import_archive',\n\t }\n\t];\n\t\n\tfunction buildMenu( isAnon, purgeAllowed, urlRoot ){\n\t return _.clone( menu ).filter( function( menuOption ){\n\t if( isAnon && !menuOption.anon ){\n\t return false;\n\t }\n\t if( !purgeAllowed && menuOption.purge ){\n\t return false;\n\t }\n\t\n\t //TODO:?? hard-coded galaxy_main\n\t if( menuOption.href ){\n\t menuOption.href = urlRoot + menuOption.href;\n\t menuOption.target = 'galaxy_main';\n\t }\n\t\n\t if( menuOption.confirm ){\n\t menuOption.func = function(){\n\t if( confirm( menuOption.confirm ) ){\n\t galaxy_main.location = menuOption.href;\n\t }\n\t };\n\t }\n\t return true;\n\t });\n\t}\n\t\n\tvar create = function( $button, options ){\n\t options = options || {};\n\t var isAnon = options.anonymous === undefined? true : options.anonymous,\n\t purgeAllowed = options.purgeAllowed || false,\n\t menu = buildMenu( isAnon, purgeAllowed, Galaxy.root );\n\t //console.debug( 'menu:', menu );\n\t return new PopupMenu( $button, menu );\n\t};\n\t\n\t\n\t// ============================================================================\n\t return create;\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(2)))\n\n/***/ },\n/* 117 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, $, _) {/**\n\t * Renders tabs e.g. used in the Charts editor, behaves similar to repeat and section rendering\n\t */\n\t!(__WEBPACK_AMD_DEFINE_ARRAY__ = [ __webpack_require__(4) ], __WEBPACK_AMD_DEFINE_RESULT__ = function( Utils ) {\n\tvar View = Backbone.View.extend({\n\t initialize : function( options ) {\n\t var self = this;\n\t this.visible = false;\n\t this.$nav = null;\n\t this.$content = null;\n\t this.first_tab = null;\n\t this.current_id = null;\n\t this.list = {};\n\t this.options = Utils.merge( options, {\n\t title_new : '',\n\t operations : null,\n\t onnew : null,\n\t max : null,\n\t onchange : null\n\t });\n\t this.setElement( $( this._template( this.options ) ) );\n\t this.$nav = this.$( '.tab-navigation' );\n\t this.$content = this.$( '.tab-content' );\n\t this.$operations = this.$nav.find( '.tab-operations' );\n\t\n\t // Renders tab operations\n\t if ( this.options.operations ) {\n\t $.each( this.options.operations, function( name, item ) {\n\t item.$el.prop( 'id', name );\n\t self.$operations.append( item.$el );\n\t });\n\t }\n\t\n\t // Allows user to add new tabs\n\t this.options.onnew && this.$nav.append( $( this._template_tab_new( this.options ) )\n\t .tooltip( { title: 'Add a new tab', placement: 'bottom', container: self.$el } )\n\t .on( 'click', function( e ) { self.options.onnew() } )\n\t );\n\t this.$tabnew = this.$nav.find( '.tab-new' );\n\t\n\t // Remove all tooltips on click\n\t this.$el.on( 'click', function() { $( '.tooltip' ).hide() } );\n\t },\n\t\n\t /** Returns current number of tabs */\n\t size: function() {\n\t return _.size( this.list );\n\t },\n\t\n\t /** Returns tab id for currently shown tab */\n\t current: function() {\n\t return this.$el.find( '.tab-pane.active' ).attr( 'id' );\n\t },\n\t\n\t /** Adds a new tab */\n\t add: function( options ) {\n\t var self = this;\n\t var id = options.id;\n\t var $tab_title = $( this._template_tab( options ) );\n\t var $tab_content = $( '
                                  ' ).attr( 'id', options.id ).addClass( 'tab-pane' );\n\t\n\t // hide new tab if maximum number of tabs has been reached\n\t this.list[ id ] = true;\n\t if ( this.options.max && this.size() >= this.options.max ) {\n\t this.$tabnew.hide();\n\t }\n\t\n\t // insert tab before new tab or as last tab\n\t if ( this.options.onnew ) {\n\t this.$tabnew.before( $tab_title );\n\t } else {\n\t this.$nav.append( $tab_title );\n\t }\n\t\n\t // assing delete callback if provided\n\t if ( options.ondel ) {\n\t $tab_title.find( '.tab-delete' ).tooltip( { title: 'Delete this tab', placement: 'bottom', container: self.$el } )\n\t .on( 'click', function() { options.ondel() } );\n\t } else {\n\t $tab_title.tooltip( { title: options.tooltip, placement: 'bottom', container: self.$el } );\n\t }\n\t $tab_title.on( 'click', function( e ) {\n\t e.preventDefault();\n\t options.onclick ? options.onclick() : self.show( id );\n\t });\n\t this.$content.append( $tab_content.append( options.$el ) );\n\t\n\t // assign current/first tab\n\t if ( this.size() == 1 ) {\n\t $tab_title.addClass( 'active' );\n\t $tab_content.addClass( 'active' );\n\t this.first_tab = id;\n\t }\n\t if ( !this.current_id ) {\n\t this.current_id = id;\n\t }\n\t },\n\t\n\t /** Delete tab */\n\t del: function( id ) {\n\t this.$( '#tab-' + id ).remove();\n\t this.$( '#' + id ).remove();\n\t this.first_tab = this.first_tab == id ? null : this.first_tab;\n\t this.first_tab != null && this.show( this.first_tab );\n\t this.list[ id ] && delete this.list[ id ];\n\t if ( this.size() < this.options.max ) {\n\t this.$el.find( '.ui-tabs-new' ).show();\n\t }\n\t },\n\t\n\t /** Delete all tabs */\n\t delAll: function() {\n\t for ( var id in this.list ) {\n\t this.del( id );\n\t }\n\t },\n\t\n\t /** Show tab view and highlight a tab by id */\n\t show: function( id ){\n\t this.$el.fadeIn( 'fast' );\n\t this.visible = true;\n\t if ( id ) {\n\t this.$( '#tab-' + this.current_id ).removeClass('active' );\n\t this.$( '#' + this.current_id ).removeClass('active' );\n\t this.$( '#tab-' + id ).addClass( 'active' );\n\t this.$( '#' + id ).addClass( 'active' );\n\t this.current_id = id;\n\t }\n\t this.options.onchange && this.options.onchange( id );\n\t },\n\t \n\t /** Hide tab view */\n\t hide: function(){\n\t this.$el.fadeOut( 'fast' );\n\t this.visible = false;\n\t },\n\t\n\t /** Show tab */\n\t showTab: function( id ) {\n\t this.$( '#tab-' + id ).show();\n\t },\n\t\n\t /** hide tab */\n\t hideTab: function( id ) {\n\t this.$( '#tab-' + id ).hide();\n\t },\n\t\n\t /** Hide operation by id */\n\t hideOperation: function( id ) {\n\t this.$nav.find( '#' + id ).hide();\n\t },\n\t\n\t /** Show operation by id */\n\t showOperation: function( id ) {\n\t this.$nav.find( '#' + id ).show();\n\t },\n\t\n\t /** Reassign an operation to a new callback */\n\t setOperation: function( id, callback ) {\n\t this.$nav.find( '#' + id ).off('click').on( 'click', callback );\n\t },\n\t\n\t /** Set/Get title */\n\t title: function( id, new_title ) {\n\t var $el = this.$( '#tab-title-text-' + id );\n\t new_title && $el.html( new_title );\n\t return $el.html();\n\t },\n\t\n\t /** Enumerate titles */\n\t retitle: function( new_title ) {\n\t var index = 0;\n\t for ( var id in this.list ) {\n\t this.title( id, ++index + ': ' + new_title );\n\t }\n\t },\n\t\n\t /** Main template */\n\t _template: function( options ) {\n\t return $( '
                                  ' ).addClass( 'ui-tabs tabbable tabs-left' )\n\t .append( $( '
                                • ' +\n\t '
                                  ' +\n\t '
                                  ' +\n\t '
                                  ' +\n\t '
                                  You can tell Galaxy to download data from web by entering URL in this box (one per line). You can also directly paste the contents of a file.
                                  ' +\n\t '',\n '
                                  ',\n '
                                  '\n ].join( '' );\n }\n});\n\n//==============================================================================\nreturn {\n CitationView : CitationView,\n CitationListView : CitationListView\n};\n\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/citation/citation-view.js\n ** module id = 28\n ** module chunks = 0 3\n **/","define([\n \"mvc/list/list-item\",\n \"mvc/dataset/dataset-li\",\n \"mvc/base-mvc\",\n \"utils/localization\"\n], function( LIST_ITEM, DATASET_LI, BASE_MVC, _l ){\n\n'use strict';\n//==============================================================================\nvar FoldoutListItemView = LIST_ITEM.FoldoutListItemView,\n ListItemView = LIST_ITEM.ListItemView;\n/** @class Read only view for DatasetCollection.\n */\nvar DCListItemView = FoldoutListItemView.extend(\n/** @lends DCListItemView.prototype */{\n\n className : FoldoutListItemView.prototype.className + \" dataset-collection\",\n id : function(){\n return [ 'dataset_collection', this.model.get( 'id' ) ].join( '-' );\n },\n\n /** override to add linkTarget */\n initialize : function( attributes ){\n this.linkTarget = attributes.linkTarget || '_blank';\n this.hasUser = attributes.hasUser;\n FoldoutListItemView.prototype.initialize.call( this, attributes );\n },\n\n /** event listeners */\n _setUpListeners : function(){\n FoldoutListItemView.prototype._setUpListeners.call( this );\n this.listenTo( this.model, 'change', function( model, options ){\n // if the model has changed deletion status render it entirely\n if( _.has( model.changed, 'deleted' ) ){\n this.render();\n\n // if the model has been decorated after the fact with the element count,\n // render the subtitle where the count is displayed\n } else if( _.has( model.changed, 'element_count' ) ){\n this.$( '> .title-bar .subtitle' ).replaceWith( this._renderSubtitle() );\n }\n });\n },\n\n // ......................................................................... rendering\n /** render a subtitle to show the user what sort of collection this is */\n _renderSubtitle : function(){\n return $( this.templates.subtitle( this.model.toJSON(), this ) );\n },\n\n // ......................................................................... foldout\n /** override to add linktarget to sub-panel */\n _getFoldoutPanelOptions : function(){\n var options = FoldoutListItemView.prototype._getFoldoutPanelOptions.call( this );\n return _.extend( options, {\n linkTarget : this.linkTarget,\n hasUser : this.hasUser\n });\n },\n\n /** override to not catch sub-panel selectors */\n $selector : function(){\n return this.$( '> .selector' );\n },\n\n // ......................................................................... misc\n /** String representation */\n toString : function(){\n var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n return 'DCListItemView(' + modelString + ')';\n }\n});\n\n// ............................................................................ TEMPLATES\n/** underscore templates */\nDCListItemView.prototype.templates = (function(){\n\n var warnings = _.extend( {}, FoldoutListItemView.prototype.templates.warnings, {\n error : BASE_MVC.wrapTemplate([\n // error during index fetch - show error on dataset\n '<% if( model.error ){ %>',\n '
                                  ',\n _l( 'There was an error getting the data for this collection' ), ': <%- model.error %>',\n '
                                  ',\n '<% } %>'\n ]),\n purged : BASE_MVC.wrapTemplate([\n '<% if( model.purged ){ %>',\n '
                                  ',\n _l( 'This collection has been deleted and removed from disk' ),\n '
                                  ',\n '<% } %>'\n ]),\n deleted : BASE_MVC.wrapTemplate([\n // deleted not purged\n '<% if( model.deleted && !model.purged ){ %>',\n '
                                  ',\n _l( 'This collection has been deleted' ),\n '
                                  ',\n '<% } %>'\n ])\n });\n\n // use element identifier\n var titleBarTemplate = BASE_MVC.wrapTemplate([\n '
                                  ',\n '
                                  ',\n '<%- collection.element_identifier || collection.name %>',\n '
                                  ',\n '
                                  ',\n '
                                  '\n ], 'collection' );\n\n // use element identifier\n var subtitleTemplate = BASE_MVC.wrapTemplate([\n '
                                  ',\n '<% var countText = collection.element_count? ( collection.element_count + \" \" ) : \"\"; %>',\n '<% if( collection.collection_type === \"list\" ){ %>',\n _l( 'a list of <%- countText %>datasets' ),\n '<% } else if( collection.collection_type === \"paired\" ){ %>',\n _l( 'a pair of datasets' ),\n '<% } else if( collection.collection_type === \"list:paired\" ){ %>',\n _l( 'a list of <%- countText %>dataset pairs' ),\n '<% } else if( collection.collection_type === \"list:list\" ){ %>',\n _l( 'a list of <%- countText %>dataset lists' ),\n '<% } %>',\n '
                                  '\n ], 'collection' );\n\n return _.extend( {}, FoldoutListItemView.prototype.templates, {\n warnings : warnings,\n titleBar : titleBarTemplate,\n subtitle : subtitleTemplate\n });\n}());\n\n\n//==============================================================================\n/** @class Read only view for DatasetCollectionElement.\n */\nvar DCEListItemView = ListItemView.extend(\n/** @lends DCEListItemView.prototype */{\n\n /** add the DCE class to the list item */\n className : ListItemView.prototype.className + \" dataset-collection-element\",\n\n /** set up */\n initialize : function( attributes ){\n if( attributes.logger ){ this.logger = this.model.logger = attributes.logger; }\n this.log( 'DCEListItemView.initialize:', attributes );\n ListItemView.prototype.initialize.call( this, attributes );\n },\n\n // ......................................................................... misc\n /** String representation */\n toString : function(){\n var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n return 'DCEListItemView(' + modelString + ')';\n }\n});\n\n// ............................................................................ TEMPLATES\n/** underscore templates */\nDCEListItemView.prototype.templates = (function(){\n\n // use the element identifier here - since that will persist and the user will need it\n var titleBarTemplate = BASE_MVC.wrapTemplate([\n '
                                  ',\n '
                                  ',\n '<%- element.element_identifier %>',\n '
                                  ',\n '
                                  ',\n '
                                  '\n ], 'element' );\n\n return _.extend( {}, ListItemView.prototype.templates, {\n titleBar : titleBarTemplate\n });\n}());\n\n\n//==============================================================================\n/** @class Read only view for a DatasetCollectionElement that is also an DatasetAssociation\n * (a dataset contained in a dataset collection).\n */\nvar DatasetDCEListItemView = DATASET_LI.DatasetListItemView.extend(\n/** @lends DatasetDCEListItemView.prototype */{\n\n className : DATASET_LI.DatasetListItemView.prototype.className + \" dataset-collection-element\",\n\n /** set up */\n initialize : function( attributes ){\n if( attributes.logger ){ this.logger = this.model.logger = attributes.logger; }\n this.log( 'DatasetDCEListItemView.initialize:', attributes );\n DATASET_LI.DatasetListItemView.prototype.initialize.call( this, attributes );\n },\n\n /** In this override, only get details if in the ready state.\n * Note: fetch with no 'change' event triggering to prevent automatic rendering.\n */\n _fetchModelDetails : function(){\n var view = this;\n if( view.model.inReadyState() && !view.model.hasDetails() ){\n return view.model.fetch({ silent: true });\n }\n return jQuery.when();\n },\n\n // ......................................................................... misc\n /** String representation */\n toString : function(){\n var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n return 'DatasetDCEListItemView(' + modelString + ')';\n }\n});\n\n// ............................................................................ TEMPLATES\n/** underscore templates */\nDatasetDCEListItemView.prototype.templates = (function(){\n\n // use the element identifier here and not the dataset name\n //TODO:?? can we steal the DCE titlebar?\n var titleBarTemplate = BASE_MVC.wrapTemplate([\n '
                                  ',\n '',\n '
                                  ',\n '<%- element.element_identifier %>',\n '
                                  ',\n '
                                  '\n ], 'element' );\n\n return _.extend( {}, DATASET_LI.DatasetListItemView.prototype.templates, {\n titleBar : titleBarTemplate\n });\n}());\n\n\n//==============================================================================\n/** @class Read only view for a DatasetCollectionElement that is also a DatasetCollection\n * (a nested DC).\n */\nvar NestedDCDCEListItemView = DCListItemView.extend(\n/** @lends NestedDCDCEListItemView.prototype */{\n\n className : DCListItemView.prototype.className + \" dataset-collection-element\",\n\n /** In this override, add the state as a class for use with state-based CSS */\n _swapNewRender : function( $newRender ){\n DCListItemView.prototype._swapNewRender.call( this, $newRender );\n var state = this.model.get( 'state' ) || 'ok';\n this.$el.addClass( 'state-' + state );\n return this.$el;\n },\n\n // ......................................................................... misc\n /** String representation */\n toString : function(){\n var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n return 'NestedDCDCEListItemView(' + modelString + ')';\n }\n});\n\n\n//==============================================================================\n return {\n DCListItemView : DCListItemView,\n DCEListItemView : DCEListItemView,\n DatasetDCEListItemView : DatasetDCEListItemView,\n NestedDCDCEListItemView : NestedDCDCEListItemView\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/collection/collection-li.js\n ** module id = 29\n ** module chunks = 3\n **/","define([\n \"mvc/dataset/dataset-model\",\n \"mvc/base-mvc\",\n \"utils/localization\"\n], function( DATASET_MODEL, BASE_MVC, _l ){\n'use strict';\n\n//==============================================================================\n/*\nNotes:\n\nTerminology:\n DatasetCollection/DC : a container of datasets or nested DatasetCollections\n Element/DatasetCollectionElement/DCE : an item contained in a DatasetCollection\n HistoryDatasetCollectionAssociation/HDCA: a DatasetCollection contained in a history\n\n\nThis all seems too complex unfortunately:\n\n- Terminology collision between DatasetCollections (DCs) and Backbone Collections.\n- In the DatasetCollections API JSON, DC Elements use a 'Has A' stucture to *contain*\n either a dataset or a nested DC. This would make the hierarchy much taller. I've\n decided to merge the contained JSON with the DC element json - making the 'has a'\n relation into an 'is a' relation. This seems simpler to me and allowed a lot of\n DRY in both models and views, but may make tracking or tracing within these models\n more difficult (since DatasetCollectionElements are now *also* DatasetAssociations\n or DatasetCollections (nested)). This also violates the rule of thumb about\n favoring aggregation over inheritance.\n- Currently, there are three DatasetCollection subclasses: List, Pair, and ListPaired.\n These each should a) be usable on their own, b) be usable in the context of\n nesting within a collection model (at least in the case of ListPaired), and\n c) be usable within the context of other container models (like History or\n LibraryFolder, etc.). I've tried to separate/extract classes in order to\n handle those three situations, but it's proven difficult to do in a simple,\n readable manner.\n- Ideally, histories and libraries would inherit from the same server models as\n dataset collections do since they are (in essence) dataset collections themselves -\n making the whole nested structure simpler. This would be a large, error-prone\n refactoring and migration.\n\nMany of the classes and heirarchy are meant as extension points so, while the\nrelations and flow may be difficult to understand initially, they'll allow us to\nhandle the growth or flux dataset collection in the future (w/o actually implementing\nany YAGNI).\n\n*/\n//_________________________________________________________________________________________________ ELEMENTS\n/** @class mixin for Dataset collection elements.\n * When collection elements are passed from the API, the underlying element is\n * in a sub-object 'object' (IOW, a DCE representing an HDA will have HDA json in element.object).\n * This mixin uses the constructor and parse methods to merge that JSON with the DCE attribtues\n * effectively changing a DCE from a container to a subclass (has a --> is a).\n */\nvar DatasetCollectionElementMixin = {\n\n /** default attributes used by elements in a dataset collection */\n defaults : {\n model_class : 'DatasetCollectionElement',\n element_identifier : null,\n element_index : null,\n element_type : null\n },\n\n /** merge the attributes of the sub-object 'object' into this model */\n _mergeObject : function( attributes ){\n // if we don't preserve and correct ids here, the element id becomes the object id\n // and collision in backbone's _byId will occur and only\n _.extend( attributes, attributes.object, { element_id: attributes.id });\n delete attributes.object;\n return attributes;\n },\n\n /** override to merge this.object into this */\n constructor : function( attributes, options ){\n // console.debug( '\\t DatasetCollectionElement.constructor:', attributes, options );\n attributes = this._mergeObject( attributes );\n this.idAttribute = 'element_id';\n Backbone.Model.apply( this, arguments );\n },\n\n /** when the model is fetched, merge this.object into this */\n parse : function( response, options ){\n var attributes = response;\n attributes = this._mergeObject( attributes );\n return attributes;\n }\n};\n\n/** @class Concrete class of Generic DatasetCollectionElement */\nvar DatasetCollectionElement = Backbone.Model\n .extend( BASE_MVC.LoggableMixin )\n .extend( DatasetCollectionElementMixin )\n .extend({ _logNamespace : 'collections' });\n\n\n//==============================================================================\n/** @class Base/Abstract Backbone collection for Generic DCEs. */\nvar DCECollection = Backbone.Collection.extend( BASE_MVC.LoggableMixin ).extend(\n/** @lends DCECollection.prototype */{\n _logNamespace : 'collections',\n\n model: DatasetCollectionElement,\n\n /** String representation. */\n toString : function(){\n return ([ 'DatasetCollectionElementCollection(', this.length, ')' ].join( '' ));\n }\n});\n\n\n//==============================================================================\n/** @class Backbone model for a dataset collection element that is a dataset (HDA).\n */\nvar DatasetDCE = DATASET_MODEL.DatasetAssociation.extend( BASE_MVC.mixin( DatasetCollectionElementMixin,\n/** @lends DatasetDCE.prototype */{\n\n /** url fn */\n url : function(){\n // won't always be an hda\n if( !this.has( 'history_id' ) ){\n console.warn( 'no endpoint for non-hdas within a collection yet' );\n // (a little silly since this api endpoint *also* points at hdas)\n return Galaxy.root + 'api/datasets';\n }\n return Galaxy.root + 'api/histories/' + this.get( 'history_id' ) + '/contents/' + this.get( 'id' );\n },\n\n defaults : _.extend( {},\n DATASET_MODEL.DatasetAssociation.prototype.defaults,\n DatasetCollectionElementMixin.defaults\n ),\n\n // because all objects have constructors (as this hashmap would even if this next line wasn't present)\n // the constructor in hcontentMixin won't be attached by BASE_MVC.mixin to this model\n // - re-apply manually for now\n /** call the mixin constructor */\n constructor : function( attributes, options ){\n this.debug( '\\t DatasetDCE.constructor:', attributes, options );\n //DATASET_MODEL.DatasetAssociation.prototype.constructor.call( this, attributes, options );\n DatasetCollectionElementMixin.constructor.call( this, attributes, options );\n },\n\n /** Does this model already contain detailed data (as opposed to just summary level data)? */\n hasDetails : function(){\n return this.elements && this.elements.length;\n },\n\n /** String representation. */\n toString : function(){\n var objStr = this.get( 'element_identifier' );\n return ([ 'DatasetDCE(', objStr, ')' ].join( '' ));\n }\n}));\n\n\n//==============================================================================\n/** @class DCECollection of DatasetDCE's (a list of datasets, a pair of datasets).\n */\nvar DatasetDCECollection = DCECollection.extend(\n/** @lends DatasetDCECollection.prototype */{\n model: DatasetDCE,\n\n /** String representation. */\n toString : function(){\n return ([ 'DatasetDCECollection(', this.length, ')' ].join( '' ));\n }\n});\n\n\n//_________________________________________________________________________________________________ COLLECTIONS\n/** @class Backbone model for Dataset Collections.\n * The DC API returns an array of JSON objects under the attribute elements.\n * This model:\n * - removes that array/attribute ('elements') from the model,\n * - creates a bbone collection (of the class defined in the 'collectionClass' attribute),\n * - passes that json onto the bbone collection\n * - caches the bbone collection in this.elements\n */\nvar DatasetCollection = Backbone.Model\n .extend( BASE_MVC.LoggableMixin )\n .extend( BASE_MVC.SearchableModelMixin )\n .extend(/** @lends DatasetCollection.prototype */{\n _logNamespace : 'collections',\n\n /** default attributes for a model */\n defaults : {\n /* 'list', 'paired', or 'list:paired' */\n collection_type : null,\n //??\n deleted : false\n },\n\n /** Which class to use for elements */\n collectionClass : DCECollection,\n\n /** set up: create elements instance var and (on changes to elements) update them */\n initialize : function( model, options ){\n this.debug( this + '(DatasetCollection).initialize:', model, options, this );\n this.elements = this._createElementsModel();\n this.on( 'change:elements', function(){\n this.log( 'change:elements' );\n //TODO: prob. better to update the collection instead of re-creating it\n this.elements = this._createElementsModel();\n });\n },\n\n /** move elements model attribute to full collection */\n _createElementsModel : function(){\n this.debug( this + '._createElementsModel', this.collectionClass, this.get( 'elements' ), this.elements );\n //TODO: same patterns as DatasetCollectionElement _createObjectModel - refactor to BASE_MVC.hasSubModel?\n var elements = this.get( 'elements' ) || [];\n this.unset( 'elements', { silent: true });\n this.elements = new this.collectionClass( elements );\n //this.debug( 'collectionClass:', this.collectionClass + '', this.elements );\n return this.elements;\n },\n\n // ........................................................................ common queries\n /** pass the elements back within the model json when this is serialized */\n toJSON : function(){\n var json = Backbone.Model.prototype.toJSON.call( this );\n if( this.elements ){\n json.elements = this.elements.toJSON();\n }\n return json;\n },\n\n /** Is this collection in a 'ready' state no processing (for the collection) is left\n * to do on the server.\n */\n inReadyState : function(){\n var populated = this.get( 'populated' );\n return ( this.isDeletedOrPurged() || populated );\n },\n\n //TODO:?? the following are the same interface as DatasetAssociation - can we combine?\n /** Does the DC contain any elements yet? Is a fetch() required? */\n hasDetails : function(){\n return this.elements.length !== 0;\n },\n\n /** Given the filters, what models in this.elements would be returned? */\n getVisibleContents : function( filters ){\n // filters unused for now\n return this.elements;\n },\n\n // ........................................................................ ajax\n /** override to use actual Dates objects for create/update times */\n parse : function( response, options ){\n var parsed = Backbone.Model.prototype.parse.call( this, response, options );\n if( parsed.create_time ){\n parsed.create_time = new Date( parsed.create_time );\n }\n if( parsed.update_time ){\n parsed.update_time = new Date( parsed.update_time );\n }\n return parsed;\n },\n\n /** save this dataset, _Mark_ing it as deleted (just a flag) */\n 'delete' : function( options ){\n if( this.get( 'deleted' ) ){ return jQuery.when(); }\n return this.save( { deleted: true }, options );\n },\n /** save this dataset, _Mark_ing it as undeleted */\n undelete : function( options ){\n if( !this.get( 'deleted' ) || this.get( 'purged' ) ){ return jQuery.when(); }\n return this.save( { deleted: false }, options );\n },\n\n /** Is this collection deleted or purged? */\n isDeletedOrPurged : function(){\n return ( this.get( 'deleted' ) || this.get( 'purged' ) );\n },\n\n // ........................................................................ searchable\n /** searchable attributes for collections */\n searchAttributes : [\n 'name'\n ],\n\n // ........................................................................ misc\n /** String representation */\n toString : function(){\n var idAndName = [ this.get( 'id' ), this.get( 'name' ) || this.get( 'element_identifier' ) ];\n return 'DatasetCollection(' + ( idAndName.join(',') ) + ')';\n }\n});\n\n\n//==============================================================================\n/** Model for a DatasetCollection containing datasets (non-nested).\n */\nvar ListDatasetCollection = DatasetCollection.extend(\n/** @lends ListDatasetCollection.prototype */{\n\n /** override since we know the collection will only contain datasets */\n collectionClass : DatasetDCECollection,\n\n /** String representation. */\n toString : function(){ return 'List' + DatasetCollection.prototype.toString.call( this ); }\n});\n\n\n//==============================================================================\n/** Model for a DatasetCollection containing fwd/rev datasets (a list of 2).\n */\nvar PairDatasetCollection = ListDatasetCollection.extend(\n/** @lends PairDatasetCollection.prototype */{\n\n /** String representation. */\n toString : function(){ return 'Pair' + DatasetCollection.prototype.toString.call( this ); }\n});\n\n\n//_________________________________________________________________________________________________ NESTED COLLECTIONS\n// this is where things get weird, man. Weird.\n//TODO: it might be possible to compact all the following...I think.\n//==============================================================================\n/** @class Backbone model for a Generic DatasetCollectionElement that is also a DatasetCollection\n * (a nested collection). Currently only list:paired.\n */\nvar NestedDCDCE = DatasetCollection.extend( BASE_MVC.mixin( DatasetCollectionElementMixin,\n/** @lends NestedDCDCE.prototype */{\n\n // because all objects have constructors (as this hashmap would even if this next line wasn't present)\n // the constructor in hcontentMixin won't be attached by BASE_MVC.mixin to this model\n // - re-apply manually it now\n /** call the mixin constructor */\n constructor : function( attributes, options ){\n this.debug( '\\t NestedDCDCE.constructor:', attributes, options );\n DatasetCollectionElementMixin.constructor.call( this, attributes, options );\n },\n\n /** String representation. */\n toString : function(){\n var objStr = ( this.object )?( '' + this.object ):( this.get( 'element_identifier' ) );\n return ([ 'NestedDCDCE(', objStr, ')' ].join( '' ));\n }\n}));\n\n\n//==============================================================================\n/** @class Backbone collection containing Generic NestedDCDCE's (nested dataset collections).\n */\nvar NestedDCDCECollection = DCECollection.extend(\n/** @lends NestedDCDCECollection.prototype */{\n\n /** This is a collection of nested collections */\n model: NestedDCDCE,\n\n /** String representation. */\n toString : function(){\n return ([ 'NestedDCDCECollection(', this.length, ')' ].join( '' ));\n }\n});\n\n\n//==============================================================================\n/** @class Backbone model for a paired dataset collection within a list:paired dataset collection.\n */\nvar NestedPairDCDCE = PairDatasetCollection.extend( BASE_MVC.mixin( DatasetCollectionElementMixin,\n/** @lends NestedPairDCDCE.prototype */{\n//TODO:?? possibly rename to NestedDatasetCollection?\n\n // because all objects have constructors (as this hashmap would even if this next line wasn't present)\n // the constructor in hcontentMixin won't be attached by BASE_MVC.mixin to this model\n // - re-apply manually it now\n /** This is both a collection and a collection element - call the constructor */\n constructor : function( attributes, options ){\n this.debug( '\\t NestedPairDCDCE.constructor:', attributes, options );\n //DatasetCollection.constructor.call( this, attributes, options );\n DatasetCollectionElementMixin.constructor.call( this, attributes, options );\n },\n\n /** String representation. */\n toString : function(){\n var objStr = ( this.object )?( '' + this.object ):( this.get( 'element_identifier' ) );\n return ([ 'NestedPairDCDCE(', objStr, ')' ].join( '' ));\n }\n}));\n\n\n//==============================================================================\n/** @class Backbone collection for a backbone collection containing paired dataset collections.\n */\nvar NestedPairDCDCECollection = NestedDCDCECollection.extend(\n/** @lends PairDCDCECollection.prototype */{\n\n /** We know this collection is composed of only nested pair collections */\n model: NestedPairDCDCE,\n\n /** String representation. */\n toString : function(){\n return ([ 'NestedPairDCDCECollection(', this.length, ')' ].join( '' ));\n }\n});\n\n\n//==============================================================================\n/** @class Backbone Model for a DatasetCollection (list) that contains DatasetCollections (pairs).\n */\nvar ListPairedDatasetCollection = DatasetCollection.extend(\n/** @lends ListPairedDatasetCollection.prototype */{\n\n /** list:paired is the only collection that itself contains collections */\n collectionClass : NestedPairDCDCECollection,\n\n /** String representation. */\n toString : function(){\n return ([ 'ListPairedDatasetCollection(', this.get( 'name' ), ')' ].join( '' ));\n }\n});\n\n\n//==============================================================================\n/** @class Backbone model for a list dataset collection within a list:list dataset collection. */\nvar NestedListDCDCE = ListDatasetCollection.extend( BASE_MVC.mixin( DatasetCollectionElementMixin,\n/** @lends NestedListDCDCE.prototype */{\n\n /** This is both a collection and a collection element - call the constructor */\n constructor : function( attributes, options ){\n this.debug( '\\t NestedListDCDCE.constructor:', attributes, options );\n DatasetCollectionElementMixin.constructor.call( this, attributes, options );\n },\n\n /** String representation. */\n toString : function(){\n var objStr = ( this.object )?( '' + this.object ):( this.get( 'element_identifier' ) );\n return ([ 'NestedListDCDCE(', objStr, ')' ].join( '' ));\n }\n}));\n\n\n//==============================================================================\n/** @class Backbone collection containing list dataset collections. */\nvar NestedListDCDCECollection = NestedDCDCECollection.extend({\n\n /** We know this collection is composed of only nested pair collections */\n model: NestedListDCDCE,\n\n /** String representation. */\n toString : function(){\n return ([ 'NestedListDCDCECollection(', this.length, ')' ].join( '' ));\n }\n});\n\n\n//==============================================================================\n/** @class Backbone Model for a DatasetCollection (list) that contains other lists. */\nvar ListOfListsDatasetCollection = DatasetCollection.extend({\n\n /** list:paired is the only collection that itself contains collections */\n collectionClass : NestedListDCDCECollection,\n\n /** String representation. */\n toString : function(){\n return ([ 'ListOfListsDatasetCollection(', this.get( 'name' ), ')' ].join( '' ));\n }\n});\n\n\n//==============================================================================\n return {\n ListDatasetCollection : ListDatasetCollection,\n PairDatasetCollection : PairDatasetCollection,\n ListPairedDatasetCollection : ListPairedDatasetCollection,\n ListOfListsDatasetCollection: ListOfListsDatasetCollection\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/collection/collection-model.js\n ** module id = 30\n ** module chunks = 3\n **/","\ndefine([\n \"mvc/history/hdca-model\",\n \"mvc/dataset/states\",\n \"mvc/base-mvc\",\n \"mvc/ui/ui-modal\",\n \"utils/natural-sort\",\n \"utils/localization\",\n \"ui/hoverhighlight\"\n], function( HDCA, STATES, BASE_MVC, UI_MODAL, naturalSort, _l ){\n\n'use strict';\n\nvar logNamespace = 'collections';\n/*==============================================================================\nTODO:\n use proper Element model and not just json\n straighten out createFn, collection.createHDCA\n possibly stop using modals for this\n It would be neat to do a drag and drop\n\n==============================================================================*/\n/** A view for both DatasetDCEs and NestedDCDCEs\n * (things that implement collection-model:DatasetCollectionElementMixin)\n */\nvar DatasetCollectionElementView = Backbone.View.extend( BASE_MVC.LoggableMixin ).extend({\n _logNamespace : logNamespace,\n\n//TODO: use proper class (DatasetDCE or NestedDCDCE (or the union of both))\n tagName : 'li',\n className : 'collection-element',\n\n initialize : function( attributes ){\n this.element = attributes.element || {};\n this.selected = attributes.selected || false;\n },\n\n render : function(){\n this.$el\n .attr( 'data-element-id', this.element.id )\n .attr( 'draggable', true )\n .html( this.template({ element: this.element }) );\n if( this.selected ){\n this.$el.addClass( 'selected' );\n }\n return this;\n },\n\n //TODO: lots of unused space in the element - possibly load details and display them horiz.\n template : _.template([\n '',\n '<%- element.name %>',\n '',\n '',\n ].join('')),\n\n /** select this element and pub */\n select : function( toggle ){\n this.$el.toggleClass( 'selected', toggle );\n this.trigger( 'select', {\n source : this,\n selected : this.$el.hasClass( 'selected' )\n });\n },\n\n /** animate the removal of this element and pub */\n discard : function(){\n var view = this,\n parentWidth = this.$el.parent().width();\n this.$el.animate({ 'margin-right' : parentWidth }, 'fast', function(){\n view.trigger( 'discard', {\n source : view\n });\n view.destroy();\n });\n },\n\n /** remove the DOM and any listeners */\n destroy : function(){\n this.off();\n this.$el.remove();\n },\n\n events : {\n 'click' : '_click',\n 'click .name' : '_clickName',\n 'click .discard': '_clickDiscard',\n\n 'dragstart' : '_dragstart',\n 'dragend' : '_dragend',\n 'dragover' : '_sendToParent',\n 'drop' : '_sendToParent'\n },\n\n /** select when the li is clicked */\n _click : function( ev ){\n ev.stopPropagation();\n this.select( ev );\n },\n\n /** rename a pair when the name is clicked */\n _clickName : function( ev ){\n ev.stopPropagation();\n ev.preventDefault();\n var promptString = [ _l( 'Enter a new name for the element' ), ':\\n(',\n _l( 'Note that changing the name here will not rename the dataset' ), ')' ].join( '' ),\n response = prompt( _l( 'Enter a new name for the element' ) + ':', this.element.name );\n if( response ){\n this.element.name = response;\n this.render();\n }\n //TODO: cancelling with ESC leads to closure of the creator...\n },\n\n /** discard when the discard button is clicked */\n _clickDiscard : function( ev ){\n ev.stopPropagation();\n this.discard();\n },\n\n /** dragging pairs for re-ordering */\n _dragstart : function( ev ){\n if( ev.originalEvent ){ ev = ev.originalEvent; }\n ev.dataTransfer.effectAllowed = 'move';\n ev.dataTransfer.setData( 'text/plain', JSON.stringify( this.element ) );\n\n this.$el.addClass( 'dragging' );\n this.$el.parent().trigger( 'collection-element.dragstart', [ this ] );\n },\n\n /** dragging for re-ordering */\n _dragend : function( ev ){\n this.$el.removeClass( 'dragging' );\n this.$el.parent().trigger( 'collection-element.dragend', [ this ] );\n },\n\n /** manually bubble up an event to the parent/container */\n _sendToParent : function( ev ){\n this.$el.parent().trigger( ev );\n },\n\n /** string rep */\n toString : function(){\n return 'DatasetCollectionElementView()';\n }\n});\n\n\n// ============================================================================\n/** An interface for building collections.\n */\nvar ListCollectionCreator = Backbone.View.extend( BASE_MVC.LoggableMixin ).extend({\n _logNamespace : logNamespace,\n\n /** the class used to display individual elements */\n elementViewClass : DatasetCollectionElementView,\n /** the class this creator will create and save */\n collectionClass : HDCA.HistoryListDatasetCollection,\n className : 'list-collection-creator collection-creator flex-row-container',\n\n /** minimum number of valid elements to start with in order to build a collection of this type */\n minElements : 1,\n\n defaultAttributes : {\n//TODO: remove - use new collectionClass().save()\n /** takes elements and creates the proper collection - returns a promise */\n creationFn : function(){ throw new TypeError( 'no creation fn for creator' ); },\n /** fn to call when the collection is created (scoped to this) */\n oncreate : function(){},\n /** fn to call when the cancel button is clicked (scoped to this) - if falsy, no btn is displayed */\n oncancel : function(){},\n /** distance from list edge to begin autoscrolling list */\n autoscrollDist : 24,\n /** Color passed to hoverhighlight */\n highlightClr : 'rgba( 64, 255, 255, 1.0 )'\n },\n\n /** set up initial options, instance vars, behaviors */\n initialize : function( attributes ){\n this.metric( 'ListCollectionCreator.initialize', attributes );\n var creator = this;\n _.each( this.defaultAttributes, function( value, key ){\n value = attributes[ key ] || value;\n creator[ key ] = value;\n });\n\n /** unordered, original list - cache to allow reversal */\n creator.initialElements = attributes.elements || [];\n\n this._instanceSetUp();\n this._elementsSetUp();\n this._setUpBehaviors();\n },\n\n /** set up instance vars */\n _instanceSetUp : function(){\n /** Ids of elements that have been selected by the user - to preserve over renders */\n this.selectedIds = {};\n /** DOM elements currently being dragged */\n this.$dragging = null;\n /** Used for blocking UI events during ajax/operations (don't post twice) */\n this.blocking = false;\n },\n\n // ------------------------------------------------------------------------ process raw list\n /** set up main data */\n _elementsSetUp : function(){\n //this.debug( '-- _dataSetUp' );\n /** a list of invalid elements and the reasons they aren't valid */\n this.invalidElements = [];\n//TODO: handle fundamental problem of syncing DOM, views, and list here\n /** data for list in progress */\n this.workingElements = [];\n /** views for workingElements */\n this.elementViews = [];\n\n // copy initial list, sort, add ids if needed\n this.workingElements = this.initialElements.slice( 0 );\n this._ensureElementIds();\n this._validateElements();\n this._mangleDuplicateNames();\n this._sortElements();\n },\n\n /** add ids to dataset objs in initial list if none */\n _ensureElementIds : function(){\n this.workingElements.forEach( function( element ){\n if( !element.hasOwnProperty( 'id' ) ){\n element.id = _.uniqueId();\n }\n });\n return this.workingElements;\n },\n\n /** separate working list into valid and invalid elements for this collection */\n _validateElements : function(){\n var creator = this,\n existingNames = {};\n creator.invalidElements = [];\n\n this.workingElements = this.workingElements.filter( function( element ){\n var problem = creator._isElementInvalid( element );\n if( problem ){\n creator.invalidElements.push({\n element : element,\n text : problem\n });\n }\n return !problem;\n });\n return this.workingElements;\n },\n\n /** describe what is wrong with a particular element if anything */\n _isElementInvalid : function( element ){\n if( element.history_content_type !== 'dataset' ){\n return _l( \"is not a dataset\" );\n }\n if( element.state !== STATES.OK ){\n if( _.contains( STATES.NOT_READY_STATES, element.state ) ){\n return _l( \"hasn't finished running yet\" );\n }\n return _l( \"has errored, is paused, or is not accessible\" );\n }\n if( element.deleted || element.purged ){\n return _l( \"has been deleted or purged\" );\n }\n return null;\n },\n\n /** mangle duplicate names using a mac-like '(counter)' addition to any duplicates */\n _mangleDuplicateNames : function(){\n var SAFETY = 900,\n counter = 1,\n existingNames = {};\n this.workingElements.forEach( function( element ){\n var currName = element.name;\n while( existingNames.hasOwnProperty( currName ) ){\n currName = element.name + ' (' + counter + ')';\n counter += 1;\n if( counter >= SAFETY ){\n throw new Error( 'Safety hit in while loop - thats impressive' );\n }\n }\n element.name = currName;\n existingNames[ element.name ] = true;\n });\n },\n\n /** sort a list of elements */\n _sortElements : function( list ){\n // // currently only natural sort by name\n // this.workingElements.sort( function( a, b ){ return naturalSort( a.name, b.name ); });\n // return this.workingElements;\n },\n\n // ------------------------------------------------------------------------ rendering\n // templates : ListCollectionCreator.templates,\n /** render the entire interface */\n render : function( speed, callback ){\n //this.debug( '-- _render' );\n if( this.workingElements.length < this.minElements ){\n return this._renderInvalid( speed, callback );\n }\n\n this.$el.empty().html( this.templates.main() );\n this._renderHeader( speed );\n this._renderMiddle( speed );\n this._renderFooter( speed );\n this._addPluginComponents();\n this.$( '.collection-name' ).focus();\n this.trigger( 'rendered', this );\n return this;\n },\n\n\n /** render a simplified interface aimed at telling the user why they can't move forward */\n _renderInvalid : function( speed, callback ){\n //this.debug( '-- _render' );\n this.$el.empty().html( this.templates.invalidInitial({\n problems: this.invalidElements,\n elements: this.workingElements,\n }));\n if( typeof this.oncancel === 'function' ){\n this.$( '.cancel-create.btn' ).show();\n }\n this.trigger( 'rendered', this );\n return this;\n },\n\n /** render the header section */\n _renderHeader : function( speed, callback ){\n var $header = this.$( '.header' ).empty().html( this.templates.header() )\n .find( '.help-content' ).prepend( $( this.templates.helpContent() ) );\n //TODO: should only show once despite calling _renderHeader again\n if( this.invalidElements.length ){\n this._invalidElementsAlert();\n }\n return $header;\n },\n\n /** render the middle including the elements */\n _renderMiddle : function( speed, callback ){\n var $middle = this.$( '.middle' ).empty().html( this.templates.middle() );\n this._renderList( speed );\n return $middle;\n },\n\n /** render the footer, completion controls, and cancel controls */\n _renderFooter : function( speed, callback ){\n var $footer = this.$( '.footer' ).empty().html( this.templates.footer() );\n if( typeof this.oncancel === 'function' ){\n this.$( '.cancel-create.btn' ).show();\n }\n return $footer;\n },\n\n /** add any jQuery/bootstrap/custom plugins to elements rendered */\n _addPluginComponents : function(){\n this.$( '.help-content i' ).hoverhighlight( '.collection-creator', this.highlightClr );\n },\n\n /** build and show an alert describing any elements that could not be included due to problems */\n _invalidElementsAlert : function(){\n this._showAlert( this.templates.invalidElements({ problems: this.invalidElements }), 'alert-warning' );\n },\n\n /** add (or clear if clear is truthy) a validation warning to the DOM element described in what */\n _validationWarning : function( what, clear ){\n var VALIDATION_CLASS = 'validation-warning';\n if( what === 'name' ){\n what = this.$( '.collection-name' ).add( this.$( '.collection-name-prompt' ) );\n this.$( '.collection-name' ).focus().select();\n }\n if( clear ){\n what = what || this.$( '.' + VALIDATION_CLASS );\n what.removeClass( VALIDATION_CLASS );\n } else {\n what.addClass( VALIDATION_CLASS );\n }\n },\n\n _disableNameAndCreate : function( disable ){\n disable = !_.isUndefined( disable )? disable : true;\n if( disable ){\n this.$( '.collection-name' ).prop( 'disabled', true );\n this.$( '.create-collection' ).toggleClass( 'disabled', true );\n // } else {\n // this.$( '.collection-name' ).prop( 'disabled', false );\n // this.$( '.create-collection' ).removeClass( 'disable' );\n }\n },\n\n // ------------------------------------------------------------------------ rendering elements\n /** conv. to the main list display DOM */\n $list : function(){\n return this.$( '.collection-elements' );\n },\n\n /** show or hide the clear selected control based on the num of selected elements */\n _renderClearSelected : function(){\n if( _.size( this.selectedIds ) ){\n this.$( '.collection-elements-controls > .clear-selected' ).show();\n } else {\n this.$( '.collection-elements-controls > .clear-selected' ).hide();\n }\n },\n\n /** render the elements in order (or a warning if no elements found) */\n _renderList : function( speed, callback ){\n //this.debug( '-- _renderList' );\n var creator = this,\n $tmp = jQuery( '
                                  ' ),\n $list = creator.$list();\n\n _.each( this.elementViews, function( view ){\n view.destroy();\n creator.removeElementView( view );\n });\n\n // if( !this.workingElements.length ){\n // this._renderNoValidElements();\n // return;\n // }\n\n creator.workingElements.forEach( function( element ){\n var elementView = creator._createElementView( element );\n $tmp.append( elementView.$el );\n });\n\n creator._renderClearSelected();\n $list.empty().append( $tmp.children() );\n _.invoke( creator.elementViews, 'render' );\n\n if( $list.height() > $list.css( 'max-height' ) ){\n $list.css( 'border-width', '1px 0px 1px 0px' );\n } else {\n $list.css( 'border-width', '0px' );\n }\n },\n\n /** create an element view, cache in elementViews, set up listeners, and return */\n _createElementView : function( element ){\n var elementView = new this.elementViewClass({\n//TODO: use non-generic class or not all\n // model : COLLECTION.DatasetDCE( element )\n element : element,\n selected: _.has( this.selectedIds, element.id )\n });\n this.elementViews.push( elementView );\n this._listenToElementView( elementView );\n return elementView;\n },\n\n /** listen to any element events */\n _listenToElementView : function( view ){\n var creator = this;\n creator.listenTo( view, {\n select : function( data ){\n var element = data.source.element;\n if( data.selected ){\n creator.selectedIds[ element.id ] = true;\n } else {\n delete creator.selectedIds[ element.id ];\n }\n creator.trigger( 'elements:select', data );\n },\n discard : function( data ){\n creator.trigger( 'elements:discard', data );\n }\n });\n },\n\n /** add a new element view based on the json in element */\n addElementView : function( element ){\n//TODO: workingElements is sorted, add element in appropo index\n // add element, sort elements, find element index\n // var view = this._createElementView( element );\n // return view;\n },\n\n /** stop listening to view and remove from caches */\n removeElementView : function( view ){\n delete this.selectedIds[ view.element.id ];\n this._renderClearSelected();\n\n this.elementViews = _.without( this.elementViews, view );\n this.stopListening( view );\n },\n\n /** render a message in the list that no elements remain to create a collection */\n _renderNoElementsLeft : function(){\n this._disableNameAndCreate( true );\n this.$( '.collection-elements' ).append( this.templates.noElementsLeft() );\n },\n\n // /** render a message in the list that no valid elements were found to create a collection */\n // _renderNoValidElements : function(){\n // this._disableNameAndCreate( true );\n // this.$( '.collection-elements' ).append( this.templates.noValidElements() );\n // },\n\n // ------------------------------------------------------------------------ API\n /** convert element into JSON compatible with the collections API */\n _elementToJSON : function( element ){\n // return element.toJSON();\n return element;\n },\n\n /** create the collection via the API\n * @returns {jQuery.xhr Object} the jquery ajax request\n */\n createList : function( name ){\n if( !this.workingElements.length ){\n var message = _l( 'No valid elements for final list' ) + '. ';\n message += '' + _l( 'Cancel' ) + ' ';\n message += _l( 'or' );\n message += ' ' + _l( 'start over' ) + '.';\n this._showAlert( message );\n return;\n }\n\n var creator = this,\n elements = this.workingElements.map( function( element ){\n return creator._elementToJSON( element );\n });\n\n creator.blocking = true;\n return creator.creationFn( elements, name )\n .always( function(){\n creator.blocking = false;\n })\n .fail( function( xhr, status, message ){\n creator.trigger( 'error', {\n xhr : xhr,\n status : status,\n message : _l( 'An error occurred while creating this collection' )\n });\n })\n .done( function( response, message, xhr ){\n creator.trigger( 'collection:created', response, message, xhr );\n creator.metric( 'collection:created', response );\n if( typeof creator.oncreate === 'function' ){\n creator.oncreate.call( this, response, message, xhr );\n }\n });\n },\n\n // ------------------------------------------------------------------------ events\n /** set up event handlers on self */\n _setUpBehaviors : function(){\n this.on( 'error', this._errorHandler );\n\n this.once( 'rendered', function(){\n this.trigger( 'rendered:initial', this );\n });\n\n this.on( 'elements:select', function( data ){\n this._renderClearSelected();\n });\n\n this.on( 'elements:discard', function( data ){\n var element = data.source.element;\n this.removeElementView( data.source );\n\n this.workingElements = _.without( this.workingElements, element );\n if( !this.workingElements.length ){\n this._renderNoElementsLeft();\n }\n });\n\n //this.on( 'all', function(){\n // this.info( arguments );\n //});\n return this;\n },\n\n /** handle errors with feedback and details to the user (if available) */\n _errorHandler : function( data ){\n this.error( data );\n\n var creator = this;\n content = data.message || _l( 'An error occurred' );\n if( data.xhr ){\n var xhr = data.xhr,\n message = data.message;\n if( xhr.readyState === 0 && xhr.status === 0 ){\n content += ': ' + _l( 'Galaxy could not be reached and may be updating.' ) +\n _l( ' Try again in a few minutes.' );\n } else if( xhr.responseJSON ){\n content += ':
                                  ' + JSON.stringify( xhr.responseJSON ) + '
                                  ';\n } else {\n content += ': ' + message;\n }\n }\n creator._showAlert( content, 'alert-danger' );\n },\n\n events : {\n // header\n 'click .more-help' : '_clickMoreHelp',\n 'click .less-help' : '_clickLessHelp',\n 'click .main-help' : '_toggleHelp',\n 'click .header .alert button' : '_hideAlert',\n\n 'click .reset' : 'reset',\n 'click .clear-selected' : 'clearSelectedElements',\n\n // elements - selection\n 'click .collection-elements' : 'clearSelectedElements',\n\n // elements - drop target\n // 'dragenter .collection-elements': '_dragenterElements',\n // 'dragleave .collection-elements': '_dragleaveElements',\n 'dragover .collection-elements' : '_dragoverElements',\n 'drop .collection-elements' : '_dropElements',\n\n // these bubble up from the elements as custom events\n 'collection-element.dragstart .collection-elements' : '_elementDragstart',\n 'collection-element.dragend .collection-elements' : '_elementDragend',\n\n // footer\n 'change .collection-name' : '_changeName',\n 'keydown .collection-name' : '_nameCheckForEnter',\n 'click .cancel-create' : function( ev ){\n if( typeof this.oncancel === 'function' ){\n this.oncancel.call( this );\n }\n },\n 'click .create-collection' : '_clickCreate'//,\n },\n\n // ........................................................................ header\n /** expand help */\n _clickMoreHelp : function( ev ){\n ev.stopPropagation();\n this.$( '.main-help' ).addClass( 'expanded' );\n this.$( '.more-help' ).hide();\n },\n /** collapse help */\n _clickLessHelp : function( ev ){\n ev.stopPropagation();\n this.$( '.main-help' ).removeClass( 'expanded' );\n this.$( '.more-help' ).show();\n },\n /** toggle help */\n _toggleHelp : function( ev ){\n ev.stopPropagation();\n this.$( '.main-help' ).toggleClass( 'expanded' );\n this.$( '.more-help' ).toggle();\n },\n\n /** show an alert on the top of the interface containing message (alertClass is bootstrap's alert-*) */\n _showAlert : function( message, alertClass ){\n alertClass = alertClass || 'alert-danger';\n this.$( '.main-help' ).hide();\n this.$( '.header .alert' )\n .attr( 'class', 'alert alert-dismissable' ).addClass( alertClass ).show()\n .find( '.alert-message' ).html( message );\n },\n /** hide the alerts at the top */\n _hideAlert : function( message ){\n this.$( '.main-help' ).show();\n this.$( '.header .alert' ).hide();\n },\n\n // ........................................................................ elements\n /** reset all data to the initial state */\n reset : function(){\n this._instanceSetUp();\n this._elementsSetUp();\n this.render();\n },\n\n /** deselect all elements */\n clearSelectedElements : function( ev ){\n this.$( '.collection-elements .collection-element' ).removeClass( 'selected' );\n this.$( '.collection-elements-controls > .clear-selected' ).hide();\n },\n\n //_dragenterElements : function( ev ){\n // //this.debug( '_dragenterElements:', ev );\n //},\n//TODO: if selected are dragged out of the list area - remove the placeholder - cuz it won't work anyway\n // _dragleaveElements : function( ev ){\n // //this.debug( '_dragleaveElements:', ev );\n // },\n\n /** track the mouse drag over the list adding a placeholder to show where the drop would occur */\n _dragoverElements : function( ev ){\n //this.debug( '_dragoverElements:', ev );\n ev.preventDefault();\n\n var $list = this.$list();\n this._checkForAutoscroll( $list, ev.originalEvent.clientY );\n var $nearest = this._getNearestElement( ev.originalEvent.clientY );\n\n //TODO: no need to re-create - move instead\n this.$( '.element-drop-placeholder' ).remove();\n var $placeholder = $( '
                                  ' );\n if( !$nearest.length ){\n $list.append( $placeholder );\n } else {\n $nearest.before( $placeholder );\n }\n },\n\n /** If the mouse is near enough to the list's top or bottom, scroll the list */\n _checkForAutoscroll : function( $element, y ){\n var AUTOSCROLL_SPEED = 2,\n offset = $element.offset(),\n scrollTop = $element.scrollTop(),\n upperDist = y - offset.top,\n lowerDist = ( offset.top + $element.outerHeight() ) - y;\n if( upperDist >= 0 && upperDist < this.autoscrollDist ){\n $element.scrollTop( scrollTop - AUTOSCROLL_SPEED );\n } else if( lowerDist >= 0 && lowerDist < this.autoscrollDist ){\n $element.scrollTop( scrollTop + AUTOSCROLL_SPEED );\n }\n },\n\n /** get the nearest element based on the mouse's Y coordinate.\n * If the y is at the end of the list, return an empty jQuery object.\n */\n _getNearestElement : function( y ){\n var WIGGLE = 4,\n lis = this.$( '.collection-elements li.collection-element' ).toArray();\n for( var i=0; i y && top - halfHeight < y ){\n return $li;\n }\n }\n return $();\n },\n\n /** drop (dragged/selected elements) onto the list, re-ordering the internal list */\n _dropElements : function( ev ){\n if( ev.originalEvent ){ ev = ev.originalEvent; }\n // both required for firefox\n ev.preventDefault();\n ev.dataTransfer.dropEffect = 'move';\n\n // insert before the nearest element or after the last.\n var $nearest = this._getNearestElement( ev.clientY );\n if( $nearest.length ){\n this.$dragging.insertBefore( $nearest );\n } else {\n // no nearest before - insert after last element\n this.$dragging.insertAfter( this.$( '.collection-elements .collection-element' ).last() );\n }\n // resync the creator's list based on the new DOM order\n this._syncOrderToDom();\n return false;\n },\n\n /** resync the creator's list of elements based on the DOM order */\n _syncOrderToDom : function(){\n var creator = this,\n newElements = [];\n //TODO: doesn't seem wise to use the dom to store these - can't we sync another way?\n this.$( '.collection-elements .collection-element' ).each( function(){\n var id = $( this ).attr( 'data-element-id' ),\n element = _.findWhere( creator.workingElements, { id: id });\n if( element ){\n newElements.push( element );\n } else {\n console.error( 'missing element: ', id );\n }\n });\n this.workingElements = newElements;\n this._renderList();\n },\n\n /** drag communication with element sub-views: dragstart */\n _elementDragstart : function( ev, element ){\n // auto select the element causing the event and move all selected\n element.select( true );\n this.$dragging = this.$( '.collection-elements .collection-element.selected' );\n },\n\n /** drag communication with element sub-views: dragend - remove the placeholder */\n _elementDragend : function( ev, element ){\n $( '.element-drop-placeholder' ).remove();\n this.$dragging = null;\n },\n\n // ........................................................................ footer\n /** handle a collection name change */\n _changeName : function( ev ){\n this._validationWarning( 'name', !!this._getName() );\n },\n\n /** check for enter key press when in the collection name and submit */\n _nameCheckForEnter : function( ev ){\n if( ev.keyCode === 13 && !this.blocking ){\n this._clickCreate();\n }\n },\n\n /** get the current collection name */\n _getName : function(){\n return _.escape( this.$( '.collection-name' ).val() );\n },\n\n /** attempt to create the current collection */\n _clickCreate : function( ev ){\n var name = this._getName();\n if( !name ){\n this._validationWarning( 'name' );\n } else if( !this.blocking ){\n this.createList( name );\n }\n },\n\n // ------------------------------------------------------------------------ templates\n //TODO: move to require text plugin and load these as text\n //TODO: underscore currently unnecc. bc no vars are used\n //TODO: better way of localizing text-nodes in long strings\n /** underscore template fns attached to class */\n templates : {\n /** the skeleton */\n main : _.template([\n '
                                  ',\n '
                                  ',\n '
                                  '\n ].join('')),\n\n /** the header (not including help text) */\n header : _.template([\n '
                                  ',\n '', _l( 'More help' ), '',\n '
                                  ',\n '', _l( 'Less' ), '',\n '
                                  ',\n '
                                  ',\n '
                                  ',\n '',\n '',\n '
                                  ',\n ].join('')),\n\n /** the middle: element list */\n middle : _.template([\n '',\n '
                                  ',\n '
                                  '\n ].join('')),\n\n /** creation and cancel controls */\n footer : _.template([\n '
                                  ',\n '
                                  ',\n '',\n '
                                  ', _l( 'Name' ), ':
                                  ',\n '
                                  ',\n '
                                  ',\n\n '
                                  ',\n '
                                  ',\n '',\n '
                                  ',\n '',\n '',\n '
                                  ',\n '
                                  ',\n\n '
                                  ',\n '',\n '
                                  ',\n '
                                  '\n ].join('')),\n\n /** help content */\n helpContent : _.template([\n '

                                  ', _l([\n 'Collections of datasets are permanent, ordered lists of datasets that can be passed to tools and ',\n 'workflows in order to have analyses done on each member of the entire group. This interface allows ',\n 'you to create a collection and re-order the final collection.'\n ].join( '' )), '

                                  ',\n '
                                    ',\n '
                                  • ', _l([\n 'Rename elements in the list by clicking on ',\n 'the existing name.'\n ].join( '' )), '
                                  • ',\n '
                                  • ', _l([\n 'Discard elements from the final created list by clicking on the ',\n '\"Discard\" button.'\n ].join( '' )), '
                                  • ',\n '
                                  • ', _l([\n 'Reorder the list by clicking and dragging elements. Select multiple elements by clicking on ',\n 'them and you can then move those selected by dragging the ',\n 'entire group. Deselect them by clicking them again or by clicking the ',\n 'the \"Clear selected\" link.'\n ].join( '' )), '
                                  • ',\n '
                                  • ', _l([\n 'Click the \"Start over\" link to begin again as if you had just opened ',\n 'the interface.'\n ].join( '' )), '
                                  • ',\n '
                                  • ', _l([\n 'Click the \"Cancel\" button to exit the interface.'\n ].join( '' )), '
                                  • ',\n '

                                  ',\n '

                                  ', _l([\n 'Once your collection is complete, enter a name and ',\n 'click \"Create list\".'\n ].join( '' )), '

                                  '\n ].join('')),\n\n /** shown in list when all elements are discarded */\n invalidElements : _.template([\n _l( 'The following selections could not be included due to problems:' ),\n '
                                    <% _.each( problems, function( problem ){ %>',\n '
                                  • <%- problem.element.name %>: <%- problem.text %>
                                  • ',\n '<% }); %>
                                  '\n ].join('')),\n\n /** shown in list when all elements are discarded */\n noElementsLeft : _.template([\n '
                                • ',\n _l( 'No elements left! ' ),\n _l( 'Would you like to ' ), '', _l( 'start over' ), '?',\n '
                                • '\n ].join('')),\n\n /** a simplified page communicating what went wrong and why the user needs to reselect something else */\n invalidInitial : _.template([\n '
                                  ',\n '
                                  ',\n '',\n '<% if( _.size( problems ) ){ %>',\n _l( 'The following selections could not be included due to problems' ), ':',\n '
                                    <% _.each( problems, function( problem ){ %>',\n '
                                  • <%- problem.element.name %>: <%- problem.text %>
                                  • ',\n '<% }); %>
                                  ',\n '<% } else if( _.size( elements ) < 1 ){ %>',\n _l( 'No datasets were selected' ), '.',\n '<% } %>',\n '
                                  ',\n _l( 'At least one element is needed for the collection' ), '. ',\n _l( 'You may need to ' ),\n '', _l( 'cancel' ), ' ',\n _l( 'and reselect new elements' ), '.',\n '
                                  ',\n '
                                  ',\n '
                                  ',\n '
                                  ',\n '
                                  ',\n '
                                  ',\n '',\n // _l( 'Create a different kind of collection' ),\n '
                                  ',\n '
                                  ',\n '
                                  '\n ].join('')),\n },\n\n // ------------------------------------------------------------------------ misc\n /** string rep */\n toString : function(){ return 'ListCollectionCreator'; }\n});\n\n\n\n//=============================================================================\n/** Create a modal and load its body with the given CreatorClass creator type\n * @returns {Deferred} resolved when creator has built a collection.\n */\nvar collectionCreatorModal = function _collectionCreatorModal( elements, options, CreatorClass ){\n\n var deferred = jQuery.Deferred(),\n modal = Galaxy.modal || ( new UI_MODAL.View() ),\n creator;\n\n options = _.defaults( options || {}, {\n elements : elements,\n oncancel : function(){\n modal.hide();\n deferred.reject( 'cancelled' );\n },\n oncreate : function( creator, response ){\n modal.hide();\n deferred.resolve( response );\n }\n });\n\n creator = new CreatorClass( options );\n modal.show({\n title : options.title || _l( 'Create a collection' ),\n body : creator.$el,\n width : '80%',\n height : '100%',\n closing_events: true\n });\n creator.render();\n window._collectionCreator = creator;\n\n //TODO: remove modal header\n return deferred;\n};\n\n/** List collection flavor of collectionCreatorModal. */\nvar listCollectionCreatorModal = function _listCollectionCreatorModal( elements, options ){\n options = options || {};\n options.title = _l( 'Create a collection from a list of datasets' );\n return collectionCreatorModal( elements, options, ListCollectionCreator );\n};\n\n\n//==============================================================================\n/** Use a modal to create a list collection, then add it to the given history contents.\n * @returns {Deferred} resolved when the collection is added to the history.\n */\nfunction createListCollection( contents ){\n var elements = contents.toJSON(),\n promise = listCollectionCreatorModal( elements, {\n creationFn : function( elements, name ){\n elements = elements.map( function( element ){\n return {\n id : element.id,\n name : element.name,\n //TODO: this allows for list:list even if the filter above does not - reconcile\n src : ( element.history_content_type === 'dataset'? 'hda' : 'hdca' )\n };\n });\n return contents.createHDCA( elements, 'list', name );\n }\n });\n return promise;\n}\n\n//==============================================================================\n return {\n DatasetCollectionElementView: DatasetCollectionElementView,\n ListCollectionCreator : ListCollectionCreator,\n\n collectionCreatorModal : collectionCreatorModal,\n listCollectionCreatorModal : listCollectionCreatorModal,\n createListCollection : createListCollection\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/collection/list-collection-creator.js\n ** module id = 31\n ** module chunks = 3\n **/","define([\n \"mvc/list/list-item\",\n \"mvc/dataset/states\",\n \"ui/fa-icon-button\",\n \"mvc/base-mvc\",\n \"utils/localization\"\n], function( LIST_ITEM, STATES, faIconButton, BASE_MVC, _l ){\n'use strict';\n\nvar logNamespace = 'dataset';\n/*==============================================================================\nTODO:\n straighten out state rendering and templates used\n inaccessible/STATES.NOT_VIEWABLE is a special case\n simplify button rendering\n\n==============================================================================*/\nvar _super = LIST_ITEM.ListItemView;\n/** @class Read only list view for either LDDAs, HDAs, or HDADCEs.\n * Roughly, any DatasetInstance (and not a raw Dataset).\n */\nvar DatasetListItemView = _super.extend(\n/** @lends DatasetListItemView.prototype */{\n _logNamespace : logNamespace,\n\n className : _super.prototype.className + \" dataset\",\n //TODO:?? doesn't exactly match an hda's type_id\n id : function(){\n return [ 'dataset', this.model.get( 'id' ) ].join( '-' );\n },\n\n /** Set up: instance vars, options, and event handlers */\n initialize : function( attributes ){\n if( attributes.logger ){ this.logger = this.model.logger = attributes.logger; }\n this.log( this + '.initialize:', attributes );\n _super.prototype.initialize.call( this, attributes );\n\n /** where should pages from links be displayed? (default to new tab/window) */\n this.linkTarget = attributes.linkTarget || '_blank';\n },\n\n /** event listeners */\n _setUpListeners : function(){\n _super.prototype._setUpListeners.call( this );\n var self = this;\n\n // re-rendering on any model changes\n return self.listenTo( self.model, {\n 'change': function( model, options ){\n // if the model moved into the ready state and is expanded without details, fetch those details now\n if( self.model.changedAttributes().state\n && self.model.inReadyState()\n && self.expanded\n && !self.model.hasDetails() ){\n // normally, will render automatically (due to fetch -> change),\n // but! setting_metadata sometimes doesn't cause any other changes besides state\n // so, not rendering causes it to seem frozen in setting_metadata state\n self.model.fetch({ silent : true })\n .done( function(){ self.render(); });\n\n } else {\n self.render();\n }\n }\n });\n },\n\n // ......................................................................... expandable\n /** In this override, only get details if in the ready state, get rerunnable if in other states.\n * Note: fetch with no 'change' event triggering to prevent automatic rendering.\n */\n _fetchModelDetails : function(){\n var view = this;\n if( view.model.inReadyState() && !view.model.hasDetails() ){\n return view.model.fetch({ silent: true });\n }\n return jQuery.when();\n },\n\n // ......................................................................... removal\n /** Remove this view's html from the DOM and remove all event listeners.\n * @param {Number or String} speed jq effect speed\n * @param {Function} callback an optional function called when removal is done (scoped to this view)\n */\n remove : function( speed, callback ){\n var view = this;\n speed = speed || this.fxSpeed;\n this.$el.fadeOut( speed, function(){\n Backbone.View.prototype.remove.call( view );\n if( callback ){ callback.call( view ); }\n });\n },\n\n // ......................................................................... rendering\n /* TODO:\n dataset states are the issue primarily making dataset rendering complex\n each state should have it's own way of displaying/set of details\n often with different actions that can be applied\n throw in deleted/purged/visible and things get complicated easily\n I've considered (a couple of times) - creating a view for each state\n - but recreating the view during an update...seems wrong\n */\n /** In this override, add the dataset state as a class for use with state-based CSS */\n _swapNewRender : function( $newRender ){\n _super.prototype._swapNewRender.call( this, $newRender );\n if( this.model.has( 'state' ) ){\n this.$el.addClass( 'state-' + this.model.get( 'state' ) );\n }\n return this.$el;\n },\n\n // ................................................................................ titlebar\n /** In this override, add the dataset display button. */\n _renderPrimaryActions : function(){\n // render just the display for read-only\n return [ this._renderDisplayButton() ];\n },\n\n /** Render icon-button to display dataset data */\n _renderDisplayButton : function(){\n // don't show display if not viewable or not accessible\n var state = this.model.get( 'state' );\n if( ( state === STATES.NOT_VIEWABLE )\n || ( state === STATES.DISCARDED )\n || ( !this.model.get( 'accessible' ) ) ){\n return null;\n }\n\n var displayBtnData = {\n target : this.linkTarget,\n classes : 'display-btn'\n };\n\n // show a disabled display if the data's been purged\n if( this.model.get( 'purged' ) ){\n displayBtnData.disabled = true;\n displayBtnData.title = _l( 'Cannot display datasets removed from disk' );\n\n // disable if still uploading\n } else if( state === STATES.UPLOAD ){\n displayBtnData.disabled = true;\n displayBtnData.title = _l( 'This dataset must finish uploading before it can be viewed' );\n\n // disable if still new\n } else if( state === STATES.NEW ){\n displayBtnData.disabled = true;\n displayBtnData.title = _l( 'This dataset is not yet viewable' );\n\n } else {\n displayBtnData.title = _l( 'View data' );\n\n // default link for dataset\n displayBtnData.href = this.model.urls.display;\n\n // add frame manager option onclick event\n var self = this;\n displayBtnData.onclick = function( ev ){\n if (Galaxy.frame && Galaxy.frame.active) {\n // Add dataset to frames.\n Galaxy.frame.addDataset(self.model.get('id'));\n ev.preventDefault();\n }\n };\n }\n displayBtnData.faIcon = 'fa-eye';\n return faIconButton( displayBtnData );\n },\n\n // ......................................................................... rendering details\n /** Render the enclosing div of the hda body and, if expanded, the html in the body\n * @returns {jQuery} rendered DOM\n */\n _renderDetails : function(){\n //TODO: generalize to be allow different details for each state\n\n // no access - render nothing but a message\n if( this.model.get( 'state' ) === STATES.NOT_VIEWABLE ){\n return $( this.templates.noAccess( this.model.toJSON(), this ) );\n }\n\n var $details = _super.prototype._renderDetails.call( this );\n $details.find( '.actions .left' ).empty().append( this._renderSecondaryActions() );\n $details.find( '.summary' ).html( this._renderSummary() )\n .prepend( this._renderDetailMessages() );\n $details.find( '.display-applications' ).html( this._renderDisplayApplications() );\n\n this._setUpBehaviors( $details );\n return $details;\n },\n\n /** Defer to the appropo summary rendering fn based on state */\n _renderSummary : function(){\n var json = this.model.toJSON(),\n summaryRenderFn = this.templates.summaries[ json.state ];\n summaryRenderFn = summaryRenderFn || this.templates.summaries.unknown;\n return summaryRenderFn( json, this );\n },\n\n /** Render messages to be displayed only when the details are shown */\n _renderDetailMessages : function(){\n var view = this,\n $warnings = $( '
                                  ' ),\n json = view.model.toJSON();\n //TODO:! unordered (map)\n _.each( view.templates.detailMessages, function( templateFn ){\n $warnings.append( $( templateFn( json, view ) ) );\n });\n return $warnings;\n },\n\n /** Render the external display application links */\n _renderDisplayApplications : function(){\n if( this.model.isDeletedOrPurged() ){ return ''; }\n // render both old and new display apps using the same template\n return [\n this.templates.displayApplications( this.model.get( 'display_apps' ), this ),\n this.templates.displayApplications( this.model.get( 'display_types' ), this )\n ].join( '' );\n },\n\n // ......................................................................... secondary/details actions\n /** A series of links/buttons for less commonly used actions: re-run, info, etc. */\n _renderSecondaryActions : function(){\n this.debug( '_renderSecondaryActions' );\n switch( this.model.get( 'state' ) ){\n case STATES.NOT_VIEWABLE:\n return [];\n case STATES.OK:\n case STATES.FAILED_METADATA:\n case STATES.ERROR:\n return [ this._renderDownloadButton(), this._renderShowParamsButton() ];\n }\n return [ this._renderShowParamsButton() ];\n },\n\n /** Render icon-button to show the input and output (stdout/err) for the job that created this.\n * @returns {jQuery} rendered DOM\n */\n _renderShowParamsButton : function(){\n // gen. safe to show in all cases\n return faIconButton({\n title : _l( 'View details' ),\n classes : 'params-btn',\n href : this.model.urls.show_params,\n target : this.linkTarget,\n faIcon : 'fa-info-circle',\n onclick : function( ev ) {\n if ( Galaxy.frame && Galaxy.frame.active ) {\n Galaxy.frame.add( { title: 'Dataset details', url: this.href } );\n ev.preventDefault();\n ev.stopPropagation();\n }\n }\n });\n },\n\n /** Render icon-button/popupmenu to download the data (and/or the associated meta files (bai, etc.)) for this.\n * @returns {jQuery} rendered DOM\n */\n _renderDownloadButton : function(){\n // don't show anything if the data's been purged\n if( this.model.get( 'purged' ) || !this.model.hasData() ){ return null; }\n\n // return either: a popupmenu with links to download assoc. meta files (if there are meta files)\n // or a single download icon-button (if there are no meta files)\n if( !_.isEmpty( this.model.get( 'meta_files' ) ) ){\n return this._renderMetaFileDownloadButton();\n }\n\n return $([\n '',\n '',\n ''\n ].join( '' ));\n },\n\n /** Render the download button which opens a dropdown with links to download assoc. meta files (indeces, etc.) */\n _renderMetaFileDownloadButton : function(){\n var urls = this.model.urls;\n return $([\n '
                                  ',\n '',\n '',\n '',\n '',\n '
                                  '\n ].join( '\\n' ));\n },\n\n // ......................................................................... misc\n events : _.extend( _.clone( _super.prototype.events ), {\n 'click .display-btn' : function( ev ){ this.trigger( 'display', this, ev ); },\n 'click .params-btn' : function( ev ){ this.trigger( 'params', this, ev ); },\n 'click .download-btn' : function( ev ){ this.trigger( 'download', this, ev ); }\n }),\n\n // ......................................................................... misc\n /** String representation */\n toString : function(){\n var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n return 'DatasetListItemView(' + modelString + ')';\n }\n});\n\n// ............................................................................ TEMPLATES\n/** underscore templates */\nDatasetListItemView.prototype.templates = (function(){\n//TODO: move to require text! plugin\n\n var warnings = _.extend( {}, _super.prototype.templates.warnings, {\n failed_metadata : BASE_MVC.wrapTemplate([\n // failed metadata is rendered as a warning on an otherwise ok dataset view\n '<% if( model.state === \"failed_metadata\" ){ %>',\n '
                                  ',\n _l( 'An error occurred setting the metadata for this dataset' ),\n '
                                  ',\n '<% } %>'\n ]),\n error : BASE_MVC.wrapTemplate([\n // error during index fetch - show error on dataset\n '<% if( model.error ){ %>',\n '
                                  ',\n _l( 'There was an error getting the data for this dataset' ), ': <%- model.error %>',\n '
                                  ',\n '<% } %>'\n ]),\n purged : BASE_MVC.wrapTemplate([\n '<% if( model.purged ){ %>',\n '
                                  ',\n _l( 'This dataset has been deleted and removed from disk' ),\n '
                                  ',\n '<% } %>'\n ]),\n deleted : BASE_MVC.wrapTemplate([\n // deleted not purged\n '<% if( model.deleted && !model.purged ){ %>',\n '
                                  ',\n _l( 'This dataset has been deleted' ),\n '
                                  ',\n '<% } %>'\n ])\n\n //NOTE: hidden warning is only needed for HDAs\n });\n\n var detailsTemplate = BASE_MVC.wrapTemplate([\n '
                                  ',\n '
                                  ',\n\n '
                                  ',\n '
                                  ',\n '
                                  ',\n '
                                  ',\n\n // do not display tags, annotation, display apps, or peek when deleted\n '<% if( !dataset.deleted && !dataset.purged ){ %>',\n '
                                  ',\n '
                                  ',\n\n '
                                  ',\n\n '<% if( dataset.peek ){ %>',\n '
                                  <%= dataset.peek %>
                                  ',\n '<% } %>',\n '<% } %>',\n '
                                  '\n ], 'dataset' );\n\n var noAccessTemplate = BASE_MVC.wrapTemplate([\n '
                                  ',\n '
                                  ',\n _l( 'You do not have permission to view this dataset' ),\n '
                                  ',\n '
                                  '\n ], 'dataset' );\n\n//TODO: still toooooooooooooo complex - rework\n var summaryTemplates = {};\n summaryTemplates[ STATES.OK ] = summaryTemplates[ STATES.FAILED_METADATA ] = BASE_MVC.wrapTemplate([\n '<% if( dataset.misc_blurb ){ %>',\n '
                                  ',\n '<%- dataset.misc_blurb %>',\n '
                                  ',\n '<% } %>',\n\n '<% if( dataset.file_ext ){ %>',\n '
                                  ',\n '',\n '<%- dataset.file_ext %>',\n '
                                  ',\n '<% } %>',\n\n '<% if( dataset.metadata_dbkey ){ %>',\n '
                                  ',\n '',\n '',\n '<%- dataset.metadata_dbkey %>',\n '',\n '
                                  ',\n '<% } %>',\n\n '<% if( dataset.misc_info ){ %>',\n '
                                  ',\n '<%- dataset.misc_info %>',\n '
                                  ',\n '<% } %>'\n ], 'dataset' );\n summaryTemplates[ STATES.NEW ] = BASE_MVC.wrapTemplate([\n '
                                  ', _l( 'This is a new dataset and not all of its data are available yet' ), '
                                  '\n ], 'dataset' );\n summaryTemplates[ STATES.NOT_VIEWABLE ] = BASE_MVC.wrapTemplate([\n '
                                  ', _l( 'You do not have permission to view this dataset' ), '
                                  '\n ], 'dataset' );\n summaryTemplates[ STATES.DISCARDED ] = BASE_MVC.wrapTemplate([\n '
                                  ', _l( 'The job creating this dataset was cancelled before completion' ), '
                                  '\n ], 'dataset' );\n summaryTemplates[ STATES.QUEUED ] = BASE_MVC.wrapTemplate([\n '
                                  ', _l( 'This job is waiting to run' ), '
                                  '\n ], 'dataset' );\n summaryTemplates[ STATES.RUNNING ] = BASE_MVC.wrapTemplate([\n '
                                  ', _l( 'This job is currently running' ), '
                                  '\n ], 'dataset' );\n summaryTemplates[ STATES.UPLOAD ] = BASE_MVC.wrapTemplate([\n '
                                  ', _l( 'This dataset is currently uploading' ), '
                                  '\n ], 'dataset' );\n summaryTemplates[ STATES.SETTING_METADATA ] = BASE_MVC.wrapTemplate([\n '
                                  ', _l( 'Metadata is being auto-detected' ), '
                                  '\n ], 'dataset' );\n summaryTemplates[ STATES.PAUSED ] = BASE_MVC.wrapTemplate([\n '
                                  ', _l( 'This job is paused. Use the \"Resume Paused Jobs\" in the history menu to resume' ), '
                                  '\n ], 'dataset' );\n summaryTemplates[ STATES.ERROR ] = BASE_MVC.wrapTemplate([\n '<% if( !dataset.purged ){ %>',\n '
                                  <%- dataset.misc_blurb %>
                                  ',\n '<% } %>',\n '', _l( 'An error occurred with this dataset' ), ':',\n '
                                  <%- dataset.misc_info %>
                                  '\n ], 'dataset' );\n summaryTemplates[ STATES.EMPTY ] = BASE_MVC.wrapTemplate([\n '
                                  ', _l( 'No data' ), ': <%- dataset.misc_blurb %>
                                  '\n ], 'dataset' );\n summaryTemplates.unknown = BASE_MVC.wrapTemplate([\n '
                                  Error: unknown dataset state: \"<%- dataset.state %>\"
                                  '\n ], 'dataset' );\n\n // messages to be displayed only within the details section ('below the fold')\n var detailMessageTemplates = {\n resubmitted : BASE_MVC.wrapTemplate([\n // deleted not purged\n '<% if( model.resubmitted ){ %>',\n '
                                  ',\n _l( 'The job creating this dataset has been resubmitted' ),\n '
                                  ',\n '<% } %>'\n ])\n };\n\n // this is applied to both old and new style display apps\n var displayApplicationsTemplate = BASE_MVC.wrapTemplate([\n '<% _.each( apps, function( app ){ %>',\n '
                                  ',\n '<%- app.label %> ',\n '',\n '<% _.each( app.links, function( link ){ %>',\n '\" href=\"<%- link.href %>\">',\n '<% print( _l( link.text ) ); %>',\n ' ',\n '<% }); %>',\n '',\n '
                                  ',\n '<% }); %>'\n ], 'apps' );\n\n return _.extend( {}, _super.prototype.templates, {\n warnings : warnings,\n details : detailsTemplate,\n noAccess : noAccessTemplate,\n summaries : summaryTemplates,\n detailMessages : detailMessageTemplates,\n displayApplications : displayApplicationsTemplate\n });\n}());\n\n\n// ============================================================================\n return {\n DatasetListItemView : DatasetListItemView\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/dataset/dataset-li.js\n ** module id = 32\n ** module chunks = 3\n **/","/* This class maps the form dom to an api compatible javascript dictionary. */\ndefine([ 'utils/utils' ], function( Utils ) {\n var Manager = Backbone.Model.extend({\n initialize: function( app ) {\n this.app = app;\n },\n\n /** Creates a checksum. */\n checksum: function() {\n var sum = '';\n var self = this;\n this.app.section.$el.find( '.section-row' ).each( function() {\n var id = $(this).attr( 'id' );\n var field = self.app.field_list[ id ];\n if ( field ) {\n sum += id + ':' + JSON.stringify( field.value && field.value() ) + ':' + field.collapsed + ';';\n }\n });\n return sum;\n },\n\n /** Convert dom into a dictionary of flat id/value pairs used e.g. on job submission. */\n create: function() {\n var self = this;\n\n // get raw dictionary from dom\n var dict = {};\n this._iterate( this.app.section.$el, dict );\n\n // add to result dictionary, label elements\n var result_dict = {};\n this.flat_dict = {};\n function add( flat_id, input_id, input_value ) {\n self.flat_dict[ flat_id ] = input_id;\n result_dict[ flat_id ] = input_value;\n self.app.element_list[ input_id ] && self.app.element_list[ input_id ].$el.attr( 'tour_id', flat_id );\n }\n // converter between raw dictionary and job dictionary\n function convert( identifier, head ) {\n for ( var index in head ) {\n var node = head[ index ];\n if ( node.input ) {\n var input = node.input;\n var flat_id = identifier;\n if ( identifier != '' ) {\n flat_id += '|';\n }\n flat_id += input.name;\n switch ( input.type ) {\n case 'repeat':\n var section_label = 'section-';\n var block_indices = [];\n var block_prefix = null;\n for ( var block_label in node ) {\n var pos = block_label.indexOf( section_label );\n if ( pos != -1 ) {\n pos += section_label.length;\n block_indices.push( parseInt( block_label.substr( pos ) ));\n if ( !block_prefix ) {\n block_prefix = block_label.substr( 0, pos );\n }\n }\n }\n block_indices.sort( function( a, b ) { return a - b; });\n var index = 0;\n for ( var i in block_indices ) {\n convert( flat_id + '_' + index++, node[ block_prefix + block_indices[ i ] ]);\n }\n break;\n case 'conditional':\n var value = self.app.field_list[ input.id ].value();\n add( flat_id + '|' + input.test_param.name, input.id, value );\n var selectedCase = matchCase( input, value );\n if ( selectedCase != -1 ) {\n convert( flat_id, head[ input.id + '-section-' + selectedCase ] );\n }\n break;\n case 'section':\n convert( !input.flat && flat_id || '', node );\n break;\n default:\n var field = self.app.field_list[ input.id ];\n if ( field && field.value ) {\n var value = field.value();\n if ( input.ignore === undefined || input.ignore != value ) {\n if ( field.collapsed && input.collapsible_value ) {\n value = input.collapsible_value;\n }\n add( flat_id, input.id, value );\n if ( input.payload ) {\n for ( var p_id in input.payload ) {\n add( p_id, input.id, input.payload[ p_id ] );\n }\n }\n }\n }\n }\n }\n }\n }\n convert( '', dict );\n return result_dict;\n },\n\n /** Matches flat ids to corresponding input element\n * @param{string} flat_id - Flat input id to be looked up.\n */\n match: function ( flat_id ) {\n return this.flat_dict && this.flat_dict[ flat_id ];\n },\n\n /** Match conditional values to selected cases\n */\n matchCase: function( input, value ) {\n return matchCase( input, value );\n },\n\n /** Matches a new tool model to the current input elements e.g. used to update dynamic options\n */\n matchModel: function( model, callback ) {\n var self = this;\n visitInputs( model.inputs, function( input, name ) {\n self.flat_dict[ name ] && callback ( input, self.flat_dict[ name ] );\n });\n },\n\n /** Matches identifier from api response to input elements e.g. used to display validation errors\n */\n matchResponse: function( response ) {\n var result = {};\n var self = this;\n function search ( id, head ) {\n if ( typeof head === 'string' ) {\n var input_id = self.flat_dict[ id ];\n input_id && ( result[ input_id ] = head );\n } else {\n for ( var i in head ) {\n var new_id = i;\n if ( id !== '' ) {\n var separator = '|';\n if ( head instanceof Array ) {\n separator = '_';\n }\n new_id = id + separator + new_id;\n }\n search ( new_id, head[ i ] );\n }\n }\n }\n search( '', response );\n return result;\n },\n\n /** Map dom tree to dictionary tree with input elements.\n */\n _iterate: function( parent, dict ) {\n var self = this;\n var children = $( parent ).children();\n children.each( function() {\n var child = this;\n var id = $( child ).attr( 'id' );\n if ( $( child ).hasClass( 'section-row' ) ) {\n var input = self.app.input_list[ id ];\n dict[ id ] = ( input && { input : input } ) || {};\n self._iterate( child, dict[ id ] );\n } else {\n self._iterate( child, dict );\n }\n });\n }\n });\n\n /** Match conditional values to selected cases\n * @param{dict} input - Definition of conditional input parameter\n * @param{dict} value - Current value\n */\n var matchCase = function( input, value ) {\n if ( input.test_param.type == 'boolean' ) {\n if ( value == 'true' ) {\n value = input.test_param.truevalue || 'true';\n } else {\n value = input.test_param.falsevalue || 'false';\n }\n }\n for ( var i in input.cases ) {\n if ( input.cases[ i ].value == value ) {\n return i;\n }\n }\n return -1;\n };\n\n /** Visits tool inputs\n * @param{dict} inputs - Nested dictionary of input elements\n * @param{dict} callback - Called with the mapped dictionary object and corresponding model node\n */\n var visitInputs = function( inputs, callback, prefix, context ) {\n context = $.extend( true, {}, context );\n _.each( inputs, function ( input ) {\n if ( input && input.type && input.name ) {\n context[ input.name ] = input;\n }\n });\n for ( var key in inputs ) {\n var node = inputs[ key ];\n node.name = node.name || key;\n var name = prefix ? prefix + '|' + node.name : node.name;\n switch ( node.type ) {\n case 'repeat':\n _.each( node.cache, function( cache, j ) {\n visitInputs( cache, callback, name + '_' + j, context );\n });\n break;\n case 'conditional':\n if ( node.test_param ) {\n callback( node.test_param, name + '|' + node.test_param.name, context );\n var selectedCase = matchCase( node, node.test_param.value );\n if ( selectedCase != -1 ) {\n visitInputs( node.cases[ selectedCase ].inputs, callback, name, context );\n } else {\n Galaxy.emit.debug( 'form-data::visitInputs() - Invalid case for ' + name + '.' );\n }\n } else {\n Galaxy.emit.debug( 'form-data::visitInputs() - Conditional test parameter missing for ' + name + '.' );\n }\n break;\n case 'section':\n visitInputs( node.inputs, callback, name, context )\n break;\n default:\n callback( node, name, context );\n }\n }\n };\n\n return {\n Manager : Manager,\n visitInputs : visitInputs\n }\n});\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/form/form-data.js\n ** module id = 33\n ** module chunks = 0 3\n **/","/**\n This class creates a form input element wrapper\n*/\ndefine([], function() {\n return Backbone.View.extend({\n initialize: function( app, options ) {\n this.app = app;\n this.app_options = app.options || {};\n this.field = options && options.field || new Backbone.View();\n this.model = options && options.model || new Backbone.Model({\n text_enable : this.app_options.text_enable || 'Enable',\n text_disable : this.app_options.text_disable || 'Disable',\n cls_enable : this.app_options.cls_enable || 'fa fa-caret-square-o-down',\n cls_disable : this.app_options.cls_disable || 'fa fa-caret-square-o-up'\n }).set( options );\n\n // set element and link components\n this.setElement( this._template() );\n this.$field = this.$( '.ui-form-field' );\n this.$info = this.$( '.ui-form-info' );\n this.$preview = this.$( '.ui-form-preview' );\n this.$collapsible = this.$( '.ui-form-collapsible' );\n this.$collapsible_text = this.$( '.ui-form-collapsible-text' );\n this.$collapsible_icon = this.$( '.ui-form-collapsible-icon' );\n this.$title = this.$( '.ui-form-title' );\n this.$title_text = this.$( '.ui-form-title-text' );\n this.$error_text = this.$( '.ui-form-error-text' );\n this.$error = this.$( '.ui-form-error' );\n this.$backdrop = this.$( '.ui-form-backdrop' );\n\n // add field element\n this.$field.prepend( this.field.$el );\n\n // decide wether to expand or collapse fields\n var collapsible_value = this.model.get( 'collapsible_value' );\n this.field.collapsed = collapsible_value !== undefined && JSON.stringify( this.model.get( 'value' ) ) == JSON.stringify( collapsible_value );\n this.listenTo( this.model, 'change', this.render, this );\n this.render();\n\n // add click handler\n var self = this;\n this.$collapsible.on( 'click', function() {\n self.field.collapsed = !self.field.collapsed;\n app.trigger && app.trigger( 'change' );\n self.render();\n });\n },\n\n /** Set backdrop for input element\n */\n backdrop: function() {\n this.model.set( 'backdrop', true );\n },\n\n /** Set error text\n */\n error: function( text ) {\n this.model.set( 'error_text', text );\n },\n\n /** Reset this view\n */\n reset: function() {\n this.model.set( 'error_text', null );\n },\n\n render: function() {\n // render help\n $( '.tooltip' ).hide();\n var help_text = this.model.get( 'help', '' );\n var help_argument = this.model.get( 'argument' );\n if ( help_argument && help_text.indexOf( '(' + help_argument + ')' ) == -1 ) {\n help_text += ' (' + help_argument + ')';\n }\n this.$info.html( help_text );\n // render visibility\n this.$el[ this.model.get( 'hidden' ) ? 'hide' : 'show' ]();\n // render preview view for collapsed fields\n this.$preview[ ( this.field.collapsed && this.model.get( 'collapsible_preview' ) || this.model.get( 'disabled' ) ) ? 'show' : 'hide' ]()\n .html( _.escape( this.model.get( 'text_value' ) ) );\n // render error messages\n var error_text = this.model.get( 'error_text' );\n this.$error[ error_text ? 'show' : 'hide' ]();\n this.$el[ error_text ? 'addClass' : 'removeClass' ]( 'ui-error' );\n this.$error_text.html( error_text );\n // render backdrop\n this.$backdrop[ this.model.get( 'backdrop' ) ? 'show' : 'hide' ]();\n // render input field\n this.field.collapsed || this.model.get( 'disabled' ) ? this.$field.hide() : this.$field.show();\n // render input field color and style\n this.field.model && this.field.model.set( { 'color': this.model.get( 'color' ), 'style': this.model.get( 'style' ) } );\n // render collapsible options\n if ( !this.model.get( 'disabled' ) && this.model.get( 'collapsible_value' ) !== undefined ) {\n var collapsible_state = this.field.collapsed ? 'enable' : 'disable';\n this.$title_text.hide();\n this.$collapsible.show();\n this.$collapsible_text.text( this.model.get( 'label' ) );\n this.$collapsible_icon.removeClass().addClass( 'icon' )\n .addClass( this.model.get( 'cls_' + collapsible_state ) )\n .attr( 'data-original-title', this.model.get( 'text_' + collapsible_state ) )\n .tooltip( { placement: 'bottom' } );\n } else {\n this.$title_text.show().text( this.model.get( 'label' ) );\n this.$collapsible.hide();\n }\n },\n\n _template: function() {\n return $( '
                                  ' ).addClass( 'ui-form-element' )\n .append( $( '
                                  ' ).addClass( 'ui-form-error ui-error' )\n .append( $( '' ).addClass( 'fa fa-arrow-down' ) )\n .append( $( '' ).addClass( 'ui-form-error-text' ) )\n )\n .append( $( '
                                  ' ).addClass( 'ui-form-title' )\n .append( $( '
                                  ' ).addClass( 'ui-form-collapsible' )\n .append( $( '' ).addClass( 'ui-form-collapsible-icon' ) )\n .append( $( '' ).addClass( 'ui-form-collapsible-text' ) )\n )\n .append( $( '' ).addClass( 'ui-form-title-text' ) )\n )\n .append( $( '
                                  ' ).addClass( 'ui-form-field' )\n .append( $( '' ).addClass( 'ui-form-info' ) )\n .append( $( '
                                  ' ).addClass( 'ui-form-backdrop' ) )\n )\n .append( $( '
                                  ' ).addClass( 'ui-form-preview' ) );\n }\n });\n});\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/form/form-input.js\n ** module id = 34\n ** module chunks = 0 3\n **/","/**\n This class creates input elements. New input parameter types should be added to the types dictionary.\n*/\ndefine(['utils/utils',\n 'mvc/ui/ui-misc',\n 'mvc/ui/ui-select-content',\n 'mvc/ui/ui-select-library',\n 'mvc/ui/ui-select-ftp',\n 'mvc/ui/ui-color-picker'],\n function( Utils, Ui, SelectContent, SelectLibrary, SelectFtp, ColorPicker ) {\n\n // create form view\n return Backbone.Model.extend({\n /** Available parameter types */\n types: {\n 'text' : '_fieldText',\n 'select' : '_fieldSelect',\n 'data_column' : '_fieldSelect',\n 'genomebuild' : '_fieldSelect',\n 'data' : '_fieldData',\n 'data_collection' : '_fieldData',\n 'integer' : '_fieldSlider',\n 'float' : '_fieldSlider',\n 'boolean' : '_fieldBoolean',\n 'drill_down' : '_fieldDrilldown',\n 'color' : '_fieldColor',\n 'hidden' : '_fieldHidden',\n 'hidden_data' : '_fieldHidden',\n 'baseurl' : '_fieldHidden',\n 'library_data' : '_fieldLibrary',\n 'ftpfile' : '_fieldFtp'\n },\n\n /** Returns an input field for a given field type */\n create: function( input_def ) {\n var fieldClass = this.types[ input_def.type ];\n var field = typeof( this[ fieldClass ] ) === 'function' ? this[ fieldClass ].call( this, input_def ) : null;\n if ( !field ) {\n field = input_def.options ? this._fieldSelect( input_def ) : this._fieldText( input_def );\n Galaxy.emit.debug('form-parameters::_addRow()', 'Auto matched field type (' + input_def.type + ').');\n }\n input_def.value === undefined && ( input_def.value = null );\n field.value( input_def.value );\n return field;\n },\n\n /** Data input field */\n _fieldData: function( input_def ) {\n return new SelectContent.View({\n id : 'field-' + input_def.id,\n extensions : input_def.extensions,\n optional : input_def.optional,\n multiple : input_def.multiple,\n type : input_def.type,\n flavor : input_def.flavor,\n data : input_def.options,\n onchange : input_def.onchange\n });\n },\n\n /** Select/Checkbox/Radio options field */\n _fieldSelect: function ( input_def ) {\n // show text field e.g. in workflow editor\n if( input_def.is_workflow ) {\n return this._fieldText( input_def );\n }\n\n // customize properties\n if ( input_def.type == 'data_column' ) {\n input_def.error_text = 'Missing columns in referenced dataset.'\n }\n\n // identify available options\n var data = input_def.data;\n if( !data ) {\n data = [];\n _.each( input_def.options, function( option ) {\n data.push( { label: option[ 0 ], value: option[ 1 ] } );\n });\n }\n\n // identify display type\n var SelectClass = Ui.Select;\n switch ( input_def.display ) {\n case 'checkboxes':\n SelectClass = Ui.Checkbox;\n break;\n case 'radio':\n SelectClass = Ui.Radio;\n break;\n case 'radiobutton':\n SelectClass = Ui.RadioButton;\n break;\n }\n\n // create select field\n return new SelectClass.View({\n id : 'field-' + input_def.id,\n data : data,\n error_text : input_def.error_text || 'No options available',\n multiple : input_def.multiple,\n optional : input_def.optional,\n onchange : input_def.onchange,\n searchable : input_def.flavor !== 'workflow'\n });\n },\n\n /** Drill down options field */\n _fieldDrilldown: function ( input_def ) {\n // show text field e.g. in workflow editor\n if( input_def.is_workflow ) {\n return this._fieldText( input_def );\n }\n\n // create drill down field\n return new Ui.Drilldown.View({\n id : 'field-' + input_def.id,\n data : input_def.options,\n display : input_def.display,\n optional : input_def.optional,\n onchange : input_def.onchange\n });\n },\n\n /** Text input field */\n _fieldText: function( input_def ) {\n // field replaces e.g. a select field\n if ( input_def.options && input_def.data ) {\n input_def.area = input_def.multiple;\n if ( Utils.isEmpty( input_def.value ) ) {\n input_def.value = null;\n } else {\n if ( $.isArray( input_def.value ) ) {\n var str_value = '';\n for ( var i in input_def.value ) {\n str_value += String( input_def.value[ i ] );\n if ( !input_def.multiple ) {\n break;\n }\n str_value += '\\n';\n }\n input_def.value = str_value;\n }\n }\n }\n // create input element\n return new Ui.Input({\n id : 'field-' + input_def.id,\n area : input_def.area,\n placeholder : input_def.placeholder,\n onchange : input_def.onchange\n });\n },\n\n /** Slider field */\n _fieldSlider: function( input_def ) {\n return new Ui.Slider.View({\n id : 'field-' + input_def.id,\n precise : input_def.type == 'float',\n is_workflow : input_def.is_workflow,\n min : input_def.min,\n max : input_def.max,\n onchange : input_def.onchange\n });\n },\n\n /** Hidden field */\n _fieldHidden: function( input_def ) {\n return new Ui.Hidden({\n id : 'field-' + input_def.id,\n info : input_def.info\n });\n },\n\n /** Boolean field */\n _fieldBoolean: function( input_def ) {\n return new Ui.RadioButton.View({\n id : 'field-' + input_def.id,\n data : [ { label : 'Yes', value : 'true' },\n { label : 'No', value : 'false' }],\n onchange : input_def.onchange\n });\n },\n\n /** Color picker field */\n _fieldColor: function( input_def ) {\n return new ColorPicker({\n id : 'field-' + input_def.id,\n onchange : input_def.onchange\n });\n },\n\n /** Library dataset field */\n _fieldLibrary: function( input_def ) {\n return new SelectLibrary.View({\n id : 'field-' + input_def.id,\n optional : input_def.optional,\n multiple : input_def.multiple,\n onchange : input_def.onchange\n });\n },\n\n /** FTP file field */\n _fieldFtp: function( input_def ) {\n return new SelectFtp.View({\n id : 'field-' + input_def.id,\n optional : input_def.optional,\n multiple : input_def.multiple,\n onchange : input_def.onchange\n });\n }\n });\n\n return {\n View: View\n };\n});\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/form/form-parameters.js\n ** module id = 35\n ** module chunks = 0 3\n **/","/** This class creates a ui component which enables the dynamic creation of portlets */\ndefine( [ 'utils/utils', 'mvc/ui/ui-portlet', 'mvc/ui/ui-misc' ],\nfunction( Utils, Portlet, Ui ) {\n var View = Backbone.View.extend({\n initialize: function( options ) {\n this.list = {};\n this.options = Utils.merge( options, {\n title : 'Repeat',\n empty_text : 'Not available.',\n max : null,\n min : null\n });\n this.button_new = new Ui.ButtonIcon({\n icon : 'fa-plus',\n title : 'Insert ' + this.options.title,\n tooltip : 'Add new ' + this.options.title + ' block',\n floating: 'clear',\n cls : 'ui-button-icon form-repeat-add',\n onclick : function() { options.onnew && options.onnew() }\n });\n this.setElement( $( '
                                  ' ).append( this.$list = $( '
                                  ' ) )\n .append( $( '
                                  ' ).append( this.button_new.$el ) ) );\n },\n\n /** Number of repeat blocks */\n size: function() {\n return _.size( this.list );\n },\n\n /** Add new repeat block */\n add: function( options ) {\n if ( !options.id || this.list[ options.id ] ) {\n Galaxy.emit.debug( 'form-repeat::add()', 'Duplicate or invalid repeat block id.' );\n return;\n }\n var button_delete = new Ui.ButtonIcon({\n icon : 'fa-trash-o',\n tooltip : 'Delete this repeat block',\n cls : 'ui-button-icon-plain form-repeat-delete',\n onclick : function() { options.ondel && options.ondel() }\n });\n var portlet = new Portlet.View({\n id : options.id,\n title : 'placeholder',\n cls : options.cls || 'ui-portlet-repeat',\n operations : { button_delete: button_delete }\n });\n portlet.append( options.$el );\n portlet.$el.addClass( 'section-row' ).hide();\n this.list[ options.id ] = portlet;\n this.$list.append( portlet.$el.fadeIn( 'fast' ) );\n this.options.max > 0 && this.size() >= this.options.max && this.button_new.disable();\n this._refresh();\n },\n\n /** Delete repeat block */\n del: function( id ) {\n if ( !this.list[ id ] ) {\n Galaxy.emit.debug( 'form-repeat::del()', 'Invalid repeat block id.' );\n return;\n }\n this.$list.find( '#' + id ).remove();\n delete this.list[ id ];\n this.button_new.enable();\n this._refresh();\n },\n\n /** Remove all */\n delAll: function() {\n for( var id in this.list ) {\n this.del( id );\n }\n },\n\n /** Hides add/del options */\n hideOptions: function() {\n this.button_new.$el.hide();\n _.each( this.list, function( portlet ) { portlet.hideOperation( 'button_delete' ) } );\n _.isEmpty( this.list ) && this.$el.append( $( '
                                  ' ).addClass( 'ui-form-info' ).html( this.options.empty_text ) );\n },\n\n /** Refresh view */\n _refresh: function() {\n var index = 0;\n for ( var id in this.list ) {\n var portlet = this.list[ id ];\n portlet.title( ++index + ': ' + this.options.title );\n portlet[ this.size() > this.options.min ? 'showOperation' : 'hideOperation' ]( 'button_delete' );\n }\n }\n });\n\n return {\n View : View\n }\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/form/form-repeat.js\n ** module id = 36\n ** module chunks = 0 3\n **/","/**\n This class creates a form section and populates it with input elements. It also handles repeat blocks and conditionals by recursively creating new sub sections.\n*/\ndefine([ 'utils/utils', 'mvc/ui/ui-misc', 'mvc/ui/ui-portlet', 'mvc/form/form-repeat', 'mvc/form/form-input', 'mvc/form/form-parameters' ],\nfunction( Utils, Ui, Portlet, Repeat, InputElement, Parameters ) {\n var View = Backbone.View.extend({\n initialize: function( app, options ) {\n this.app = app;\n this.inputs = options.inputs;\n this.parameters = new Parameters();\n this.setElement( $( '
                                  ' ) );\n this.render();\n },\n\n /** Render section view */\n render: function() {\n var self = this;\n this.$el.empty();\n _.each( this.inputs, function( input ) { self.add( input ) } );\n },\n\n /** Add a new input element */\n add: function( input ) {\n var input_def = jQuery.extend( true, {}, input );\n input_def.id = input.id = Utils.uid();\n this.app.input_list[ input_def.id ] = input_def;\n switch( input_def.type ) {\n case 'conditional':\n this._addConditional( input_def );\n break;\n case 'repeat':\n this._addRepeat( input_def );\n break;\n case 'section':\n this._addSection( input_def );\n break;\n default:\n this._addRow( input_def );\n }\n },\n\n /** Add a conditional block */\n _addConditional: function( input_def ) {\n var self = this;\n input_def.test_param.id = input_def.id;\n this.app.options.sustain_conditionals && ( input_def.test_param.disabled = true );\n var field = this._addRow( input_def.test_param );\n\n // set onchange event for test parameter\n field.model && field.model.set( 'onchange', function( value ) {\n var selectedCase = self.app.data.matchCase( input_def, value );\n for ( var i in input_def.cases ) {\n var case_def = input_def.cases[ i ];\n var section_row = self.$( '#' + input_def.id + '-section-' + i );\n var nonhidden = false;\n for ( var j in case_def.inputs ) {\n if ( !case_def.inputs[ j ].hidden ) {\n nonhidden = true;\n break;\n }\n }\n if ( i == selectedCase && nonhidden ) {\n section_row.fadeIn( 'fast' );\n } else {\n section_row.hide();\n }\n }\n self.app.trigger( 'change' );\n });\n\n // add conditional sub sections\n for ( var i in input_def.cases ) {\n var sub_section = new View( this.app, { inputs: input_def.cases[ i ].inputs } );\n this._append( sub_section.$el.addClass( 'ui-form-section' ), input_def.id + '-section-' + i );\n }\n\n // trigger refresh on conditional input field after all input elements have been created\n field.trigger( 'change' );\n },\n\n /** Add a repeat block */\n _addRepeat: function( input_def ) {\n var self = this;\n var block_index = 0;\n\n // create repeat block element\n var repeat = new Repeat.View({\n title : input_def.title || 'Repeat',\n min : input_def.min,\n max : input_def.max,\n onnew : function() { create( input_def.inputs ); self.app.trigger( 'change' ); }\n });\n\n // helper function to create new repeat blocks\n function create ( inputs ) {\n var sub_section_id = input_def.id + '-section-' + ( block_index++ );\n var sub_section = new View( self.app, { inputs: inputs } );\n repeat.add( { id : sub_section_id,\n $el : sub_section.$el,\n ondel : function() { repeat.del( sub_section_id ); self.app.trigger( 'change' ); } } );\n }\n\n //\n // add parsed/minimum number of repeat blocks\n //\n var n_cache = _.size( input_def.cache );\n for ( var i = 0; i < Math.max( Math.max( n_cache, input_def.min ), input_def.default || 0 ); i++ ) {\n create( i < n_cache ? input_def.cache[ i ] : input_def.inputs );\n }\n\n // hide options\n this.app.options.sustain_repeats && repeat.hideOptions();\n\n // create input field wrapper\n var input_element = new InputElement( this.app, {\n label : input_def.title || input_def.name,\n help : input_def.help,\n field : repeat\n });\n this._append( input_element.$el, input_def.id );\n },\n\n /** Add a customized section */\n _addSection: function( input_def ) {\n var portlet = new Portlet.View({\n title : input_def.title || input_def.name,\n cls : 'ui-portlet-section',\n collapsible : true,\n collapsible_button : true,\n collapsed : !input_def.expanded\n });\n portlet.append( new View( this.app, { inputs: input_def.inputs } ).$el );\n portlet.append( $( '
                                  ' ).addClass( 'ui-form-info' ).html( input_def.help ) );\n this.app.on( 'expand', function( input_id ) { ( portlet.$( '#' + input_id ).length > 0 ) && portlet.expand(); } );\n this._append( portlet.$el, input_def.id );\n },\n\n /** Add a single input field element */\n _addRow: function( input_def ) {\n var self = this;\n var id = input_def.id;\n input_def.onchange = function() { self.app.trigger( 'change', id ) };\n var field = this.parameters.create( input_def );\n this.app.field_list[ id ] = field;\n var input_element = new InputElement( this.app, {\n name : input_def.name,\n label : input_def.label || input_def.name,\n value : input_def.value,\n text_value : input_def.text_value,\n collapsible_value : input_def.collapsible_value,\n collapsible_preview : input_def.collapsible_preview,\n help : input_def.help,\n argument : input_def.argument,\n disabled : input_def.disabled,\n color : input_def.color,\n style : input_def.style,\n backdrop : input_def.backdrop,\n hidden : input_def.hidden,\n field : field\n });\n this.app.element_list[ id ] = input_element;\n this._append( input_element.$el, input_def.id );\n return field;\n },\n\n /** Append a new element to the form i.e. input element, repeat block, conditionals etc. */\n _append: function( $el, id ) {\n this.$el.append( $el.addClass( 'section-row' ).attr( 'id', id ) );\n }\n });\n\n return {\n View: View\n };\n});\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/form/form-section.js\n ** module id = 37\n ** module chunks = 0 3\n **/","/**\n This is the main class of the form plugin. It is referenced as 'app' in lower level modules.\n*/\ndefine( [ 'utils/utils', 'mvc/ui/ui-portlet', 'mvc/ui/ui-misc', 'mvc/form/form-section', 'mvc/form/form-data' ],\nfunction( Utils, Portlet, Ui, FormSection, FormData ) {\n return Backbone.View.extend({\n initialize: function( options ) {\n this.options = Utils.merge( options, {\n initial_errors : false,\n cls : 'ui-portlet-limited',\n icon : null,\n always_refresh : true\n });\n this.setElement( '
                                  ' );\n this.render();\n },\n\n /** Update available options */\n update: function( new_model ){\n var self = this;\n this.data.matchModel( new_model, function( node, input_id ) {\n var input = self.input_list[ input_id ];\n if ( input && input.options ) {\n if ( !_.isEqual( input.options, node.options ) ) {\n input.options = node.options;\n var field = self.field_list[ input_id ];\n if ( field.update ) {\n var new_options = [];\n if ( ( [ 'data', 'data_collection', 'drill_down' ] ).indexOf( input.type ) != -1 ) {\n new_options = input.options;\n } else {\n for ( var i in node.options ) {\n var opt = node.options[ i ];\n if ( opt.length > 2 ) {\n new_options.push( { label: opt[ 0 ], value: opt[ 1 ] } );\n }\n }\n }\n field.update( new_options );\n field.trigger( 'change' );\n Galaxy.emit.debug( 'form-view::update()', 'Updating options for ' + input_id );\n }\n }\n }\n });\n },\n\n /** Set form into wait mode */\n wait: function( active ) {\n for ( var i in this.input_list ) {\n var field = this.field_list[ i ];\n var input = this.input_list[ i ];\n if ( input.is_dynamic && field.wait && field.unwait ) {\n field[ active ? 'wait' : 'unwait' ]();\n }\n }\n },\n\n /** Highlight and scroll to input element (currently only used for error notifications) */\n highlight: function ( input_id, message, silent ) {\n var input_element = this.element_list[ input_id ];\n if ( input_element ) {\n input_element.error( message || 'Please verify this parameter.' );\n this.portlet.expand();\n this.trigger( 'expand', input_id );\n if ( !silent ) {\n var $panel = this.$el.parents().filter(function() {\n return [ 'auto', 'scroll' ].indexOf( $( this ).css( 'overflow' ) ) != -1;\n }).first();\n $panel.animate( { scrollTop : $panel.scrollTop() + input_element.$el.offset().top - 120 }, 500 );\n }\n }\n },\n\n /** Highlights errors */\n errors: function( options ) {\n this.trigger( 'reset' );\n if ( options && options.errors ) {\n var error_messages = this.data.matchResponse( options.errors );\n for ( var input_id in this.element_list ) {\n var input = this.element_list[ input_id ];\n if ( error_messages[ input_id ] ) {\n this.highlight( input_id, error_messages[ input_id ], true );\n }\n }\n }\n },\n\n /** Render tool form */\n render: function() {\n var self = this;\n this.off('change');\n this.off('reset');\n // contains the dom field elements as created by the parameter factory i.e. form-parameters\n this.field_list = {};\n // contains input definitions/dictionaries as provided by the parameters to_dict() function through the api\n this.input_list = {};\n // contains the dom elements of each input element i.e. form-input which wraps the actual input field\n this.element_list = {};\n // converts the form into a json data structure\n this.data = new FormData.Manager( this );\n this._renderForm();\n this.data.create();\n this.options.initial_errors && this.errors( this.options );\n // add listener which triggers on checksum change, and reset the form input wrappers\n var current_check = this.data.checksum();\n this.on('change', function( input_id ) {\n var input = self.input_list[ input_id ];\n if ( !input || input.refresh_on_change || self.options.always_refresh ) {\n var new_check = self.data.checksum();\n if ( new_check != current_check ) {\n current_check = new_check;\n self.options.onchange && self.options.onchange();\n }\n }\n });\n this.on('reset', function() {\n _.each( self.element_list, function( input_element ) { input_element.reset() } );\n });\n return this;\n },\n\n /** Renders/appends dom elements of the form */\n _renderForm: function() {\n $( '.tooltip' ).remove();\n this.message = new Ui.Message();\n this.section = new FormSection.View( this, { inputs: this.options.inputs } );\n this.portlet = new Portlet.View({\n icon : this.options.icon,\n title : this.options.title,\n cls : this.options.cls,\n operations : this.options.operations,\n buttons : this.options.buttons,\n collapsible : this.options.collapsible,\n collapsed : this.options.collapsed\n });\n this.portlet.append( this.message.$el );\n this.portlet.append( this.section.$el );\n this.$el.empty();\n this.options.inputs && this.$el.append( this.portlet.$el );\n this.options.message && this.message.update( { persistent: true, status: 'warning', message: this.options.message } );\n Galaxy.emit.debug( 'form-view::initialize()', 'Completed' );\n }\n });\n});\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/form/form-view.js\n ** module id = 38\n ** module chunks = 0 3\n **/","define([\n \"mvc/collection/collection-model\",\n \"mvc/history/history-content-model\",\n \"utils/localization\"\n], function( DC_MODEL, HISTORY_CONTENT, _l ){\n\n'use strict';\n\n/*==============================================================================\n\nModels for DatasetCollections contained within a history.\n\nTODO:\n these might be compactable to one class if some duplication with\n collection-model is used.\n\n==============================================================================*/\nvar hcontentMixin = HISTORY_CONTENT.HistoryContentMixin,\n ListDC = DC_MODEL.ListDatasetCollection,\n PairDC = DC_MODEL.PairDatasetCollection,\n ListPairedDC = DC_MODEL.ListPairedDatasetCollection,\n ListOfListsDC = DC_MODEL.ListOfListsDatasetCollection;\n\n//==============================================================================\n/** Override to post to contents route w/o id. */\nfunction buildHDCASave( _super ){\n return function _save( attributes, options ){\n if( this.isNew() ){\n options = options || {};\n options.url = this.urlRoot + this.get( 'history_id' ) + '/contents';\n attributes = attributes || {};\n attributes.type = 'dataset_collection';\n }\n return _super.call( this, attributes, options );\n };\n}\n\n\n//==============================================================================\n/** @class Backbone model for List Dataset Collection within a History.\n */\nvar HistoryListDatasetCollection = ListDC.extend( hcontentMixin ).extend(\n/** @lends HistoryListDatasetCollection.prototype */{\n\n defaults : _.extend( _.clone( ListDC.prototype.defaults ), {\n history_content_type: 'dataset_collection',\n collection_type : 'list',\n model_class : 'HistoryDatasetCollectionAssociation'\n }),\n\n /** Override to post to contents route w/o id. */\n save : buildHDCASave( ListDC.prototype.save ),\n\n /** String representation. */\n toString : function(){\n return 'History' + ListDC.prototype.toString.call( this );\n }\n});\n\n\n//==============================================================================\n/** @class Backbone model for Pair Dataset Collection within a History.\n * @constructs\n */\nvar HistoryPairDatasetCollection = PairDC.extend( hcontentMixin ).extend(\n/** @lends HistoryPairDatasetCollection.prototype */{\n\n defaults : _.extend( _.clone( PairDC.prototype.defaults ), {\n history_content_type: 'dataset_collection',\n collection_type : 'paired',\n model_class : 'HistoryDatasetCollectionAssociation'\n }),\n\n /** Override to post to contents route w/o id. */\n save : buildHDCASave( PairDC.prototype.save ),\n\n /** String representation. */\n toString : function(){\n return 'History' + PairDC.prototype.toString.call( this );\n }\n});\n\n\n//==============================================================================\n/** @class Backbone model for List of Pairs Dataset Collection within a History. */\nvar HistoryListPairedDatasetCollection = ListPairedDC.extend( hcontentMixin ).extend({\n\n defaults : _.extend( _.clone( ListPairedDC.prototype.defaults ), {\n history_content_type: 'dataset_collection',\n collection_type : 'list:paired',\n model_class : 'HistoryDatasetCollectionAssociation'\n }),\n\n /** Override to post to contents route w/o id. */\n save : buildHDCASave( ListPairedDC.prototype.save ),\n\n /** String representation. */\n toString : function(){\n return 'History' + ListPairedDC.prototype.toString.call( this );\n }\n});\n\n\n//==============================================================================\n/** @class Backbone model for List of Lists Dataset Collection within a History. */\nvar HistoryListOfListsDatasetCollection = ListOfListsDC.extend( hcontentMixin ).extend({\n\n defaults : _.extend( _.clone( ListOfListsDC.prototype.defaults ), {\n history_content_type: 'dataset_collection',\n collection_type : 'list:list',\n model_class : 'HistoryDatasetCollectionAssociation'\n }),\n\n /** Override to post to contents route w/o id. */\n save : buildHDCASave( ListOfListsDC.prototype.save ),\n\n /** String representation. */\n toString : function(){\n return ([ 'HistoryListOfListsDatasetCollection(', this.get( 'name' ), ')' ].join( '' ));\n }\n});\n\n\n//==============================================================================\n return {\n HistoryListDatasetCollection : HistoryListDatasetCollection,\n HistoryPairDatasetCollection : HistoryPairDatasetCollection,\n HistoryListPairedDatasetCollection : HistoryListPairedDatasetCollection,\n HistoryListOfListsDatasetCollection : HistoryListOfListsDatasetCollection\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/history/hdca-model.js\n ** module id = 39\n ** module chunks = 3\n **/","define([\n \"mvc/base/controlled-fetch-collection\",\n \"mvc/history/hda-model\",\n \"mvc/history/hdca-model\",\n \"mvc/history/history-preferences\",\n \"mvc/base-mvc\",\n \"utils/ajax-queue\"\n], function( CONTROLLED_FETCH_COLLECTION, HDA_MODEL, HDCA_MODEL, HISTORY_PREFS, BASE_MVC, AJAX_QUEUE ){\n'use strict';\n\n//==============================================================================\nvar _super = CONTROLLED_FETCH_COLLECTION.PaginatedCollection;\n/** @class Backbone collection for history content.\n * NOTE: history content seems like a dataset collection, but differs in that it is mixed:\n * each element can be either an HDA (dataset) or a DatasetCollection and co-exist on\n * the same level.\n * Dataset collections on the other hand are not mixed and (so far) can only contain either\n * HDAs or child dataset collections on one level.\n * This is why this does not inherit from any of the DatasetCollections (currently).\n */\nvar HistoryContents = _super.extend( BASE_MVC.LoggableMixin ).extend({\n _logNamespace : 'history',\n\n // ........................................................................ composite collection\n /** since history content is a mix, override model fn into a factory, creating based on history_content_type */\n model : function( attrs, options ) {\n if( attrs.history_content_type === \"dataset\" ) {\n return new HDA_MODEL.HistoryDatasetAssociation( attrs, options );\n\n } else if( attrs.history_content_type === \"dataset_collection\" ) {\n switch( attrs.collection_type ){\n case 'list':\n return new HDCA_MODEL.HistoryListDatasetCollection( attrs, options );\n case 'paired':\n return new HDCA_MODEL.HistoryPairDatasetCollection( attrs, options );\n case 'list:paired':\n return new HDCA_MODEL.HistoryListPairedDatasetCollection( attrs, options );\n case 'list:list':\n return new HDCA_MODEL.HistoryListOfListsDatasetCollection( attrs, options );\n }\n // This is a hack inside a hack:\n // Raise a plain object with validationError to fake a model.validationError\n // (since we don't have a model to use validate with)\n // (the outer hack being the mixed content/model function in this collection)\n var msg = 'Unknown collection_type: ' + attrs.collection_type;\n console.warn( msg, attrs );\n return { validationError : msg };\n }\n return { validationError : 'Unknown history_content_type: ' + attrs.history_content_type };\n },\n\n // ........................................................................ set up\n limitPerPage : 500,\n\n /** @type {Integer} how many contents per call to fetch when using progressivelyFetchDetails */\n limitPerProgressiveFetch : 500,\n\n /** @type {String} order used here and when fetching from server */\n order : 'hid',\n\n /** root api url */\n urlRoot : Galaxy.root + 'api/histories',\n\n /** complete api url */\n url : function(){\n return this.urlRoot + '/' + this.historyId + '/contents';\n },\n\n /** Set up */\n initialize : function( models, options ){\n options = options || {};\n _super.prototype.initialize.call( this, models, options );\n\n this.history = options.history || null;\n this.setHistoryId( options.historyId || null );\n /** @type {Boolean} does this collection contain and fetch deleted elements */\n this.includeDeleted = options.includeDeleted || this.includeDeleted;\n /** @type {Boolean} does this collection contain and fetch non-visible elements */\n this.includeHidden = options.includeHidden || this.includeHidden;\n\n // backbonejs uses collection.model.prototype.idAttribute to determine if a model is *already* in a collection\n // and either merged or replaced. In this case, our 'model' is a function so we need to add idAttribute\n // manually here - if we don't, contents will not merge but be replaced/swapped.\n this.model.prototype.idAttribute = 'type_id';\n },\n\n setHistoryId : function( newId ){\n this.historyId = newId;\n this._setUpWebStorage();\n },\n\n /** Set up client side storage. Currently PersistanStorage keyed under 'history:' */\n _setUpWebStorage : function( initialSettings ){\n // TODO: use initialSettings\n if( !this.historyId ){ return; }\n this.storage = new HISTORY_PREFS.HistoryPrefs({\n id: HISTORY_PREFS.HistoryPrefs.historyStorageKey( this.historyId )\n });\n this.trigger( 'new-storage', this.storage, this );\n\n this.on({\n 'include-deleted' : function( newVal ){\n this.storage.includeDeleted( newVal );\n },\n 'include-hidden' : function( newVal ){\n this.storage.includeHidden( newVal );\n }\n });\n\n this.includeDeleted = this.storage.includeDeleted() || false;\n this.includeHidden = this.storage.includeHidden() || false;\n return this;\n },\n\n // ........................................................................ common queries\n /** @type {Object} map of collection available sorting orders containing comparator fns */\n comparators : _.extend( _.clone( _super.prototype.comparators ), {\n 'name' : BASE_MVC.buildComparator( 'name', { ascending: true }),\n 'name-dsc' : BASE_MVC.buildComparator( 'name', { ascending: false }),\n 'hid' : BASE_MVC.buildComparator( 'hid', { ascending: false }),\n 'hid-asc' : BASE_MVC.buildComparator( 'hid', { ascending: true }),\n }),\n\n /** Get every model in this collection not in a 'ready' state (running). */\n running : function(){\n return this.filter( function( c ){ return !c.inReadyState(); });\n },\n\n /** return contents that are not ready and not deleted/hidden */\n runningAndActive : function(){\n return this.filter( function( c ){\n return ( !c.inReadyState() )\n && ( c.get( 'visible' ) )\n // TODO: deletedOrPurged?\n && ( !c.get( 'deleted' ) );\n });\n },\n\n /** Get the model with the given hid\n * @param {Int} hid the hid to search for\n * @returns {HistoryDatasetAssociation} the model with the given hid or undefined if not found\n */\n getByHid : function( hid ){\n // note: there *can* be more than one content with a given hid, this finds the first based on order\n return this.findWhere({ hid: hid });\n },\n\n /** return true if all contents have details */\n haveDetails : function(){\n return this.all( function( c ){ return c.hasDetails(); });\n },\n\n // ........................................................................ hidden / deleted\n /** return a new contents collection of only hidden items */\n hidden : function(){\n return this.filter( function( c ){ return c.hidden(); });\n },\n\n /** return a new contents collection of only hidden items */\n deleted : function(){\n return this.filter( function( c ){ return c.get( 'deleted' ); });\n },\n\n /** return a new contents collection of only hidden items */\n visibleAndUndeleted : function(){\n return this.filter( function( c ){\n return ( c.get( 'visible' ) )\n // TODO: deletedOrPurged?\n && ( !c.get( 'deleted' ) );\n });\n },\n\n /** create a setter in order to publish the change */\n setIncludeDeleted : function( setting, options ){\n if( _.isBoolean( setting ) && setting !== this.includeDeleted ){\n this.includeDeleted = setting;\n if( _.result( options, 'silent' ) ){ return; }\n this.trigger( 'include-deleted', setting, this );\n }\n },\n\n /** create a setter in order to publish the change */\n setIncludeHidden : function( setting, options ){\n if( _.isBoolean( setting ) && setting !== this.includeHidden ){\n this.includeHidden = setting;\n options = options || {};\n if( _.result( options, 'silent' ) ){ return; }\n this.trigger( 'include-hidden', setting, this );\n }\n },\n\n // ........................................................................ ajax\n // ............ controlled fetch collection\n /** override to get expanded ids from sessionStorage and pass to API as details */\n fetch : function( options ){\n options = options || {};\n if( this.historyId && !options.details ){\n var prefs = HISTORY_PREFS.HistoryPrefs.get( this.historyId ).toJSON();\n if( !_.isEmpty( prefs.expandedIds ) ){\n options.details = _.values( prefs.expandedIds ).join( ',' );\n }\n }\n return _super.prototype.fetch.call( this, options );\n },\n\n // ............. ControlledFetch stuff\n /** override to include the API versioning flag */\n _buildFetchData : function( options ){\n return _.extend( _super.prototype._buildFetchData.call( this, options ), {\n v : 'dev'\n });\n },\n\n /** Extend to include details and version */\n _fetchParams : _super.prototype._fetchParams.concat([\n // TODO: remove (the need for) both\n /** version */\n 'v',\n /** dataset ids to get full details of */\n 'details',\n ]),\n\n /** override to add deleted/hidden filters */\n _buildFetchFilters : function( options ){\n var superFilters = _super.prototype._buildFetchFilters.call( this, options ) || {};\n var filters = {};\n if( !this.includeDeleted ){\n filters.deleted = false;\n filters.purged = false;\n }\n if( !this.includeHidden ){\n filters.visible = true;\n }\n return _.defaults( superFilters, filters );\n },\n\n // ............ paginated collection\n getTotalItemCount : function(){\n return this.history.contentsShown();\n },\n\n // ............ history contents specific ajax\n /** override to filter requested contents to those updated after the Date 'since' */\n fetchUpdated : function( since, options ){\n if( since ){\n options = options || { filters: {} };\n options.remove = false;\n options.filters = {\n 'update_time-ge' : since.toISOString(),\n // workflows will produce hidden datasets (non-output datasets) that still\n // need to be updated in the collection or they'll update forever\n // we can remove the default visible filter by using an 'empty' value\n visible : ''\n };\n }\n return this.fetch( options );\n },\n\n /** fetch all the deleted==true contents of this collection */\n fetchDeleted : function( options ){\n options = options || {};\n var self = this;\n options.filters = _.extend( options.filters, {\n // all deleted, purged or not\n deleted : true,\n purged : undefined\n });\n options.remove = false;\n\n self.trigger( 'fetching-deleted', self );\n return self.fetch( options )\n .always( function(){ self.trigger( 'fetching-deleted-done', self ); });\n },\n\n /** fetch all the visible==false contents of this collection */\n fetchHidden : function( options ){\n options = options || {};\n var self = this;\n options.filters = _.extend( options.filters, {\n visible : false\n });\n options.remove = false;\n\n self.trigger( 'fetching-hidden', self );\n return self.fetch( options )\n .always( function(){ self.trigger( 'fetching-hidden-done', self ); });\n },\n\n /** fetch detailed model data for all contents in this collection */\n fetchAllDetails : function( options ){\n options = options || {};\n var detailsFlag = { details: 'all' };\n options.data = _.extend( options.data || {}, detailsFlag );\n return this.fetch( options );\n },\n\n /** specialty fetch method for retrieving the element_counts of all hdcas in the history */\n fetchCollectionCounts : function( options ){\n options = options || {};\n options.keys = [ 'type_id', 'element_count' ].join( ',' );\n options.filters = _.extend( options.filters || {}, {\n history_content_type: 'dataset_collection',\n });\n options.remove = false;\n return this.fetch( options );\n },\n\n // ............. quasi-batch ops\n // TODO: to batch\n /** helper that fetches using filterParams then calls save on each fetched using updateWhat as the save params */\n _filterAndUpdate : function( filterParams, updateWhat ){\n var self = this;\n var idAttribute = self.model.prototype.idAttribute;\n var updateArgs = [ updateWhat ];\n\n return self.fetch({ filters: filterParams, remove: false })\n .then( function( fetched ){\n // convert filtered json array to model array\n fetched = fetched.reduce( function( modelArray, currJson, i ){\n var model = self.get( currJson[ idAttribute ] );\n return model? modelArray.concat( model ) : modelArray;\n }, []);\n return self.ajaxQueue( 'save', updateArgs, fetched );\n });\n },\n\n /** using a queue, perform ajaxFn on each of the models in this collection */\n ajaxQueue : function( ajaxFn, args, collection ){\n collection = collection || this.models;\n return new AJAX_QUEUE.AjaxQueue( collection.slice().reverse().map( function( content, i ){\n var fn = _.isString( ajaxFn )? content[ ajaxFn ] : ajaxFn;\n return function(){ return fn.apply( content, args ); };\n })).deferred;\n },\n\n /** fetch contents' details in batches of limitPerCall - note: only get searchable details here */\n progressivelyFetchDetails : function( options ){\n options = options || {};\n var deferred = jQuery.Deferred();\n var self = this;\n var limit = options.limitPerCall || self.limitPerProgressiveFetch;\n // TODO: only fetch tags and annotations if specifically requested\n var searchAttributes = HDA_MODEL.HistoryDatasetAssociation.prototype.searchAttributes;\n var detailKeys = searchAttributes.join( ',' );\n\n function _recursivelyFetch( offset ){\n offset = offset || 0;\n var _options = _.extend( _.clone( options ), {\n view : 'summary',\n keys : detailKeys,\n limit : limit,\n offset : offset,\n reset : offset === 0,\n remove : false\n });\n\n _.defer( function(){\n self.fetch.call( self, _options )\n .fail( deferred.reject )\n .done( function( response ){\n deferred.notify( response, limit, offset );\n if( response.length !== limit ){\n self.allFetched = true;\n deferred.resolve( response, limit, offset );\n\n } else {\n _recursivelyFetch( offset + limit );\n }\n });\n });\n }\n _recursivelyFetch();\n return deferred;\n },\n\n /** does some bit of JSON represent something that can be copied into this contents collection */\n isCopyable : function( contentsJSON ){\n var copyableModelClasses = [\n 'HistoryDatasetAssociation',\n 'HistoryDatasetCollectionAssociation'\n ];\n return ( ( _.isObject( contentsJSON ) && contentsJSON.id )\n && ( _.contains( copyableModelClasses, contentsJSON.model_class ) ) );\n },\n\n /** copy an existing, accessible hda into this collection */\n copy : function( json ){\n // TODO: somehow showhorn all this into 'save'\n var id, type, contentType;\n if( _.isString( json ) ){\n id = json;\n contentType = 'hda';\n type = 'dataset';\n } else {\n id = json.id;\n contentType = ({\n 'HistoryDatasetAssociation' : 'hda',\n 'LibraryDatasetDatasetAssociation' : 'ldda',\n 'HistoryDatasetCollectionAssociation' : 'hdca'\n })[ json.model_class ] || 'hda';\n type = ( contentType === 'hdca'? 'dataset_collection' : 'dataset' );\n }\n var collection = this,\n xhr = jQuery.ajax( this.url(), {\n method: 'POST',\n contentType: 'application/json',\n data: JSON.stringify({\n content : id,\n source : contentType,\n type : type\n })\n })\n .done( function( response ){\n collection.add([ response ], { parse: true });\n })\n .fail( function( error, status, message ){\n collection.trigger( 'error', collection, xhr, {},\n 'Error copying contents', { type: type, id: id, source: contentType });\n });\n return xhr;\n },\n\n /** create a new HDCA in this collection */\n createHDCA : function( elementIdentifiers, collectionType, name, options ){\n // normally collection.create returns the new model, but we need the promise from the ajax, so we fake create\n //precondition: elementIdentifiers is an array of plain js objects\n // in the proper form to create the collectionType\n var hdca = this.model({\n history_content_type: 'dataset_collection',\n collection_type : collectionType,\n history_id : this.historyId,\n name : name,\n // should probably be able to just send in a bunch of json here and restruct per class\n // note: element_identifiers is now (incorrectly) an attribute\n element_identifiers : elementIdentifiers\n // do not create the model on the client until the ajax returns\n });\n return hdca.save( options );\n },\n\n // ........................................................................ searching\n /** return true if all contents have the searchable attributes */\n haveSearchDetails : function(){\n return this.allFetched && this.all( function( content ){\n // null (which is a valid returned annotation value)\n // will return false when using content.has( 'annotation' )\n //TODO: a bit hacky - formalize\n return _.has( content.attributes, 'annotation' );\n });\n },\n\n /** return a new collection of contents whose attributes contain the substring matchesWhat */\n matches : function( matchesWhat ){\n return this.filter( function( content ){\n return content.matches( matchesWhat );\n });\n },\n\n // ........................................................................ misc\n /** In this override, copy the historyId to the clone */\n clone : function(){\n var clone = Backbone.Collection.prototype.clone.call( this );\n clone.historyId = this.historyId;\n return clone;\n },\n\n /** String representation. */\n toString : function(){\n return ([ 'HistoryContents(', [ this.historyId, this.length ].join(), ')' ].join( '' ));\n }\n});\n\n\n//==============================================================================\n return {\n HistoryContents : HistoryContents\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/history/history-contents.js\n ** module id = 40\n ** module chunks = 3\n **/","define([\n \"mvc/base-mvc\"\n], function( BASE_MVC ){\n\n'use strict';\n\nvar logNamespace = 'history';\n\n// ============================================================================\n/** session storage for individual history preferences */\nvar HistoryPrefs = BASE_MVC.SessionStorageModel.extend(\n/** @lends HistoryPrefs.prototype */{\n //TODO:?? move to user prefs?\n defaults : {\n //TODO:?? expandedIds to array?\n expandedIds : {},\n show_deleted : false,\n show_hidden : false\n },\n\n /** add an hda id to the hash of expanded hdas */\n addExpanded : function( model ){\n//TODO: use type_id and not model\n var current = this.get( 'expandedIds' );\n current[ model.id ] = model.get( 'id' );\n this.save( 'expandedIds', current );\n },\n\n /** remove an hda id from the hash of expanded hdas */\n removeExpanded : function( model ){\n var current = this.get( 'expandedIds' );\n delete current[ model.id ];\n this.save( 'expandedIds', current );\n },\n\n isExpanded : function( contentId ){\n return _.result( this.get( 'expandedIds' ), contentId, false );\n },\n\n allExpanded : function(){\n return _.values( this.get( 'expandedIds' ) );\n },\n\n clearExpanded : function(){\n this.set( 'expandedIds', {} );\n },\n\n includeDeleted : function( val ){\n // moving the invocation here so other components don't need to know the key\n // TODO: change this key later\n if( !_.isUndefined( val ) ){ this.set( 'show_deleted', val ); }\n return this.get( 'show_deleted' );\n },\n\n includeHidden : function( val ){\n // TODO: change this key later\n if( !_.isUndefined( val ) ){ this.set( 'show_hidden', val ); }\n return this.get( 'show_hidden' );\n },\n\n toString : function(){\n return 'HistoryPrefs(' + this.id + ')';\n }\n\n}, {\n // ........................................................................ class vars\n // class lvl for access w/o instantiation\n storageKeyPrefix : 'history:',\n\n /** key string to store each histories settings under */\n historyStorageKey : function historyStorageKey( historyId ){\n if( !historyId ){\n throw new Error( 'HistoryPrefs.historyStorageKey needs valid id: ' + historyId );\n }\n // single point of change\n return ( HistoryPrefs.storageKeyPrefix + historyId );\n },\n\n /** return the existing storage for the history with the given id (or create one if it doesn't exist) */\n get : function get( historyId ){\n return new HistoryPrefs({ id: HistoryPrefs.historyStorageKey( historyId ) });\n },\n\n /** clear all history related items in sessionStorage */\n clearAll : function clearAll( historyId ){\n for( var key in sessionStorage ){\n if( key.indexOf( HistoryPrefs.storageKeyPrefix ) === 0 ){\n sessionStorage.removeItem( key );\n }\n }\n }\n});\n\n//==============================================================================\n return {\n HistoryPrefs: HistoryPrefs\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/history/history-preferences.js\n ** module id = 41\n ** module chunks = 3\n **/","define([\n 'mvc/base-mvc',\n 'utils/localization'\n], function( BASE_MVC, _l ){\n\n'use strict';\n\nvar logNamespace = 'list';\n//==============================================================================\n/** A view which, when first rendered, shows only summary data/attributes, but\n * can be expanded to show further details (and optionally fetch those\n * details from the server).\n */\nvar ExpandableView = Backbone.View.extend( BASE_MVC.LoggableMixin ).extend({\n _logNamespace : logNamespace,\n\n //TODO: Although the reasoning behind them is different, this shares a lot with HiddenUntilActivated above: combine them\n //PRECONDITION: model must have method hasDetails\n //PRECONDITION: subclasses must have templates.el and templates.details\n\n initialize : function( attributes ){\n /** are the details of this view expanded/shown or not? */\n this.expanded = attributes.expanded || false;\n this.log( '\\t expanded:', this.expanded );\n this.fxSpeed = attributes.fxSpeed !== undefined? attributes.fxSpeed : this.fxSpeed;\n },\n\n // ........................................................................ render main\n /** jq fx speed */\n fxSpeed : 'fast',\n\n /** Render this content, set up ui.\n * @param {Number or String} speed the speed of the render\n */\n render : function( speed ){\n var $newRender = this._buildNewRender();\n this._setUpBehaviors( $newRender );\n this._queueNewRender( $newRender, speed );\n return this;\n },\n\n /** Build a temp div containing the new children for the view's $el.\n * If the view is already expanded, build the details as well.\n */\n _buildNewRender : function(){\n // create a new render using a skeleton template, render title buttons, render body, and set up events, etc.\n var $newRender = $( this.templates.el( this.model.toJSON(), this ) );\n if( this.expanded ){\n this.$details( $newRender ).replaceWith( this._renderDetails().show() );\n }\n return $newRender;\n },\n\n /** Fade out the old el, swap in the new contents, then fade in.\n * @param {Number or String} speed jq speed to use for rendering effects\n * @fires rendered when rendered\n */\n _queueNewRender : function( $newRender, speed ) {\n speed = ( speed === undefined )?( this.fxSpeed ):( speed );\n var view = this;\n\n if( speed === 0 ){\n view._swapNewRender( $newRender );\n view.trigger( 'rendered', view );\n\n } else {\n $( view ).queue( 'fx', [\n function( next ){\n view.$el.fadeOut( speed, next );\n },\n function( next ){\n view._swapNewRender( $newRender );\n next();\n },\n function( next ){\n view.$el.fadeIn( speed, next );\n },\n function( next ){\n view.trigger( 'rendered', view );\n next();\n }\n ]);\n }\n },\n\n /** empty out the current el, move the $newRender's children in */\n _swapNewRender : function( $newRender ){\n return this.$el.empty()\n .attr( 'class', _.isFunction( this.className )? this.className(): this.className )\n .append( $newRender.children() );\n },\n\n /** set up js behaviors, event handlers for elements within the given container\n * @param {jQuery} $container jq object that contains the elements to process (defaults to this.$el)\n */\n _setUpBehaviors : function( $where ){\n $where = $where || this.$el;\n // set up canned behavior on children (bootstrap, popupmenus, editable_text, etc.)\n //make_popup_menus( $where );\n $where.find( '[title]' ).tooltip({ placement : 'bottom' });\n },\n\n // ......................................................................... details\n /** shortcut to details DOM (as jQ) */\n $details : function( $where ){\n $where = $where || this.$el;\n return $where.find( '> .details' );\n },\n\n /** build the DOM for the details and set up behaviors on it */\n _renderDetails : function(){\n var $newDetails = $( this.templates.details( this.model.toJSON(), this ) );\n this._setUpBehaviors( $newDetails );\n return $newDetails;\n },\n\n // ......................................................................... expansion/details\n /** Show or hide the details\n * @param {Boolean} expand if true, expand; if false, collapse\n */\n toggleExpanded : function( expand ){\n expand = ( expand === undefined )?( !this.expanded ):( expand );\n if( expand ){\n this.expand();\n } else {\n this.collapse();\n }\n return this;\n },\n\n /** Render and show the full, detailed body of this view including extra data and controls.\n * note: if the model does not have detailed data, fetch that data before showing the body\n * @fires expanded when a body has been expanded\n */\n expand : function(){\n var view = this;\n return view._fetchModelDetails().always( function(){\n view._expand();\n });\n },\n\n /** Check for model details and, if none, fetch them.\n * @returns {jQuery.promise} the model.fetch.xhr if details are being fetched, an empty promise if not\n */\n _fetchModelDetails : function(){\n if( !this.model.hasDetails() ){\n return this.model.fetch();\n }\n return jQuery.when();\n },\n\n /** Inner fn called when expand (public) has fetched the details */\n _expand : function(){\n var view = this,\n $newDetails = view._renderDetails();\n view.$details().replaceWith( $newDetails );\n // needs to be set after the above or the slide will not show\n view.expanded = true;\n view.$details().slideDown( view.fxSpeed, function(){\n view.trigger( 'expanded', view );\n });\n },\n\n /** Hide the body/details of an HDA.\n * @fires collapsed when a body has been collapsed\n */\n collapse : function(){\n this.debug( this + '(ExpandableView).collapse' );\n var view = this;\n view.expanded = false;\n this.$details().slideUp( view.fxSpeed, function(){\n view.trigger( 'collapsed', view );\n });\n }\n\n});\n\n\n//==============================================================================\n/** A view that is displayed in some larger list/grid/collection.\n * Inherits from Expandable, Selectable, Draggable.\n * The DOM contains warnings, a title bar, and a series of primary action controls.\n * Primary actions are meant to be easily accessible item functions (such as delete)\n * that are rendered in the title bar.\n *\n * Details are rendered when the user clicks the title bar or presses enter/space when\n * the title bar is in focus.\n *\n * Designed as a base class for history panel contents - but usable elsewhere (I hope).\n */\nvar ListItemView = ExpandableView.extend(\n BASE_MVC.mixin( BASE_MVC.SelectableViewMixin, BASE_MVC.DraggableViewMixin, {\n\n tagName : 'div',\n className : 'list-item',\n\n /** Set up the base class and all mixins */\n initialize : function( attributes ){\n ExpandableView.prototype.initialize.call( this, attributes );\n BASE_MVC.SelectableViewMixin.initialize.call( this, attributes );\n BASE_MVC.DraggableViewMixin.initialize.call( this, attributes );\n this._setUpListeners();\n },\n\n /** event listeners */\n _setUpListeners : function(){\n // hide the primary actions in the title bar when selectable and narrow\n this.on( 'selectable', function( isSelectable ){\n if( isSelectable ){\n this.$( '.primary-actions' ).hide();\n } else {\n this.$( '.primary-actions' ).show();\n }\n }, this );\n return this;\n },\n\n // ........................................................................ rendering\n /** In this override, call methods to build warnings, titlebar and primary actions */\n _buildNewRender : function(){\n var $newRender = ExpandableView.prototype._buildNewRender.call( this );\n $newRender.children( '.warnings' ).replaceWith( this._renderWarnings() );\n $newRender.children( '.title-bar' ).replaceWith( this._renderTitleBar() );\n $newRender.children( '.primary-actions' ).append( this._renderPrimaryActions() );\n $newRender.find( '> .title-bar .subtitle' ).replaceWith( this._renderSubtitle() );\n return $newRender;\n },\n\n /** In this override, render the selector controls and set up dragging before the swap */\n _swapNewRender : function( $newRender ){\n ExpandableView.prototype._swapNewRender.call( this, $newRender );\n if( this.selectable ){ this.showSelector( 0 ); }\n if( this.draggable ){ this.draggableOn(); }\n return this.$el;\n },\n\n /** Render any warnings the item may need to show (e.g. \"I'm deleted\") */\n _renderWarnings : function(){\n var view = this,\n $warnings = $( '
                                  ' ),\n json = view.model.toJSON();\n //TODO:! unordered (map)\n _.each( view.templates.warnings, function( templateFn ){\n $warnings.append( $( templateFn( json, view ) ) );\n });\n return $warnings;\n },\n\n /** Render the title bar (the main/exposed SUMMARY dom element) */\n _renderTitleBar : function(){\n return $( this.templates.titleBar( this.model.toJSON(), this ) );\n },\n\n /** Return an array of jQ objects containing common/easily-accessible item controls */\n _renderPrimaryActions : function(){\n // override this\n return [];\n },\n\n /** Render the title bar (the main/exposed SUMMARY dom element) */\n _renderSubtitle : function(){\n return $( this.templates.subtitle( this.model.toJSON(), this ) );\n },\n\n // ......................................................................... events\n /** event map */\n events : {\n // expand the body when the title is clicked or when in focus and space or enter is pressed\n 'click .title-bar' : '_clickTitleBar',\n 'keydown .title-bar' : '_keyDownTitleBar',\n 'click .selector' : 'toggleSelect'\n },\n\n /** expand when the title bar is clicked */\n _clickTitleBar : function( event ){\n event.stopPropagation();\n if( event.altKey ){\n this.toggleSelect( event );\n if( !this.selectable ){\n this.showSelector();\n }\n } else {\n this.toggleExpanded();\n }\n },\n\n /** expand when the title bar is in focus and enter or space is pressed */\n _keyDownTitleBar : function( event ){\n // bail (with propagation) if keydown and not space or enter\n var KEYCODE_SPACE = 32, KEYCODE_RETURN = 13;\n if( event && ( event.type === 'keydown' )\n &&( event.keyCode === KEYCODE_SPACE || event.keyCode === KEYCODE_RETURN ) ){\n this.toggleExpanded();\n event.stopPropagation();\n return false;\n }\n return true;\n },\n\n // ......................................................................... misc\n /** String representation */\n toString : function(){\n var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n return 'ListItemView(' + modelString + ')';\n }\n}));\n\n// ............................................................................ TEMPLATES\n/** underscore templates */\nListItemView.prototype.templates = (function(){\n\n var elTemplato = BASE_MVC.wrapTemplate([\n '
                                  ',\n // errors, messages, etc.\n '
                                  ',\n\n // multi-select checkbox\n '
                                  ',\n '',\n '
                                  ',\n // space for title bar buttons - gen. floated to the right\n '
                                  ',\n '
                                  ',\n\n // expandable area for more details\n '
                                  ',\n '
                                  '\n ]);\n\n var warnings = {};\n\n var titleBarTemplate = BASE_MVC.wrapTemplate([\n // adding a tabindex here allows focusing the title bar and the use of keydown to expand the dataset display\n '
                                  ',\n //TODO: prob. belongs in dataset-list-item\n '',\n '
                                  ',\n '<%- element.name %>',\n '
                                  ',\n '
                                  ',\n '
                                  '\n ], 'element' );\n\n var subtitleTemplate = BASE_MVC.wrapTemplate([\n // override this\n '
                                  '\n ]);\n\n var detailsTemplate = BASE_MVC.wrapTemplate([\n // override this\n '
                                  '\n ]);\n\n return {\n el : elTemplato,\n warnings : warnings,\n titleBar : titleBarTemplate,\n subtitle : subtitleTemplate,\n details : detailsTemplate\n };\n}());\n\n\n//==============================================================================\n/** A view that is displayed in some larger list/grid/collection.\n * *AND* can display some sub-list of it's own when expanded (e.g. dataset collections).\n * This list will 'foldout' when the item is expanded depending on this.foldoutStyle:\n * If 'foldout': will expand vertically to show the nested list\n * If 'drilldown': will overlay the parent list\n *\n * Inherits from ListItemView.\n *\n * _renderDetails does the work of creating this.details: a sub-view that shows the nested list\n */\nvar FoldoutListItemView = ListItemView.extend({\n\n /** If 'foldout': show the sub-panel inside the expanded item\n * If 'drilldown': only fire events and handle by pub-sub\n * (allow the panel containing this item to attach it, hide itself, etc.)\n */\n foldoutStyle : 'foldout',\n /** Panel view class to instantiate for the sub-panel */\n foldoutPanelClass : null,\n\n /** override to:\n * add attributes foldoutStyle and foldoutPanelClass for config poly\n * disrespect attributes.expanded if drilldown\n */\n initialize : function( attributes ){\n if( this.foldoutStyle === 'drilldown' ){ this.expanded = false; }\n this.foldoutStyle = attributes.foldoutStyle || this.foldoutStyle;\n this.foldoutPanelClass = attributes.foldoutPanelClass || this.foldoutPanelClass;\n\n ListItemView.prototype.initialize.call( this, attributes );\n this.foldout = this._createFoldoutPanel();\n },\n\n /** in this override, attach the foldout panel when rendering details */\n _renderDetails : function(){\n if( this.foldoutStyle === 'drilldown' ){ return $(); }\n var $newDetails = ListItemView.prototype._renderDetails.call( this );\n return this._attachFoldout( this.foldout, $newDetails );\n },\n\n /** In this override, handle collection expansion. */\n _createFoldoutPanel : function(){\n var model = this.model;\n var FoldoutClass = this._getFoldoutPanelClass( model ),\n options = this._getFoldoutPanelOptions( model ),\n foldout = new FoldoutClass( _.extend( options, {\n model : model\n }));\n return foldout;\n },\n\n /** Stub to return proper foldout panel class */\n _getFoldoutPanelClass : function(){\n // override\n return this.foldoutPanelClass;\n },\n\n /** Stub to return proper foldout panel options */\n _getFoldoutPanelOptions : function(){\n return {\n // propagate foldout style down\n foldoutStyle : this.foldoutStyle,\n fxSpeed : this.fxSpeed\n };\n },\n\n /** Render the foldout panel inside the view, hiding controls */\n _attachFoldout : function( foldout, $whereTo ){\n $whereTo = $whereTo || this.$( '> .details' );\n this.foldout = foldout.render( 0 );\n foldout.$( '> .controls' ).hide();\n return $whereTo.append( foldout.$el );\n },\n\n /** In this override, branch on foldoutStyle to show expanded */\n expand : function(){\n var view = this;\n return view._fetchModelDetails()\n .always(function(){\n if( view.foldoutStyle === 'foldout' ){\n view._expand();\n } else if( view.foldoutStyle === 'drilldown' ){\n view._expandByDrilldown();\n }\n });\n },\n\n /** For drilldown, set up close handler and fire expanded:drilldown\n * containing views can listen to this and handle other things\n * (like hiding themselves) by listening for expanded/collapsed:drilldown\n */\n _expandByDrilldown : function(){\n var view = this;\n // attachment and rendering done by listener\n view.listenTo( view.foldout, 'close', function(){\n view.trigger( 'collapsed:drilldown', view, view.foldout );\n });\n view.trigger( 'expanded:drilldown', view, view.foldout );\n }\n\n});\n\n// ............................................................................ TEMPLATES\n/** underscore templates */\nFoldoutListItemView.prototype.templates = (function(){\n\n var detailsTemplate = BASE_MVC.wrapTemplate([\n '
                                  ',\n // override with more info (that goes above the panel)\n '
                                  '\n ], 'collection' );\n\n return _.extend( {}, ListItemView.prototype.templates, {\n details : detailsTemplate\n });\n}());\n\n\n//==============================================================================\n return {\n ExpandableView : ExpandableView,\n ListItemView : ListItemView,\n FoldoutListItemView : FoldoutListItemView\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/list/list-item.js\n ** module id = 42\n ** module chunks = 3\n **/","/**\n This is the base class of the tool form plugin. This class is e.g. inherited by the regular and the workflow tool form.\n*/\ndefine(['utils/utils', 'utils/deferred', 'mvc/ui/ui-misc', 'mvc/form/form-view',\n 'mvc/citation/citation-model', 'mvc/citation/citation-view'],\n function(Utils, Deferred, Ui, FormBase, CitationModel, CitationView) {\n return FormBase.extend({\n initialize: function(options) {\n var self = this;\n FormBase.prototype.initialize.call(this, options);\n this.deferred = new Deferred();\n if (options.inputs) {\n this._buildForm(options);\n } else {\n this.deferred.execute(function(process) {\n self._buildModel(process, options, true);\n });\n }\n // listen to history panel\n if ( options.listen_to_history && parent.Galaxy && parent.Galaxy.currHistoryPanel ) {\n this.listenTo( parent.Galaxy.currHistoryPanel.collection, 'change', function() {\n this.refresh();\n });\n }\n // destroy dom elements\n this.$el.on( 'remove', function() { self.remove() } );\n },\n\n /** Listen to history panel changes and update the tool form */\n refresh: function() {\n var self = this;\n self.deferred.reset();\n this.deferred.execute( function (process){\n self._updateModel( process)\n });\n },\n\n /** Wait for deferred build processes before removal */\n remove: function() {\n var self = this;\n this.$el.hide();\n this.deferred.execute(function(){\n FormBase.prototype.remove.call(self);\n Galaxy.emit.debug('tool-form-base::remove()', 'Destroy view.');\n });\n },\n\n /** Build form */\n _buildForm: function(options) {\n var self = this;\n this.options = Utils.merge(options, this.options);\n this.options = Utils.merge({\n icon : options.icon,\n title : '' + options.name + ' ' + options.description + ' (Galaxy Version ' + options.version + ')',\n operations : !this.options.hide_operations && this._operations(),\n onchange : function() {\n self.refresh();\n }\n }, this.options);\n this.options.customize && this.options.customize( this.options );\n this.render();\n if ( !this.options.collapsible ) {\n this.$el.append( $( '
                                  ' ).addClass( 'ui-margin-top-large' ).append( this._footer() ) );\n }\n },\n\n /** Builds a new model through api call and recreates the entire form\n */\n _buildModel: function(process, options, hide_message) {\n var self = this;\n this.options.id = options.id;\n this.options.version = options.version;\n\n // build request url\n var build_url = '';\n var build_data = {};\n if ( options.job_id ) {\n build_url = Galaxy.root + 'api/jobs/' + options.job_id + '/build_for_rerun';\n } else {\n build_url = Galaxy.root + 'api/tools/' + options.id + '/build';\n if ( Galaxy.params && Galaxy.params.tool_id == options.id ) {\n build_data = $.extend( {}, Galaxy.params );\n options.version && ( build_data[ 'tool_version' ] = options.version );\n }\n }\n\n // get initial model\n Utils.get({\n url : build_url,\n data : build_data,\n success : function(new_model) {\n new_model = new_model.tool_model || new_model;\n if( !new_model.display ) {\n window.location = Galaxy.root;\n return;\n }\n self._buildForm(new_model);\n !hide_message && self.message.update({\n status : 'success',\n message : 'Now you are using \\'' + self.options.name + '\\' version ' + self.options.version + ', id \\'' + self.options.id + '\\'.',\n persistent : false\n });\n Galaxy.emit.debug('tool-form-base::initialize()', 'Initial tool model ready.', new_model);\n process.resolve();\n },\n error : function(response, status) {\n var error_message = ( response && response.err_msg ) || 'Uncaught error.';\n if ( status == 401 ) {\n window.location = Galaxy.root + 'user/login?' + $.param({ redirect : Galaxy.root + '?tool_id=' + self.options.id });\n } else if ( self.$el.is(':empty') ) {\n self.$el.prepend((new Ui.Message({\n message : error_message,\n status : 'danger',\n persistent : true,\n large : true\n })).$el);\n } else {\n Galaxy.modal && Galaxy.modal.show({\n title : 'Tool request failed',\n body : error_message,\n buttons : {\n 'Close' : function() {\n Galaxy.modal.hide();\n }\n }\n });\n }\n Galaxy.emit.debug('tool-form::initialize()', 'Initial tool model request failed.', response);\n process.reject();\n }\n });\n },\n\n /** Request a new model for an already created tool form and updates the form inputs\n */\n _updateModel: function(process) {\n // link this\n var self = this;\n var model_url = this.options.update_url || Galaxy.root + 'api/tools/' + this.options.id + '/build';\n var current_state = {\n tool_id : this.options.id,\n tool_version : this.options.version,\n inputs : $.extend(true, {}, self.data.create())\n }\n this.wait(true);\n\n // log tool state\n Galaxy.emit.debug('tool-form-base::_updateModel()', 'Sending current state.', current_state);\n\n // post job\n Utils.request({\n type : 'POST',\n url : model_url,\n data : current_state,\n success : function(new_model) {\n self.update(new_model['tool_model'] || new_model);\n self.options.update && self.options.update(new_model);\n self.wait(false);\n Galaxy.emit.debug('tool-form-base::_updateModel()', 'Received new model.', new_model);\n process.resolve();\n },\n error : function(response) {\n Galaxy.emit.debug('tool-form-base::_updateModel()', 'Refresh request failed.', response);\n process.reject();\n }\n });\n },\n\n /** Create tool operation menu\n */\n _operations: function() {\n var self = this;\n var options = this.options;\n\n // button for version selection\n var versions_button = new Ui.ButtonMenu({\n icon : 'fa-cubes',\n title : (!options.narrow && 'Versions') || null,\n tooltip : 'Select another tool version'\n });\n if (!options.sustain_version && options.versions && options.versions.length > 1) {\n for (var i in options.versions) {\n var version = options.versions[i];\n if (version != options.version) {\n versions_button.addMenu({\n title : 'Switch to ' + version,\n version : version,\n icon : 'fa-cube',\n onclick : function() {\n // here we update the tool version (some tools encode the version also in the id)\n var id = options.id.replace(options.version, this.version);\n var version = this.version;\n // queue model request\n self.deferred.reset();\n self.deferred.execute(function(process) {\n self._buildModel(process, {id: id, version: version})\n });\n }\n });\n }\n }\n } else {\n versions_button.$el.hide();\n }\n\n // button for options e.g. search, help\n var menu_button = new Ui.ButtonMenu({\n icon : 'fa-caret-down',\n title : (!options.narrow && 'Options') || null,\n tooltip : 'View available options'\n });\n if(options.biostar_url) {\n menu_button.addMenu({\n icon : 'fa-question-circle',\n title : 'Question?',\n tooltip : 'Ask a question about this tool (Biostar)',\n onclick : function() {\n window.open(options.biostar_url + '/p/new/post/');\n }\n });\n menu_button.addMenu({\n icon : 'fa-search',\n title : 'Search',\n tooltip : 'Search help for this tool (Biostar)',\n onclick : function() {\n window.open(options.biostar_url + '/local/search/page/?q=' + options.name);\n }\n });\n };\n menu_button.addMenu({\n icon : 'fa-share',\n title : 'Share',\n tooltip : 'Share this tool',\n onclick : function() {\n prompt('Copy to clipboard: Ctrl+C, Enter', window.location.origin + Galaxy.root + 'root?tool_id=' + options.id);\n }\n });\n\n // add admin operations\n if (Galaxy.user && Galaxy.user.get('is_admin')) {\n menu_button.addMenu({\n icon : 'fa-download',\n title : 'Download',\n tooltip : 'Download this tool',\n onclick : function() {\n window.location.href = Galaxy.root + 'api/tools/' + options.id + '/download';\n }\n });\n }\n\n // button for version selection\n if (options.requirements && options.requirements.length > 0) {\n menu_button.addMenu({\n icon : 'fa-info-circle',\n title : 'Requirements',\n tooltip : 'Display tool requirements',\n onclick : function() {\n if (!this.visible || self.portlet.collapsed ) {\n this.visible = true;\n self.portlet.expand();\n self.message.update({\n persistent : true,\n message : self._templateRequirements(options),\n status : 'info'\n });\n } else {\n this.visible = false;\n self.message.update({\n message : ''\n });\n }\n }\n });\n }\n\n // add toolshed url\n if (options.sharable_url) {\n menu_button.addMenu({\n icon : 'fa-external-link',\n title : 'See in Tool Shed',\n tooltip : 'Access the repository',\n onclick : function() {\n window.open(options.sharable_url);\n }\n });\n }\n\n return {\n menu : menu_button,\n versions : versions_button\n }\n },\n\n /** Create footer\n */\n _footer: function() {\n var options = this.options;\n var $el = $( '
                                  ' ).append( this._templateHelp( options ) );\n if ( options.citations ) {\n var $citations = $( '
                                  ' );\n var citations = new CitationModel.ToolCitationCollection();\n citations.tool_id = options.id;\n var citation_list_view = new CitationView.CitationListView({ el: $citations, collection: citations });\n citation_list_view.render();\n citations.fetch();\n $el.append( $citations );\n }\n return $el;\n },\n\n /** Templates\n */\n _templateHelp: function( options ) {\n var $tmpl = $( '
                                  ' ).addClass( 'ui-form-help' ).append( options.help );\n $tmpl.find( 'a' ).attr( 'target', '_blank' );\n return $tmpl;\n },\n\n _templateRequirements: function( options ) {\n var nreq = options.requirements.length;\n if ( nreq > 0 ) {\n var requirements_message = 'This tool requires ';\n _.each( options.requirements, function( req, i ) {\n requirements_message += req.name + ( req.version ? ' (Version ' + req.version + ')' : '' ) + ( i < nreq - 2 ? ', ' : ( i == nreq - 2 ? ' and ' : '' ) );\n });\n var requirements_link = $( '' ).attr( 'target', '_blank' ).attr( 'href', 'https://wiki.galaxyproject.org/Tools/Requirements' ).text( 'here' );\n return $( '' ).append( requirements_message + '. Click ' ).append( requirements_link ).append( ' for more information.' );\n }\n return 'No requirements found.';\n }\n });\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/tool/tool-form-base.js\n ** module id = 43\n ** module chunks = 0 3\n **/","/**\n * Model, view, and controller objects for Galaxy tools and tool panel.\n */\n\n define([\n \"libs/underscore\",\n \"viz/trackster/util\",\n \"mvc/dataset/data\",\n \"mvc/tool/tool-form\"\n\n], function(_, util, data, ToolForm) {\n 'use strict';\n\n/**\n * Mixin for tracking model visibility.\n */\nvar VisibilityMixin = {\n hidden: false,\n\n show: function() {\n this.set(\"hidden\", false);\n },\n\n hide: function() {\n this.set(\"hidden\", true);\n },\n\n toggle: function() {\n this.set(\"hidden\", !this.get(\"hidden\"));\n },\n\n is_visible: function() {\n return !this.attributes.hidden;\n }\n\n};\n\n/**\n * A tool parameter.\n */\nvar ToolParameter = Backbone.Model.extend({\n defaults: {\n name: null,\n label: null,\n type: null,\n value: null,\n html: null,\n num_samples: 5\n },\n\n initialize: function(options) {\n this.attributes.html = unescape(this.attributes.html);\n },\n\n copy: function() {\n return new ToolParameter(this.toJSON());\n },\n\n set_value: function(value) {\n this.set('value', value || '');\n }\n});\n\nvar ToolParameterCollection = Backbone.Collection.extend({\n model: ToolParameter\n});\n\n/**\n * A data tool parameter.\n */\nvar DataToolParameter = ToolParameter.extend({});\n\n/**\n * An integer tool parameter.\n */\nvar IntegerToolParameter = ToolParameter.extend({\n set_value: function(value) {\n this.set('value', parseInt(value, 10));\n },\n\n /**\n * Returns samples from a tool input.\n */\n get_samples: function() {\n return d3.scale.linear()\n .domain([this.get('min'), this.get('max')])\n .ticks(this.get('num_samples'));\n }\n});\n\nvar FloatToolParameter = IntegerToolParameter.extend({\n set_value: function(value) {\n this.set('value', parseFloat(value));\n }\n});\n\n/**\n * A select tool parameter.\n */\nvar SelectToolParameter = ToolParameter.extend({\n /**\n * Returns tool options.\n */\n get_samples: function() {\n return _.map(this.get('options'), function(option) {\n return option[0];\n });\n }\n});\n\n// Set up dictionary of parameter types.\nToolParameter.subModelTypes = {\n 'integer': IntegerToolParameter,\n 'float': FloatToolParameter,\n 'data': DataToolParameter,\n 'select': SelectToolParameter\n};\n\n/**\n * A Galaxy tool.\n */\nvar Tool = Backbone.Model.extend({\n // Default attributes.\n defaults: {\n id: null,\n name: null,\n description: null,\n target: null,\n inputs: [],\n outputs: []\n },\n\n urlRoot: Galaxy.root + 'api/tools',\n\n initialize: function(options) {\n\n // Set parameters.\n this.set('inputs', new ToolParameterCollection(_.map(options.inputs, function(p) {\n var p_class = ToolParameter.subModelTypes[p.type] || ToolParameter;\n return new p_class(p);\n })));\n },\n\n /**\n *\n */\n toJSON: function() {\n var rval = Backbone.Model.prototype.toJSON.call(this);\n\n // Convert inputs to JSON manually.\n rval.inputs = this.get('inputs').map(function(i) { return i.toJSON(); });\n return rval;\n },\n\n /**\n * Removes inputs of a particular type; this is useful because not all inputs can be handled by\n * client and server yet.\n */\n remove_inputs: function(types) {\n var tool = this,\n incompatible_inputs = tool.get('inputs').filter( function(input) {\n return ( types.indexOf( input.get('type') ) !== -1);\n });\n tool.get('inputs').remove(incompatible_inputs);\n },\n\n /**\n * Returns object copy, optionally including only inputs that can be sampled.\n */\n copy: function(only_samplable_inputs) {\n var copy = new Tool(this.toJSON());\n\n // Return only samplable inputs if flag is set.\n if (only_samplable_inputs) {\n var valid_inputs = new Backbone.Collection();\n copy.get('inputs').each(function(input) {\n if (input.get_samples()) {\n valid_inputs.push(input);\n }\n });\n copy.set('inputs', valid_inputs);\n }\n\n return copy;\n },\n\n apply_search_results: function(results) {\n ( _.indexOf(results, this.attributes.id) !== -1 ? this.show() : this.hide() );\n return this.is_visible();\n },\n\n /**\n * Set a tool input's value.\n */\n set_input_value: function(name, value) {\n this.get('inputs').find(function(input) {\n return input.get('name') === name;\n }).set('value', value);\n },\n\n /**\n * Set many input values at once.\n */\n set_input_values: function(inputs_dict) {\n var self = this;\n _.each(_.keys(inputs_dict), function(input_name) {\n self.set_input_value(input_name, inputs_dict[input_name]);\n });\n },\n\n /**\n * Run tool; returns a Deferred that resolves to the tool's output(s).\n */\n run: function() {\n return this._run();\n },\n\n /**\n * Rerun tool using regions and a target dataset.\n */\n rerun: function(target_dataset, regions) {\n return this._run({\n action: 'rerun',\n target_dataset_id: target_dataset.id,\n regions: regions\n });\n },\n\n /**\n * Returns input dict for tool's inputs.\n */\n get_inputs_dict: function() {\n var input_dict = {};\n this.get('inputs').each(function(input) {\n input_dict[input.get('name')] = input.get('value');\n });\n return input_dict;\n },\n\n /**\n * Run tool; returns a Deferred that resolves to the tool's output(s).\n * NOTE: this method is a helper method and should not be called directly.\n */\n _run: function(additional_params) {\n // Create payload.\n var payload = _.extend({\n tool_id: this.id,\n inputs: this.get_inputs_dict()\n }, additional_params);\n\n // Because job may require indexing datasets, use server-side\n // deferred to ensure that job is run. Also use deferred that\n // resolves to outputs from tool.\n var run_deferred = $.Deferred(),\n ss_deferred = new util.ServerStateDeferred({\n ajax_settings: {\n url: this.urlRoot,\n data: JSON.stringify(payload),\n dataType: \"json\",\n contentType: 'application/json',\n type: \"POST\"\n },\n interval: 2000,\n success_fn: function(response) {\n return response !== \"pending\";\n }\n });\n\n // Run job and resolve run_deferred to tool outputs.\n $.when(ss_deferred.go()).then(function(result) {\n run_deferred.resolve(new data.DatasetCollection(result));\n });\n return run_deferred;\n }\n});\n_.extend(Tool.prototype, VisibilityMixin);\n\n/**\n * Tool view.\n */\nvar ToolView = Backbone.View.extend({\n\n});\n\n/**\n * Wrap collection of tools for fast access/manipulation.\n */\nvar ToolCollection = Backbone.Collection.extend({\n model: Tool\n});\n\n/**\n * Label or section header in tool panel.\n */\nvar ToolSectionLabel = Backbone.Model.extend(VisibilityMixin);\n\n/**\n * Section of tool panel with elements (labels and tools).\n */\nvar ToolSection = Backbone.Model.extend({\n defaults: {\n elems: [],\n open: false\n },\n\n clear_search_results: function() {\n _.each(this.attributes.elems, function(elt) {\n elt.show();\n });\n\n this.show();\n this.set(\"open\", false);\n },\n\n apply_search_results: function(results) {\n var all_hidden = true,\n cur_label;\n _.each(this.attributes.elems, function(elt) {\n if (elt instanceof ToolSectionLabel) {\n cur_label = elt;\n cur_label.hide();\n }\n else if (elt instanceof Tool) {\n if (elt.apply_search_results(results)) {\n all_hidden = false;\n if (cur_label) {\n cur_label.show();\n }\n }\n }\n });\n\n if (all_hidden) {\n this.hide();\n }\n else {\n this.show();\n this.set(\"open\", true);\n }\n }\n});\n_.extend(ToolSection.prototype, VisibilityMixin);\n\n/**\n * Tool search that updates results when query is changed. Result value of null\n * indicates that query was not run; if not null, results are from search using\n * query.\n */\nvar ToolSearch = Backbone.Model.extend({\n defaults: {\n search_hint_string: \"search tools\",\n min_chars_for_search: 3,\n clear_btn_url: \"\",\n search_url: \"\",\n visible: true,\n query: \"\",\n results: null,\n // ESC (27) will clear the input field and tool search filters\n clear_key: 27\n },\n\n urlRoot: Galaxy.root + 'api/tools',\n\n initialize: function() {\n this.on(\"change:query\", this.do_search);\n },\n\n /**\n * Do the search and update the results.\n */\n do_search: function() {\n var query = this.attributes.query;\n\n // If query is too short, do not search.\n if (query.length < this.attributes.min_chars_for_search) {\n this.set(\"results\", null);\n return;\n }\n\n // Do search via AJAX.\n var q = query;\n // Stop previous ajax-request\n if (this.timer) {\n clearTimeout(this.timer);\n }\n // Start a new ajax-request in X ms\n $(\"#search-clear-btn\").hide();\n $(\"#search-spinner\").show();\n var self = this;\n this.timer = setTimeout(function () {\n // log the search to analytics if present\n if ( typeof ga !== 'undefined' ) {\n ga( 'send', 'pageview', Galaxy.root + '?q=' + q );\n }\n $.get( self.urlRoot, { q: q }, function (data) {\n self.set(\"results\", data);\n $(\"#search-spinner\").hide();\n $(\"#search-clear-btn\").show();\n }, \"json\" );\n }, 400 );\n },\n\n clear_search: function() {\n this.set(\"query\", \"\");\n this.set(\"results\", null);\n }\n\n});\n_.extend(ToolSearch.prototype, VisibilityMixin);\n\n/**\n * Tool Panel.\n */\nvar ToolPanel = Backbone.Model.extend({\n\n initialize: function(options) {\n this.attributes.tool_search = options.tool_search;\n this.attributes.tool_search.on(\"change:results\", this.apply_search_results, this);\n this.attributes.tools = options.tools;\n this.attributes.layout = new Backbone.Collection( this.parse(options.layout) );\n },\n\n /**\n * Parse tool panel dictionary and return collection of tool panel elements.\n */\n parse: function(response) {\n // Recursive function to parse tool panel elements.\n var self = this,\n // Helper to recursively parse tool panel.\n parse_elt = function(elt_dict) {\n var type = elt_dict.model_class;\n // There are many types of tools; for now, anything that ends in 'Tool'\n // is treated as a generic tool.\n if ( type.indexOf('Tool') === type.length - 4 ) {\n return self.attributes.tools.get(elt_dict.id);\n }\n else if (type === 'ToolSection') {\n // Parse elements.\n var elems = _.map(elt_dict.elems, parse_elt);\n elt_dict.elems = elems;\n return new ToolSection(elt_dict);\n }\n else if (type === 'ToolSectionLabel') {\n return new ToolSectionLabel(elt_dict);\n }\n };\n\n return _.map(response, parse_elt);\n },\n\n clear_search_results: function() {\n this.get('layout').each(function(panel_elt) {\n if (panel_elt instanceof ToolSection) {\n panel_elt.clear_search_results();\n }\n else {\n // Label or tool, so just show.\n panel_elt.show();\n }\n });\n },\n\n apply_search_results: function() {\n var results = this.get('tool_search').get('results');\n if (results === null) {\n this.clear_search_results();\n return;\n }\n\n var cur_label = null;\n this.get('layout').each(function(panel_elt) {\n if (panel_elt instanceof ToolSectionLabel) {\n cur_label = panel_elt;\n cur_label.hide();\n }\n else if (panel_elt instanceof Tool) {\n if (panel_elt.apply_search_results(results)) {\n if (cur_label) {\n cur_label.show();\n }\n }\n }\n else {\n // Starting new section, so clear current label.\n cur_label = null;\n panel_elt.apply_search_results(results);\n }\n });\n }\n});\n\n/**\n * View classes for Galaxy tools and tool panel.\n *\n * Views use the templates defined below for rendering. Views update as needed\n * based on (a) model/collection events and (b) user interactions; in this sense,\n * they are controllers are well and the HTML is the real view in the MVC architecture.\n */\n\n/**\n * Base view that handles visibility based on model's hidden attribute.\n */\nvar BaseView = Backbone.View.extend({\n initialize: function() {\n this.model.on(\"change:hidden\", this.update_visible, this);\n this.update_visible();\n },\n update_visible: function() {\n ( this.model.attributes.hidden ? this.$el.hide() : this.$el.show() );\n }\n});\n\n/**\n * Link to a tool.\n */\nvar ToolLinkView = BaseView.extend({\n tagName: 'div',\n\n render: function() {\n // create element\n var $link = $('
                                  ');\n $link.append(templates.tool_link(this.model.toJSON()));\n\n var formStyle = this.model.get( 'form_style', null );\n // open upload dialog for upload tool\n if (this.model.id === 'upload1') {\n $link.find('a').on('click', function(e) {\n e.preventDefault();\n Galaxy.upload.show();\n });\n }\n else if ( formStyle === 'regular' ) { // regular tools\n var self = this;\n $link.find('a').on('click', function(e) {\n e.preventDefault();\n var form = new ToolForm.View( { id : self.model.id, version : self.model.get('version') } );\n form.deferred.execute(function() {\n Galaxy.app.display( form );\n });\n });\n }\n\n // add element\n this.$el.append($link);\n return this;\n }\n});\n\n/**\n * Panel label/section header.\n */\nvar ToolSectionLabelView = BaseView.extend({\n tagName: 'div',\n className: 'toolPanelLabel',\n\n render: function() {\n this.$el.append( $(\"\").text(this.model.attributes.text) );\n return this;\n }\n});\n\n/**\n * Panel section.\n */\nvar ToolSectionView = BaseView.extend({\n tagName: 'div',\n className: 'toolSectionWrapper',\n\n initialize: function() {\n BaseView.prototype.initialize.call(this);\n this.model.on(\"change:open\", this.update_open, this);\n },\n\n render: function() {\n // Build using template.\n this.$el.append( templates.panel_section(this.model.toJSON()) );\n\n // Add tools to section.\n var section_body = this.$el.find(\".toolSectionBody\");\n _.each(this.model.attributes.elems, function(elt) {\n if (elt instanceof Tool) {\n var tool_view = new ToolLinkView({model: elt, className: \"toolTitle\"});\n tool_view.render();\n section_body.append(tool_view.$el);\n }\n else if (elt instanceof ToolSectionLabel) {\n var label_view = new ToolSectionLabelView({model: elt});\n label_view.render();\n section_body.append(label_view.$el);\n }\n else {\n // TODO: handle nested section bodies?\n }\n });\n return this;\n },\n\n events: {\n 'click .toolSectionTitle > a': 'toggle'\n },\n\n /**\n * Toggle visibility of tool section.\n */\n toggle: function() {\n this.model.set(\"open\", !this.model.attributes.open);\n },\n\n /**\n * Update whether section is open or close.\n */\n update_open: function() {\n (this.model.attributes.open ?\n this.$el.children(\".toolSectionBody\").slideDown(\"fast\") :\n this.$el.children(\".toolSectionBody\").slideUp(\"fast\")\n );\n }\n});\n\nvar ToolSearchView = Backbone.View.extend({\n tagName: 'div',\n id: 'tool-search',\n className: 'bar',\n\n events: {\n 'click': 'focus_and_select',\n 'keyup :input': 'query_changed',\n 'click #search-clear-btn': 'clear'\n },\n\n render: function() {\n this.$el.append( templates.tool_search(this.model.toJSON()) );\n if (!this.model.is_visible()) {\n this.$el.hide();\n }\n\n // Adjust top for issue 2907 depending on whether the messagebox is visible.\n if ($(\"#messagebox\").is(\":visible\")) {\n this.$el.css(\"top\",\"95px\");\n }\n\n this.$el.find('[title]').tooltip();\n return this;\n },\n\n focus_and_select: function() {\n this.$el.find(\":input\").focus().select();\n },\n\n clear: function() {\n this.model.clear_search();\n this.$el.find(\":input\").val('');\n this.focus_and_select();\n return false;\n },\n\n query_changed: function( evData ) {\n // check for the 'clear key' (ESC) first\n if( ( this.model.attributes.clear_key ) &&\n ( this.model.attributes.clear_key === evData.which ) ){\n this.clear();\n return false;\n }\n this.model.set(\"query\", this.$el.find(\":input\").val());\n }\n});\n\n/**\n * Tool panel view. Events triggered include:\n * tool_link_click(click event, tool_model)\n */\nvar ToolPanelView = Backbone.View.extend({\n tagName: 'div',\n className: 'toolMenu',\n\n /**\n * Set up view.\n */\n initialize: function() {\n this.model.get('tool_search').on(\"change:results\", this.handle_search_results, this);\n },\n\n render: function() {\n var self = this;\n\n // Render search.\n var search_view = new ToolSearchView( { model: this.model.get('tool_search') } );\n search_view.render();\n self.$el.append(search_view.$el);\n\n // Render panel.\n this.model.get('layout').each(function(panel_elt) {\n if (panel_elt instanceof ToolSection) {\n var section_title_view = new ToolSectionView({model: panel_elt});\n section_title_view.render();\n self.$el.append(section_title_view.$el);\n }\n else if (panel_elt instanceof Tool) {\n var tool_view = new ToolLinkView({model: panel_elt, className: \"toolTitleNoSection\"});\n tool_view.render();\n self.$el.append(tool_view.$el);\n }\n else if (panel_elt instanceof ToolSectionLabel) {\n var label_view = new ToolSectionLabelView({model: panel_elt});\n label_view.render();\n self.$el.append(label_view.$el);\n }\n });\n\n // Setup tool link click eventing.\n self.$el.find(\"a.tool-link\").click(function(e) {\n // Tool id is always the first class.\n var\n tool_id = $(this).attr('class').split(/\\s+/)[0],\n tool = self.model.get('tools').get(tool_id);\n\n self.trigger(\"tool_link_click\", e, tool);\n });\n\n return this;\n },\n\n handle_search_results: function() {\n var results = this.model.get('tool_search').get('results');\n if (results && results.length === 0) {\n $(\"#search-no-results\").show();\n }\n else {\n $(\"#search-no-results\").hide();\n }\n }\n});\n\n/**\n * View for working with a tool: setting parameters and inputs and executing the tool.\n */\nvar ToolFormView = Backbone.View.extend({\n className: 'toolForm',\n\n render: function() {\n this.$el.children().remove();\n this.$el.append( templates.tool_form(this.model.toJSON()) );\n }\n});\n\n/**\n * Integrated tool menu + tool execution.\n */\nvar IntegratedToolMenuAndView = Backbone.View.extend({\n className: 'toolMenuAndView',\n\n initialize: function() {\n this.tool_panel_view = new ToolPanelView({collection: this.collection});\n this.tool_form_view = new ToolFormView();\n },\n\n render: function() {\n // Render and append tool panel.\n this.tool_panel_view.render();\n this.tool_panel_view.$el.css(\"float\", \"left\");\n this.$el.append(this.tool_panel_view.$el);\n\n // Append tool form view.\n this.tool_form_view.$el.hide();\n this.$el.append(this.tool_form_view.$el);\n\n // On tool link click, show tool.\n var self = this;\n this.tool_panel_view.on(\"tool_link_click\", function(e, tool) {\n // Prevents click from activating link:\n e.preventDefault();\n // Show tool that was clicked on:\n self.show_tool(tool);\n });\n },\n\n /**\n * Fetch and display tool.\n */\n show_tool: function(tool) {\n var self = this;\n tool.fetch().done( function() {\n self.tool_form_view.model = tool;\n self.tool_form_view.render();\n self.tool_form_view.$el.show();\n $('#left').width(\"650px\");\n });\n }\n});\n\n// TODO: move into relevant views\nvar templates = {\n // the search bar at the top of the tool panel\n tool_search : _.template([\n '\" autocomplete=\"off\" type=\"text\" />',\n ' ',\n //TODO: replace with icon\n '',\n ].join('')),\n\n // the category level container in the tool panel (e.g. 'Get Data', 'Text Manipulation')\n panel_section : _.template([\n '
                                  \">',\n '<%- name %>',\n '
                                  ',\n '
                                  \" class=\"toolSectionBody\" style=\"display: none;\">',\n '
                                  ',\n '
                                  '\n ].join('')),\n\n // a single tool's link in the tool panel; will load the tool form in the center panel\n tool_link : _.template([\n '',\n '<% _.each( labels, function( label ){ %>',\n '\">',\n '<%- label %>',\n '',\n '<% }); %>',\n '',\n ' tool-link\" href=\"<%= link %>\" target=\"<%- target %>\" minsizehint=\"<%- min_width %>\">',\n '<%- name %>',\n '',\n ' <%- description %>'\n ].join('')),\n\n // the tool form for entering tool parameters, viewing help and executing the tool\n // loaded when a tool link is clicked in the tool panel\n tool_form : _.template([\n '
                                  <%- tool.name %> (version <%- tool.version %>)
                                  ',\n '
                                  ',\n '<% _.each( tool.inputs, function( input ){ %>',\n '
                                  ',\n '',\n '
                                  ',\n '<%= input.html %>',\n '
                                  ',\n '
                                  ',\n '<%- input.help %>',\n '
                                  ',\n '
                                  ',\n '
                                  ',\n '<% }); %>',\n '
                                  ',\n '
                                  ',\n '',\n '
                                  ',\n '
                                  ',\n '
                                  <% tool.help %>
                                  ',\n '
                                  ',\n // TODO: we need scoping here because 'help' is the dom for the help menu in the masthead\n // which implies a leaky variable that I can't find\n ].join(''), { variable: 'tool' }),\n};\n\n\n// Exports\nreturn {\n ToolParameter: ToolParameter,\n IntegerToolParameter: IntegerToolParameter,\n SelectToolParameter: SelectToolParameter,\n Tool: Tool,\n ToolCollection: ToolCollection,\n ToolSearch: ToolSearch,\n ToolPanel: ToolPanel,\n ToolPanelView: ToolPanelView,\n ToolFormView: ToolFormView\n};\n\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/tool/tools.js\n ** module id = 44\n ** module chunks = 0 3\n **/","/** Renders the color picker used e.g. in the tool form **/\ndefine(['utils/utils'], function( Utils ) {\n return Backbone.View.extend({\n colors: {\n standard: ['c00000','ff0000','ffc000','ffff00','92d050','00b050','00b0f0','0070c0','002060','7030a0'],\n base : ['ffffff','000000','eeece1','1f497d','4f81bd','c0504d','9bbb59','8064a2','4bacc6','f79646'],\n theme :[['f2f2f2','7f7f7f','ddd9c3','c6d9f0','dbe5f1','f2dcdb','ebf1dd','e5e0ec','dbeef3','fdeada'],\n ['d8d8d8','595959','c4bd97','8db3e2','b8cce4','e5b9b7','d7e3bc','ccc1d9','b7dde8','fbd5b5'],\n ['bfbfbf','3f3f3f','938953','548dd4','95b3d7','d99694','c3d69b','b2a2c7','92cddc','fac08f'],\n ['a5a5a5','262626','494429','17365d','366092','953734','76923c','5f497a','31859b','e36c09'],\n ['7f7f7e','0c0c0c','1d1b10','0f243e','244061','632423','4f6128','3f3151','205867','974806']]\n },\n\n initialize : function( options ) {\n this.options = Utils.merge( options, {} );\n this.setElement( this._template() );\n this.$panel = this.$( '.ui-color-picker-panel' );\n this.$view = this.$( '.ui-color-picker-view' );\n this.$value = this.$( '.ui-color-picker-value' );\n this.$header = this.$( '.ui-color-picker-header' );\n this._build();\n this.visible = false;\n this.value( this.options.value );\n this.$boxes = this.$( '.ui-color-picker-box' );\n var self = this;\n this.$boxes.on( 'click', function() {\n self.value( $( this ).css( 'background-color' ) );\n self.$header.trigger( 'click' );\n } );\n this.$header.on( 'click', function() {\n self.visible = !self.visible;\n if ( self.visible ) {\n self.$view.fadeIn( 'fast' );\n } else {\n self.$view.fadeOut( 'fast' );\n }\n } );\n },\n\n /** Get/set value */\n value : function ( new_val ) {\n if ( new_val !== undefined && new_val !== null ) {\n this.$value.css( 'background-color', new_val );\n this.$( '.ui-color-picker-box' ).empty();\n this.$( this._getValue() ).html( this._templateCheck() );\n this.options.onchange && this.options.onchange( new_val );\n }\n return this._getValue();\n },\n\n /** Get value from dom */\n _getValue: function() {\n var rgb = this.$value.css( 'background-color' );\n rgb = rgb.match(/^rgb\\((\\d+),\\s*(\\d+),\\s*(\\d+)\\)$/);\n if ( rgb ) {\n function hex( x ) {\n return ( '0' + parseInt( x ).toString( 16 ) ).slice( -2 );\n }\n return '#' + hex( rgb[ 1] ) + hex( rgb[ 2 ] ) + hex( rgb[ 3 ] );\n } else {\n return null;\n }\n },\n\n /** Build color panel */\n _build: function() {\n var $content = this._content({\n label : 'Theme Colors',\n colors : this.colors.base,\n padding : 10\n });\n for ( var i in this.colors.theme ) {\n var line_def = {};\n if ( i == 0 ) {\n line_def[ 'bottom' ] = true;\n } else {\n if ( i != this.colors.theme.length - 1 ) {\n line_def[ 'top' ] = true;\n line_def[ 'bottom' ] = true;\n } else {\n line_def[ 'top' ] = true;\n line_def[ 'padding' ] = 5;\n }\n }\n line_def[ 'colors' ] = this.colors.theme[ i ];\n this._content( line_def );\n }\n this._content({\n label : 'Standard Colors',\n colors : this.colors.standard,\n padding : 5\n });\n },\n\n /** Create content */\n _content: function( options ) {\n var label = options.label;\n var colors = options.colors;\n var padding = options.padding;\n var top = options.top;\n var bottom = options.bottom;\n var $content = $( this._templateContent() );\n var $label = $content.find( '.label' );\n if ( options.label ) {\n $label.html( options.label );\n } else {\n $label.hide();\n }\n var $line = $content.find( '.line' );\n this.$panel.append( $content );\n for ( var i in colors ) {\n var $box = $( this._templateBox( colors[ i ] ) );\n if ( top ) {\n $box.css( 'border-top', 'none' );\n $box.css( 'border-top-left-radius', '0px' );\n $box.css( 'border-top-right-radius', '0px' );\n }\n if ( bottom ) {\n $box.css( 'border-bottom', 'none' );\n $box.css( 'border-bottom-left-radius', '0px' );\n $box.css( 'border-bottom-right-radius', '0px' );\n }\n $line.append( $box );\n }\n if (padding) {\n $line.css( 'padding-bottom', padding );\n }\n return $content;\n },\n\n /** Check icon */\n _templateCheck: function() {\n return '
                                  ';\n },\n\n /** Content template */\n _templateContent: function() {\n return '
                                  ' +\n '
                                  ' +\n '
                                  ' +\n '
                                  ';\n },\n\n /** Box template */\n _templateBox: function( color ) {\n return '
                                  ';\n },\n\n /** Main template */\n _template: function() {\n return '
                                  ' +\n '
                                  ' +\n '
                                  ' +\n '
                                  Select a color
                                  ' +\n '
                                  ' +\n '
                                  ' +\n '
                                  ' +\n '
                                  '\n '
                                  ';\n }\n });\n});\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/ui/ui-color-picker.js\n ** module id = 46\n ** module chunks = 0 3\n **/","/** This class creates/wraps a drill down element. */\ndefine([ 'utils/utils', 'mvc/ui/ui-options' ], function( Utils, Options ) {\n\nvar View = Options.BaseIcons.extend({\n initialize: function( options ) {\n options.type = options.display || 'checkbox';\n options.multiple = ( options.type == 'checkbox' );\n Options.BaseIcons.prototype.initialize.call( this, options );\n },\n\n /** Set states for selected values */\n _setValue: function ( new_value ) {\n Options.BaseIcons.prototype._setValue.call( this, new_value );\n if ( new_value !== undefined && new_value !== null && this.header_index ) {\n var self = this;\n var values = $.isArray( new_value ) ? new_value : [ new_value ];\n _.each( values, function( v ) {\n var list = self.header_index[ v ];\n _.each( list, function( element ) {\n self._setState( element, true );\n });\n });\n }\n },\n\n /** Expand/collapse a sub group */\n _setState: function ( header_id, is_expanded ) {\n var $button = this.$( '.button-' + header_id );\n var $subgroup = this.$( '.subgroup-' + header_id );\n $button.data( 'is_expanded', is_expanded );\n if ( is_expanded ) {\n $subgroup.show();\n $button.removeClass( 'fa-plus-square' ).addClass( 'fa-minus-square' );\n } else {\n $subgroup.hide();\n $button.removeClass( 'fa-minus-square' ).addClass( 'fa-plus-square' );\n }\n },\n\n /** Template to create options tree */\n _templateOptions: function() {\n var self = this;\n this.header_index = {};\n\n // attach event handler\n function attach( $el, header_id ) {\n var $button = $el.find( '.button-' + header_id );\n $button.on( 'click', function() {\n self._setState( header_id, !$button.data( 'is_expanded' ) );\n });\n }\n\n // recursive function which iterates through options\n function iterate ( $tmpl, options, header ) {\n header = header || [];\n for ( i in options ) {\n var level = options[ i ];\n var has_options = level.options && level.options.length > 0;\n var new_header = header.slice( 0 );\n self.header_index[ level.value ] = new_header.slice( 0 );\n var $group = $( '
                                  ' );\n if ( has_options ) {\n var header_id = Utils.uid();\n var $button = $( '' ).addClass( 'button-' + header_id ).addClass( 'ui-drilldown-button fa fa-plus-square' );\n var $subgroup = $( '
                                  ' ).addClass( 'subgroup-' + header_id ).addClass( 'ui-drilldown-subgroup' );\n $group.append( $( '
                                  ' )\n .append( $button )\n .append( self._templateOption( { label: level.name, value: level.value } ) ) );\n new_header.push( header_id );\n iterate ( $subgroup, level.options, new_header );\n $group.append( $subgroup );\n attach( $group, header_id );\n } else {\n $group.append( self._templateOption( { label: level.name, value: level.value } ) );\n }\n $tmpl.append( $group );\n }\n }\n\n // iterate through options and create dom\n var $tmpl = $( '
                                  ' );\n iterate( $tmpl, this.model.get( 'data' ) );\n return $tmpl;\n },\n\n /** Template for drill down view */\n _template: function() {\n return $( '
                                  ' ).addClass( 'ui-options-list drilldown-container' ).attr( 'id', this.model.id );\n }\n});\n\nreturn {\n View: View\n}\n\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/ui/ui-drilldown.js\n ** module id = 47\n ** module chunks = 0 3\n **/","define([ 'utils/utils', 'mvc/ui/ui-misc', 'mvc/ui/ui-select-default' ], function( Utils, Ui, Select ) {\n\n/** Batch mode variations */\nvar Batch = { DISABLED: 'disabled', ENABLED: 'enabled', LINKED: 'linked' };\n\n/** List of available content selectors options */\nvar Configurations = {\n data: [\n { src: 'hda', icon: 'fa-file-o', tooltip: 'Single dataset', multiple: false, batch: Batch.DISABLED },\n { src: 'hda', icon: 'fa-files-o', tooltip: 'Multiple datasets', multiple: true, batch: Batch.LINKED },\n { src: 'hdca', icon: 'fa-folder-o', tooltip: 'Dataset collection', multiple: false, batch: Batch.LINKED } ],\n data_multiple: [\n { src: 'hda', icon: 'fa-files-o', tooltip: 'Multiple datasets', multiple: true, batch: Batch.DISABLED },\n { src: 'hdca', icon: 'fa-folder-o', tooltip: 'Dataset collection', multiple: false, batch: Batch.DISABLED } ],\n data_collection: [\n { src: 'hdca', icon: 'fa-folder-o', tooltip: 'Dataset collection', multiple: false, batch: Batch.DISABLED } ],\n workflow_data: [\n { src: 'hda', icon: 'fa-file-o', tooltip: 'Single dataset', multiple: false, batch: Batch.DISABLED } ],\n workflow_data_multiple: [\n { src: 'hda', icon: 'fa-files-o', tooltip: 'Multiple datasets', multiple: true, batch: Batch.DISABLED } ],\n workflow_data_collection: [\n { src: 'hdca', icon: 'fa-folder-o', tooltip: 'Dataset collection', multiple: false, batch: Batch.DISABLED } ],\n module_data: [\n { src: 'hda', icon: 'fa-file-o', tooltip: 'Single dataset', multiple: false, batch: Batch.DISABLED },\n { src: 'hda', icon: 'fa-files-o', tooltip: 'Multiple datasets', multiple: true, batch: Batch.ENABLED } ],\n module_data_collection: [\n { src: 'hdca', icon: 'fa-folder-o', tooltip: 'Dataset collection', multiple: false, batch: Batch.DISABLED },\n { src: 'hdca', icon: 'fa-folder', tooltip: 'Multiple collections', multiple: true, batch: Batch.ENABLED } ]\n};\n\n/** View for hda and hdca content selector ui elements */\nvar View = Backbone.View.extend({\n initialize : function( options ) {\n var self = this;\n this.model = options && options.model || new Backbone.Model({\n src_labels : { 'hda' : 'dataset', 'hdca': 'dataset collection' },\n pagelimit : 100\n }).set( options );\n this.setElement( $( '
                                  ' ).addClass( 'ui-select-content' ) );\n this.button_product = new Ui.RadioButton.View( {\n value : 'false',\n data : [ { icon: 'fa fa-chain', value: 'false',\n tooltip: 'Linked inputs will be run in matched order with other datasets e.g. use this for matching forward and reverse reads.' },\n { icon: 'fa fa-chain-broken', value: 'true',\n tooltip: 'Unlinked dataset inputs will be run against *all* other inputs.' } ] } );\n var $batch_div = $( '
                                  ' ).addClass( 'ui-form-info' )\n .append( $( '' ).addClass( 'fa fa-sitemap' ) )\n .append( $( '' ).html( 'This is a batch mode input field. Separate jobs will be triggered for each dataset selection.' ) );\n this.$batch = {\n linked : $batch_div.clone(),\n enabled : $batch_div.clone().append( $( '
                                  ' )\n .append( $( '
                                  ' ).addClass( 'ui-form-title' ).html( 'Batch options:' ) )\n .append( this.button_product.$el ) )\n .append( $( '
                                  ' ).css( 'clear', 'both' ) )\n };\n\n // track current history elements\n this.history = {};\n\n // add listeners\n this.listenTo( this.model, 'change:data', this._changeData, this );\n this.listenTo( this.model, 'change:wait', this._changeWait, this );\n this.listenTo( this.model, 'change:current', this._changeCurrent, this );\n this.listenTo( this.model, 'change:value', this._changeValue, this );\n this.listenTo( this.model, 'change:type change:optional change:multiple change:extensions', this._changeType, this );\n this.render();\n\n // add change event\n this.on( 'change', function() { options.onchange && options.onchange( self.value() ) } );\n },\n\n render: function() {\n this._changeType();\n this._changeValue();\n this._changeWait();\n },\n\n /** Indicate that select fields are being updated */\n wait: function() {\n this.model.set( 'wait', true );\n },\n\n /** Indicate that the options update has been completed */\n unwait: function() {\n this.model.set( 'wait', false );\n },\n\n /** Update data representing selectable options */\n update: function( options ) {\n this.model.set( 'data', options );\n },\n\n /** Return the currently selected dataset values */\n value: function ( new_value ) {\n new_value !== undefined && this.model.set( 'value', new_value );\n var current = this.model.get( 'current' );\n if ( this.config[ current ] ) {\n var id_list = this.fields[ current ].value();\n if (id_list !== null) {\n id_list = $.isArray( id_list ) ? id_list : [ id_list ];\n if ( id_list.length > 0 ) {\n var result = this._batch( { values: [] } );\n for ( var i in id_list ) {\n var details = this.history[ id_list[ i ] + '_' + this.config[ current ].src ];\n if ( details ) {\n result.values.push( details );\n } else {\n Galaxy.emit.debug( 'ui-select-content::value()', 'Requested details not found for \\'' + id_list[ i ] + '\\'.' );\n return null;\n }\n }\n result.values.sort( function( a, b ) { return a.hid - b.hid } );\n return result;\n }\n }\n } else {\n Galaxy.emit.debug( 'ui-select-content::value()', 'Invalid value/source \\'' + new_value + '\\'.' );\n }\n return null;\n },\n\n /** Change of current select field */\n _changeCurrent: function() {\n var self = this;\n _.each( this.fields, function( field, i ) {\n if ( self.model.get( 'current' ) == i ) {\n field.$el.show();\n _.each( self.$batch, function( $batchfield, batchmode ) {\n $batchfield[ self.config[ i ].batch == batchmode ? 'show' : 'hide' ]();\n });\n self.button_type.value( i );\n } else {\n field.$el.hide();\n }\n });\n },\n\n /** Change of type */\n _changeType: function() {\n var self = this;\n\n // identify selector type identifier i.e. [ flavor ]_[ type ]_[ multiple ]\n var config_id = ( this.model.get( 'flavor' ) ? this.model.get( 'flavor' ) + '_' : '' ) +\n String( this.model.get( 'type' ) ) + ( this.model.get( 'multiple' ) ? '_multiple' : '' );\n if ( Configurations[ config_id ] ) {\n this.config = Configurations[ config_id ];\n } else {\n this.config = Configurations[ 'data' ];\n Galaxy.emit.debug( 'ui-select-content::_changeType()', 'Invalid configuration/type id \\'' + config_id + '\\'.' );\n }\n\n // prepare extension component of error message\n var data = self.model.get( 'data' );\n var extensions = Utils.textify( this.model.get( 'extensions' ) );\n var src_labels = this.model.get( 'src_labels' );\n\n // build views\n this.fields = [];\n this.button_data = [];\n _.each( this.config, function( c, i ) {\n self.button_data.push({\n value : i,\n icon : c.icon,\n tooltip : c.tooltip\n });\n self.fields.push(\n new Select.View({\n optional : self.model.get( 'optional' ),\n multiple : c.multiple,\n searchable : !c.multiple || ( data && data[ c.src ] && data[ c.src ].length > self.model.get( 'pagelimit' ) ),\n selectall : false,\n error_text : 'No ' + ( extensions ? extensions + ' ' : '' ) + ( src_labels[ c.src ] || 'content' ) + ' available.',\n onchange : function() {\n self.trigger( 'change' );\n }\n })\n );\n });\n this.button_type = new Ui.RadioButton.View({\n value : this.model.get( 'current' ),\n data : this.button_data,\n onchange: function( value ) {\n self.model.set( 'current', value );\n self.trigger( 'change' );\n }\n });\n\n // append views\n this.$el.empty();\n var button_width = 0;\n if ( this.fields.length > 1 ) {\n this.$el.append( this.button_type.$el );\n button_width = Math.max( 0, this.fields.length * 36 ) + 'px';\n }\n _.each( this.fields, function( field ) {\n self.$el.append( field.$el.css( { 'margin-left': button_width } ) );\n });\n _.each( this.$batch, function( $batchfield, batchmode ) {\n self.$el.append( $batchfield.css( { 'margin-left': button_width } ) );\n });\n this.model.set( 'current', 0 );\n this._changeCurrent();\n this._changeData();\n },\n\n /** Change of wait flag */\n _changeWait: function() {\n var self = this;\n _.each( this.fields, function( field ) { field[ self.model.get( 'wait' ) ? 'wait' : 'unwait' ]() } );\n },\n\n /** Change of available options */\n _changeData: function() {\n var options = this.model.get( 'data' );\n var self = this;\n var select_options = {};\n _.each( options, function( items, src ) {\n select_options[ src ] = [];\n _.each( items, function( item ) {\n select_options[ src ].push({\n hid : item.hid,\n keep : item.keep,\n label: item.hid + ': ' + item.name,\n value: item.id\n });\n self.history[ item.id + '_' + src ] = item;\n });\n });\n _.each( this.config, function( c, i ) {\n select_options[ c.src ] && self.fields[ i ].add( select_options[ c.src ], function( a, b ) { return b.hid - a.hid } );\n });\n },\n\n /** Change of incoming value */\n _changeValue: function () {\n var new_value = this.model.get( 'value' );\n if ( new_value && new_value.values && new_value.values.length > 0 ) {\n // create list with content ids\n var list = [];\n _.each( new_value.values, function( value ) {\n list.push( value.id );\n });\n // sniff first suitable field type from config list\n var src = new_value.values[ 0 ].src;\n var multiple = new_value.values.length > 1;\n for( var i = 0; i < this.config.length; i++ ) {\n var field = this.fields[ i ];\n var c = this.config[ i ];\n if ( c.src == src && [ multiple, true ].indexOf( c.multiple ) !== -1 ) {\n this.model.set( 'current', i );\n field.value( list );\n break;\n }\n }\n } else {\n _.each( this.fields, function( field ) {\n field.value( null );\n });\n }\n },\n\n /** Assists in identifying the batch mode */\n _batch: function( result ) {\n result[ 'batch' ] = false;\n var current = this.model.get( 'current' );\n var config = this.config[ current ];\n if ( config.src == 'hdca' && !config.multiple ) {\n var hdca = this.history[ this.fields[ current ].value() + '_hdca' ];\n if ( hdca && hdca.map_over_type ) {\n result[ 'batch' ] = true;\n }\n }\n if ( config.batch == Batch.LINKED || config.batch == Batch.ENABLED ) {\n result[ 'batch' ] = true;\n if ( config.batch == Batch.ENABLED && this.button_product.value() === 'true' ) {\n result[ 'product' ] = true;\n }\n }\n return result;\n }\n});\n\nreturn {\n View: View\n}\n\n});\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/ui/ui-select-content.js\n ** module id = 48\n ** module chunks = 0 3\n **/","// dependencies\ndefine(['utils/utils', 'mvc/ui/ui-list'],\n function(Utils, List) {\n\n/**\n * FTP file selector\n */\nvar View = Backbone.View.extend({\n // initialize\n initialize : function(options) {\n // link this\n var self = this;\n\n // create ui-list view to keep track of selected ftp files\n this.ftpfile_list = new List.View({\n name : 'file',\n optional : options.optional,\n multiple : options.multiple,\n onchange : function() {\n options.onchange && options.onchange(self.value());\n }\n });\n\n // create elements\n this.setElement(this.ftpfile_list.$el);\n\n // initial fetch of ftps\n Utils.get({\n url : Galaxy.root + 'api/remote_files',\n success : function(response) {\n var data = [];\n for (var i in response) {\n data.push({\n value : response[i]['path'],\n label : response[i]['path']\n });\n }\n self.ftpfile_list.update(data);\n }\n });\n },\n\n /** Return/Set currently selected ftp datasets */\n value: function(val) {\n return this.ftpfile_list.value(val);\n }\n});\n\nreturn {\n View: View\n}\n\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/ui/ui-select-ftp.js\n ** module id = 49\n ** module chunks = 0 3\n **/","// dependencies\ndefine(['utils/utils', 'mvc/ui/ui-misc', 'mvc/ui/ui-table', 'mvc/ui/ui-list'],\n function(Utils, Ui, Table, List) {\n\n// collection of libraries\nvar Libraries = Backbone.Collection.extend({\n url: Galaxy.root + 'api/libraries?deleted=false'\n});\n\n// collection of dataset\nvar LibraryDatasets = Backbone.Collection.extend({\n initialize: function() {\n var self = this;\n this.config = new Backbone.Model({ library_id: null });\n this.config.on('change', function() {\n self.fetch({ reset: true });\n });\n },\n url: function() {\n return Galaxy.root + 'api/libraries/' + this.config.get('library_id') + '/contents';\n }\n});\n\n// hda/hdca content selector ui element\nvar View = Backbone.View.extend({\n // initialize\n initialize : function(options) {\n // link this\n var self = this;\n\n // collections\n this.libraries = new Libraries();\n this.datasets = new LibraryDatasets();\n\n // link app and options\n this.options = options;\n\n // select field for the library\n // TODO: Remove this once the library API supports searching for library datasets\n this.library_select = new Ui.Select.View({\n onchange : function(value) {\n self.datasets.config.set('library_id', value);\n }\n });\n\n // create ui-list view to keep track of selected data libraries\n this.dataset_list = new List.View({\n name : 'dataset',\n optional : options.optional,\n multiple : options.multiple,\n onchange : function() {\n self.trigger('change');\n }\n });\n\n // add reset handler for fetched libraries\n this.libraries.on('reset', function() {\n var data = [];\n self.libraries.each(function(model) {\n data.push({\n value : model.id,\n label : model.get('name')\n });\n });\n self.library_select.update(data);\n });\n\n // add reset handler for fetched library datasets\n this.datasets.on('reset', function() {\n var data = [];\n var library_current = self.library_select.text();\n if (library_current !== null) {\n self.datasets.each(function(model) {\n if (model.get('type') === 'file') {\n data.push({\n value : model.id,\n label : model.get('name')\n });\n }\n });\n }\n self.dataset_list.update(data);\n });\n\n // add change event. fires on trigger\n this.on('change', function() {\n options.onchange && options.onchange(self.value());\n });\n\n // create elements\n this.setElement(this._template());\n this.$('.library-select').append(this.library_select.$el);\n this.$el.append(this.dataset_list.$el);\n\n // initial fetch of libraries\n this.libraries.fetch({\n reset: true,\n success: function() {\n self.library_select.trigger('change');\n if (self.options.value !== undefined) {\n self.value(self.options.value);\n }\n }\n });\n },\n\n /** Return/Set currently selected library datasets */\n value: function(val) {\n return this.dataset_list.value(val);\n },\n\n /** Template */\n _template: function() {\n return '
                                  ' +\n '
                                  ' +\n 'Select Library' +\n '' +\n '
                                  ' +\n '
                                  ';\n }\n});\n\nreturn {\n View: View\n}\n\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/ui/ui-select-library.js\n ** module id = 50\n ** module chunks = 0 3\n **/","define([ 'utils/utils' ], function( Utils ) {\nvar View = Backbone.View.extend({\n initialize : function( options ) {\n var self = this;\n this.options = Utils.merge( options, {\n id : Utils.uid(),\n min : null,\n max : null,\n step : null,\n precise : false,\n split : 10000\n } );\n\n // create new element\n this.setElement( this._template( this.options ) );\n\n // determine wether to use the slider\n this.useslider = this.options.max !== null && this.options.min !== null && this.options.max > this.options.min;\n\n // set default step size\n if ( this.options.step === null ) {\n this.options.step = 1.0;\n if ( this.options.precise && this.useslider ) {\n this.options.step = ( this.options.max - this.options.min ) / this.options.split;\n }\n }\n\n // create slider if min and max are defined properly\n if ( this.useslider ) {\n this.$slider = this.$( '#slider' );\n this.$slider.slider( this.options );\n this.$slider.on( 'slide', function ( event, ui ) {\n self.value( ui.value );\n });\n } else {\n this.$( '.ui-form-slider-text' ).css( 'width', '100%' );\n }\n\n // link text input field\n this.$text = this.$( '#text' );\n\n // set initial value\n this.options.value !== undefined && ( this.value( this.options.value ) );\n\n // add text field event\n var pressed = [];\n this.$text.on( 'change', function () {\n self.value( $( this ).val() );\n });\n this.$text.on( 'keyup', function( e ) {\n pressed[e.which] = false;\n self.options.onchange && self.options.onchange( $( this ).val() );\n });\n this.$text.on( 'keydown', function ( e ) {\n var v = e.which;\n pressed[ v ] = true;\n if ( self.options.is_workflow && pressed[ 16 ] && v == 52 ) {\n self.value( '$' )\n event.preventDefault();\n } else if (!( v == 8 || v == 9 || v == 13 || v == 37 || v == 39 || ( v >= 48 && v <= 57 && !pressed[ 16 ] ) || ( v >= 96 && v <= 105 )\n || ( ( v == 190 || v == 110 ) && $( this ).val().indexOf( '.' ) == -1 && self.options.precise )\n || ( ( v == 189 || v == 109 ) && $( this ).val().indexOf( '-' ) == -1 )\n || self._isParameter( $( this ).val() )\n || pressed[ 91 ] || pressed[ 17 ] ) ) {\n event.preventDefault();\n }\n });\n },\n\n /** Set and Return the current value\n */\n value : function ( new_val ) {\n if ( new_val !== undefined ) {\n if ( new_val !== null && new_val !== '' && !this._isParameter( new_val ) ) {\n isNaN( new_val ) && ( new_val = 0 );\n this.options.max !== null && ( new_val = Math.min( new_val, this.options.max ) );\n this.options.min !== null && ( new_val = Math.max( new_val, this.options.min ) );\n }\n this.$slider && this.$slider.slider( 'value', new_val );\n this.$text.val( new_val );\n this.options.onchange && this.options.onchange( new_val );\n }\n return this.$text.val();\n },\n\n /** Return true if the field contains a workflow parameter i.e. $('name')\n */\n _isParameter: function( value ) {\n return this.options.is_workflow && String( value ).substring( 0, 1 ) === '$';\n },\n\n /** Slider template\n */\n _template: function( options ) {\n return '
                                  ' +\n '' +\n '
                                  ' +\n '
                                  ';\n }\n});\n\nreturn {\n View : View\n};\n});\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/ui/ui-slider.js\n ** module id = 51\n ** module chunks = 0 3\n **/","// dependencies\ndefine(['utils/utils'], function(Utils) {\n\n/**\n * This class creates a ui table element.\n */\nvar View = Backbone.View.extend({\n // current row\n row: null,\n \n // count rows\n row_count: 0,\n \n // defaults options\n optionsDefault: {\n content : 'No content available.',\n onchange : null,\n ondblclick : null,\n onconfirm : null,\n cls : 'ui-table',\n cls_tr : ''\n },\n \n // events\n events : {\n 'click' : '_onclick',\n 'dblclick' : '_ondblclick'\n },\n \n // initialize\n initialize : function(options) {\n // configure options\n this.options = Utils.merge(options, this.optionsDefault);\n \n // create new element\n var $el = $(this._template(this.options));\n \n // link sub-elements\n this.$thead = $el.find('thead');\n this.$tbody = $el.find('tbody');\n this.$tmessage = $el.find('tmessage');\n \n // set element\n this.setElement($el);\n \n // initialize row\n this.row = this._row();\n },\n \n // add header cell\n addHeader: function($el) {\n var wrapper = $('
                                  ' +\n '' +\n '' +\n '
                                  ' +\n '' + options.content + '' +\n '
                                  ';\n }\n});\n\nreturn {\n View: View\n}\n\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/ui/ui-table.js\n ** module id = 52\n ** module chunks = 0 3\n **/","define( [], function() {\n var Model = Backbone.Model.extend({\n defaults: {\n extension : 'auto',\n genome : '?',\n url_paste : '',\n status : 'init',\n info : null,\n file_name : '',\n file_mode : '',\n file_size : 0,\n file_type : null,\n file_path : '',\n file_data : null,\n percentage : 0,\n space_to_tab : false,\n to_posix_lines : true,\n enabled : true\n },\n reset: function( attr ) {\n this.clear().set( this.defaults ).set( attr );\n }\n });\n var Collection = Backbone.Collection.extend( { model: Model } );\n return { Model: Model, Collection : Collection };\n});\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/upload/upload-model.js\n ** module id = 53\n ** module chunks = 3\n **/","/**\n * This class defines a queue to ensure that multiple deferred callbacks are executed sequentially.\n */\ndefine(['utils/utils'], function( Utils ) {\nreturn Backbone.Model.extend({\n initialize: function(){\n this.active = {};\n this.last = null;\n },\n\n /** Adds a callback to the queue. Upon execution a deferred object is parsed to the callback i.e. callback( deferred ).\n * If the callback does not take any arguments, the deferred is resolved instantly.\n */\n execute: function( callback ) {\n var self = this;\n var id = Utils.uid();\n var has_deferred = callback.length > 0;\n\n // register process\n this.active[ id ] = true;\n\n // deferred process\n var process = $.Deferred();\n process.promise().always(function() {\n delete self.active[ id ];\n has_deferred && Galaxy.emit.debug( 'deferred::execute()', this.state().charAt(0).toUpperCase() + this.state().slice(1) + ' ' + id );\n });\n\n // deferred queue\n $.when( this.last ).always(function() {\n if ( self.active[ id ] ) {\n has_deferred && Galaxy.emit.debug( 'deferred::execute()', 'Running ' + id );\n callback( process );\n !has_deferred && process.resolve();\n } else {\n process.reject();\n }\n });\n this.last = process.promise();\n },\n\n /** Resets the promise queue. All currently queued but unexecuted callbacks/promises will be rejected.\n */\n reset: function() {\n Galaxy.emit.debug('deferred::execute()', 'Reset');\n for ( var i in this.active ) {\n this.active[ i ] = false;\n }\n },\n\n /** Returns true if all processes are done.\n */\n ready: function() {\n return $.isEmptyObject( this.active );\n }\n});\n\n});\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/utils/deferred.js\n ** module id = 55\n ** module chunks = 0 3\n **/","define([\n \"mvc/base-mvc\",\n \"utils/localization\",\n \"ui/editable-text\",\n], function( baseMVC, _l ){\n// =============================================================================\n/** A view on any model that has a 'annotation' attribute\n */\nvar AnnotationEditor = Backbone.View\n .extend( baseMVC.LoggableMixin )\n .extend( baseMVC.HiddenUntilActivatedViewMixin ).extend({\n\n tagName : 'div',\n className : 'annotation-display',\n\n /** Set up listeners, parse options */\n initialize : function( options ){\n options = options || {};\n this.tooltipConfig = options.tooltipConfig || { placement: 'bottom' };\n //console.debug( this, options );\n // only listen to the model only for changes to annotations\n this.listenTo( this.model, 'change:annotation', function(){\n this.render();\n });\n this.hiddenUntilActivated( options.$activator, options );\n },\n\n /** Build the DOM elements, call select to on the created input, and set up behaviors */\n render : function(){\n var view = this;\n this.$el.html( this._template() );\n\n //TODO: handle empties better\n this.$annotation().make_text_editable({\n use_textarea: true,\n on_finish: function( newAnnotation ){\n view.$annotation().text( newAnnotation );\n view.model.save({ annotation: newAnnotation }, { silent: true })\n .fail( function(){\n view.$annotation().text( view.model.previous( 'annotation' ) );\n });\n }\n });\n return this;\n },\n\n /** @returns {String} the html text used to build the view's DOM */\n _template : function(){\n var annotation = this.model.get( 'annotation' );\n return [\n //TODO: make prompt optional\n '',\n // set up initial tags by adding as CSV to input vals (necc. to init select2)\n '
                                  ',\n _.escape( annotation ),\n '
                                  '\n ].join( '' );\n },\n\n /** @returns {jQuery} the main element for this view */\n $annotation : function(){\n return this.$el.find( '.annotation' );\n },\n\n /** shut down event listeners and remove this view's DOM */\n remove : function(){\n this.$annotation.off();\n this.stopListening( this.model );\n Backbone.View.prototype.remove.call( this );\n },\n\n /** string rep */\n toString : function(){ return [ 'AnnotationEditor(', this.model + '', ')' ].join(''); }\n});\n// =============================================================================\nreturn {\n AnnotationEditor : AnnotationEditor\n};\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/annotation.js\n ** module id = 66\n ** module chunks = 3\n **/","define([\n 'libs/underscore',\n 'libs/backbone',\n 'mvc/base-mvc',\n], function( _, Backbone, BASE_MVC ){\n'use strict';\n\n//=============================================================================\n/**\n * A Collection that can be limited/offset/re-ordered/filtered.\n * @type {Backbone.Collection}\n */\nvar ControlledFetchCollection = Backbone.Collection.extend({\n\n /** call setOrder on initialization to build the comparator based on options */\n initialize : function( models, options ){\n Backbone.Collection.prototype.initialize.call( this, models, options );\n this.setOrder( options.order || this.order, { silent: true });\n },\n\n /** set up to track order changes and re-sort when changed */\n _setUpListeners : function(){\n return this.on({\n 'changed-order' : this.sort\n });\n },\n\n /** override to provide order and offsets based on instance vars, set limit if passed,\n * and set allFetched/fire 'all-fetched' when xhr returns\n */\n fetch : function( options ){\n options = this._buildFetchOptions( options );\n // console.log( 'fetch options:', options );\n return Backbone.Collection.prototype.fetch.call( this, options );\n },\n\n /** build ajax data/parameters from options */\n _buildFetchOptions : function( options ){\n // note: we normally want options passed in to override the defaults built here\n // so most of these fns will generate defaults\n options = _.clone( options ) || {};\n var self = this;\n\n // jquery ajax option; allows multiple q/qv for filters (instead of 'q[]')\n options.traditional = true;\n\n // options.data\n // we keep limit, offset, etc. in options *as well as move it into data* because:\n // - it makes fetch calling convenient to add it to a single options map (instead of as mult. args)\n // - it allows the std. event handlers (for fetch, etc.) to have access\n // to the pagination options too\n // (i.e. this.on( 'sync', function( options ){ if( options.limit ){ ... } }))\n // however, when we send to xhr/jquery we copy them to data also so that they become API query params\n options.data = options.data || self._buildFetchData( options );\n // console.log( 'data:', options.data );\n\n // options.data.filters --> options.data.q, options.data.qv\n var filters = this._buildFetchFilters( options );\n // console.log( 'filters:', filters );\n if( !_.isEmpty( filters ) ){\n _.extend( options.data, this._fetchFiltersToAjaxData( filters ) );\n }\n // console.log( 'data:', options.data );\n return options;\n },\n\n /** Build the dictionary to send to fetch's XHR as data */\n _buildFetchData : function( options ){\n var defaults = {};\n if( this.order ){ defaults.order = this.order; }\n return _.defaults( _.pick( options, this._fetchParams ), defaults );\n },\n\n /** These attribute keys are valid params to fetch/API-index */\n _fetchParams : [\n /** model dependent string to control the order of models returned */\n 'order',\n /** limit the number of models returned from a fetch */\n 'limit',\n /** skip this number of models when fetching */\n 'offset',\n /** what series of attributes to return (model dependent) */\n 'view',\n /** individual keys to return for the models (see api/histories.index) */\n 'keys'\n ],\n\n /** add any needed filters here based on collection state */\n _buildFetchFilters : function( options ){\n // override\n return _.clone( options.filters || {} );\n },\n\n /** Convert dictionary filters to qqv style arrays */\n _fetchFiltersToAjaxData : function( filters ){\n // return as a map so ajax.data can extend from it\n var filterMap = {\n q : [],\n qv : []\n };\n _.each( filters, function( v, k ){\n // don't send if filter value is empty\n if( v === undefined || v === '' ){ return; }\n // json to python\n if( v === true ){ v = 'True'; }\n if( v === false ){ v = 'False'; }\n if( v === null ){ v = 'None'; }\n // map to k/v arrays (q/qv)\n filterMap.q.push( k );\n filterMap.qv.push( v );\n });\n return filterMap;\n },\n\n /** override to reset allFetched flag to false */\n reset : function( models, options ){\n this.allFetched = false;\n return Backbone.Collection.prototype.reset.call( this, models, options );\n },\n\n // ........................................................................ order\n order : null,\n\n /** @type {Object} map of collection available sorting orders containing comparator fns */\n comparators : {\n 'update_time' : BASE_MVC.buildComparator( 'update_time', { ascending: false }),\n 'update_time-asc' : BASE_MVC.buildComparator( 'update_time', { ascending: true }),\n 'create_time' : BASE_MVC.buildComparator( 'create_time', { ascending: false }),\n 'create_time-asc' : BASE_MVC.buildComparator( 'create_time', { ascending: true }),\n },\n\n /** set the order and comparator for this collection then sort with the new order\n * @event 'changed-order' passed the new order and the collection\n */\n setOrder : function( order, options ){\n options = options || {};\n var collection = this;\n var comparator = collection.comparators[ order ];\n if( _.isUndefined( comparator ) ){ throw new Error( 'unknown order: ' + order ); }\n // if( _.isUndefined( comparator ) ){ return; }\n if( comparator === collection.comparator ){ return; }\n\n var oldOrder = collection.order;\n collection.order = order;\n collection.comparator = comparator;\n\n if( !options.silent ){\n collection.trigger( 'changed-order', options );\n }\n return collection;\n },\n\n});\n\n\n//=============================================================================\n/**\n *\n */\nvar PaginatedCollection = ControlledFetchCollection.extend({\n\n /** @type {Number} limit used for each page's fetch */\n limitPerPage : 500,\n\n initialize : function( models, options ){\n ControlledFetchCollection.prototype.initialize.call( this, models, options );\n this.currentPage = options.currentPage || 0;\n },\n\n getTotalItemCount : function(){\n return this.length;\n },\n\n shouldPaginate : function(){\n return this.getTotalItemCount() >= this.limitPerPage;\n },\n\n getLastPage : function(){\n return Math.floor( this.getTotalItemCount() / this.limitPerPage );\n },\n\n getPageCount : function(){\n return this.getLastPage() + 1;\n },\n\n getPageLimitOffset : function( pageNum ){\n pageNum = this.constrainPageNum( pageNum );\n return {\n limit : this.limitPerPage,\n offset: pageNum * this.limitPerPage\n };\n },\n\n constrainPageNum : function( pageNum ){\n return Math.max( 0, Math.min( pageNum, this.getLastPage() ));\n },\n\n /** fetch the next page of data */\n fetchPage : function( pageNum, options ){\n var self = this;\n pageNum = self.constrainPageNum( pageNum );\n self.currentPage = pageNum;\n options = _.defaults( options || {}, self.getPageLimitOffset( pageNum ) );\n\n self.trigger( 'fetching-more' );\n return self.fetch( options )\n .always( function(){\n self.trigger( 'fetching-more-done' );\n });\n },\n\n fetchCurrentPage : function( options ){\n return this.fetchPage( this.currentPage, options );\n },\n\n fetchPrevPage : function( options ){\n return this.fetchPage( this.currentPage - 1, options );\n },\n\n fetchNextPage : function( options ){\n return this.fetchPage( this.currentPage + 1, options );\n },\n});\n\n\n//=============================================================================\n/**\n * A Collection that will load more elements without reseting.\n */\nvar InfinitelyScrollingCollection = ControlledFetchCollection.extend({\n\n /** @type {Number} limit used for the first fetch (or a reset) */\n limitOnFirstFetch : null,\n /** @type {Number} limit used for each subsequent fetch */\n limitPerFetch : 100,\n\n initialize : function( models, options ){\n ControlledFetchCollection.prototype.initialize.call( this, models, options );\n /** @type {Integer} number of contents to return from the first fetch */\n this.limitOnFirstFetch = options.limitOnFirstFetch || this.limitOnFirstFetch;\n /** @type {Integer} limit for every fetch after the first */\n this.limitPerFetch = options.limitPerFetch || this.limitPerFetch;\n /** @type {Boolean} are all contents fetched? */\n this.allFetched = false;\n /** @type {Integer} what was the offset of the last content returned */\n this.lastFetched = options.lastFetched || 0;\n },\n\n /** build ajax data/parameters from options */\n _buildFetchOptions : function( options ){\n // options (options for backbone.fetch and jquery.ajax generally)\n // backbone option; false here to make fetching an addititive process\n options.remove = options.remove || false;\n return ControlledFetchCollection.prototype._buildFetchOptions.call( this, options );\n },\n\n /** fetch the first 'page' of data */\n fetchFirst : function( options ){\n // console.log( 'ControlledFetchCollection.fetchFirst:', options );\n options = options? _.clone( options ) : {};\n this.allFetched = false;\n this.lastFetched = 0;\n return this.fetchMore( _.defaults( options, {\n reset : true,\n limit : this.limitOnFirstFetch,\n }));\n },\n\n /** fetch the next page of data */\n fetchMore : function( options ){\n // console.log( 'ControlledFetchCollection.fetchMore:', options );\n options = _.clone( options || {} );\n var collection = this;\n\n // console.log( 'fetchMore, options.reset:', options.reset );\n if( ( !options.reset && collection.allFetched ) ){\n return jQuery.when();\n }\n\n // TODO: this fails in the edge case where\n // the first fetch offset === limit (limit 4, offset 4, collection.length 4)\n options.offset = options.reset? 0 : ( options.offset || collection.lastFetched );\n var limit = options.limit = options.limit || collection.limitPerFetch || null;\n // console.log( 'fetchMore, limit:', limit, 'offset:', options.offset );\n\n collection.trigger( 'fetching-more' );\n return collection.fetch( options )\n .always( function(){\n collection.trigger( 'fetching-more-done' );\n })\n // maintain allFetched flag and trigger if all were fetched this time\n .done( function _postFetchMore( fetchedData ){\n var numFetched = _.isArray( fetchedData )? fetchedData.length : 0;\n collection.lastFetched += numFetched;\n // console.log( 'fetchMore, lastFetched:', collection.lastFetched );\n // anything less than a full page means we got all there is to get\n if( !limit || numFetched < limit ){\n collection.allFetched = true;\n collection.trigger( 'all-fetched', this );\n }\n }\n );\n },\n\n /** fetch all the collection */\n fetchAll : function( options ){\n // whitelist options to prevent allowing limit/offset/filters\n // (use vanilla fetch instead)\n options = options || {};\n var self = this;\n options = _.pick( options, 'silent' );\n options.filters = {};\n return self.fetch( options ).done( function( fetchData ){\n self.allFetched = true;\n self.trigger( 'all-fetched', self );\n });\n },\n});\n\n\n//==============================================================================\n return {\n ControlledFetchCollection : ControlledFetchCollection,\n PaginatedCollection : PaginatedCollection,\n InfinitelyScrollingCollection : InfinitelyScrollingCollection,\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/base/controlled-fetch-collection.js\n ** module id = 67\n ** module chunks = 3\n **/","define([\n \"mvc/list/list-view\",\n \"mvc/collection/collection-model\",\n \"mvc/collection/collection-li\",\n \"mvc/base-mvc\",\n \"utils/localization\"\n], function( LIST_VIEW, DC_MODEL, DC_LI, BASE_MVC, _l ){\n\n'use strict';\n\nvar logNamespace = 'collections';\n/* =============================================================================\nTODO:\n\n============================================================================= */\n/** @class non-editable, read-only View/Controller for a dataset collection.\n */\nvar _super = LIST_VIEW.ModelListPanel;\nvar CollectionView = _super.extend(\n/** @lends CollectionView.prototype */{\n //MODEL is either a DatasetCollection (or subclass) or a DatasetCollectionElement (list of pairs)\n _logNamespace : logNamespace,\n\n className : _super.prototype.className + ' dataset-collection-panel',\n\n /** sub view class used for datasets */\n DatasetDCEViewClass : DC_LI.DatasetDCEListItemView,\n /** sub view class used for nested collections */\n NestedDCDCEViewClass: DC_LI.NestedDCDCEListItemView,\n /** key of attribute in model to assign to this.collection */\n modelCollectionKey : 'elements',\n\n // ......................................................................... SET UP\n /** Set up the view, set up storage, bind listeners to HistoryContents events\n * @param {Object} attributes optional settings for the panel\n */\n initialize : function( attributes ){\n _super.prototype.initialize.call( this, attributes );\n this.linkTarget = attributes.linkTarget || '_blank';\n\n this.hasUser = attributes.hasUser;\n /** A stack of panels that currently cover or hide this panel */\n this.panelStack = [];\n /** The text of the link to go back to the panel containing this one */\n this.parentName = attributes.parentName;\n /** foldout or drilldown */\n this.foldoutStyle = attributes.foldoutStyle || 'foldout';\n },\n\n _queueNewRender : function( $newRender, speed ) {\n speed = ( speed === undefined )?( this.fxSpeed ):( speed );\n var panel = this;\n panel.log( '_queueNewRender:', $newRender, speed );\n\n // TODO: jquery@1.12 doesn't change display when the elem has display: flex\n // this causes display: block for those elems after the use of show/hide animations\n // animations are removed from this view for now until fixed\n panel._swapNewRender( $newRender );\n panel.trigger( 'rendered', panel );\n },\n\n // ------------------------------------------------------------------------ sub-views\n /** In this override, use model.getVisibleContents */\n _filterCollection : function(){\n //TODO: should *not* be model.getVisibleContents - visibility is not model related\n return this.model.getVisibleContents();\n },\n\n /** override to return proper view class based on element_type */\n _getItemViewClass : function( model ){\n //this.debug( this + '._getItemViewClass:', model );\n //TODO: subclasses use DCEViewClass - but are currently unused - decide\n switch( model.get( 'element_type' ) ){\n case 'hda':\n return this.DatasetDCEViewClass;\n case 'dataset_collection':\n return this.NestedDCDCEViewClass;\n }\n throw new TypeError( 'Unknown element type:', model.get( 'element_type' ) );\n },\n\n /** override to add link target and anon */\n _getItemViewOptions : function( model ){\n var options = _super.prototype._getItemViewOptions.call( this, model );\n return _.extend( options, {\n linkTarget : this.linkTarget,\n hasUser : this.hasUser,\n //TODO: could move to only nested: list:paired\n foldoutStyle : this.foldoutStyle\n });\n },\n\n // ------------------------------------------------------------------------ collection sub-views\n /** In this override, add/remove expanded/collapsed model ids to/from web storage */\n _setUpItemViewListeners : function( view ){\n var panel = this;\n _super.prototype._setUpItemViewListeners.call( panel, view );\n\n // use pub-sub to: handle drilldown expansion and collapse\n panel.listenTo( view, {\n 'expanded:drilldown': function( v, drilldown ){\n this._expandDrilldownPanel( drilldown );\n },\n 'collapsed:drilldown': function( v, drilldown ){\n this._collapseDrilldownPanel( drilldown );\n }\n });\n return this;\n },\n\n /** Handle drill down by hiding this panels list and controls and showing the sub-panel */\n _expandDrilldownPanel : function( drilldown ){\n this.panelStack.push( drilldown );\n // hide this panel's controls and list, set the name for back navigation, and attach to the $el\n this.$( '> .controls' ).add( this.$list() ).hide();\n drilldown.parentName = this.model.get( 'name' );\n this.$el.append( drilldown.render().$el );\n },\n\n /** Handle drilldown close by freeing the panel and re-rendering this panel */\n _collapseDrilldownPanel : function( drilldown ){\n this.panelStack.pop();\n this.render();\n },\n\n // ------------------------------------------------------------------------ panel events\n /** event map */\n events : {\n 'click .navigation .back' : 'close'\n },\n\n /** close/remove this collection panel */\n close : function( event ){\n this.remove();\n this.trigger( 'close' );\n },\n\n // ........................................................................ misc\n /** string rep */\n toString : function(){\n return 'CollectionView(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';\n }\n});\n\n\n//------------------------------------------------------------------------------ TEMPLATES\nCollectionView.prototype.templates = (function(){\n\n var controlsTemplate = BASE_MVC.wrapTemplate([\n '
                                  ',\n '',\n\n '
                                  ',\n '
                                  <%- collection.name || collection.element_identifier %>
                                  ',\n '
                                  ',\n '<% if( collection.collection_type === \"list\" ){ %>',\n _l( 'a list of datasets' ),\n '<% } else if( collection.collection_type === \"paired\" ){ %>',\n _l( 'a pair of datasets' ),\n '<% } else if( collection.collection_type === \"list:paired\" ){ %>',\n _l( 'a list of paired datasets' ),\n '<% } else if( collection.collection_type === \"list:list\" ){ %>',\n _l( 'a list of dataset lists' ),\n '<% } %>',\n '
                                  ',\n '
                                  ',\n '
                                  '\n ], 'collection' );\n\n return _.extend( _.clone( _super.prototype.templates ), {\n controls : controlsTemplate\n });\n}());\n\n\n\n// =============================================================================\n/** @class non-editable, read-only View/Controller for a dataset collection. */\nvar ListCollectionView = CollectionView.extend(\n/** @lends ListCollectionView.prototype */{\n\n //TODO: not strictly needed - due to switch in CollectionView._getContentClass\n /** sub view class used for datasets */\n DatasetDCEViewClass : DC_LI.DatasetDCEListItemView,\n\n // ........................................................................ misc\n /** string rep */\n toString : function(){\n return 'ListCollectionView(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';\n }\n});\n\n\n// =============================================================================\n/** @class non-editable, read-only View/Controller for a dataset collection. */\nvar PairCollectionView = ListCollectionView.extend(\n/** @lends PairCollectionView.prototype */{\n\n // ........................................................................ misc\n /** string rep */\n toString : function(){\n return 'PairCollectionView(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';\n }\n});\n\n\n// =============================================================================\n/** @class non-editable, read-only View/Controller for a dataset collection. */\nvar ListOfPairsCollectionView = CollectionView.extend(\n/** @lends ListOfPairsCollectionView.prototype */{\n\n //TODO: not strictly needed - due to switch in CollectionView._getContentClass\n /** sub view class used for nested collections */\n NestedDCDCEViewClass : DC_LI.NestedDCDCEListItemView.extend({\n foldoutPanelClass : PairCollectionView\n }),\n\n // ........................................................................ misc\n /** string rep */\n toString : function(){\n return 'ListOfPairsCollectionView(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';\n }\n});\n\n\n// =============================================================================\n/** @class non-editable, read-only View/Controller for a list of lists dataset collection. */\nvar ListOfListsCollectionView = CollectionView.extend({\n\n /** sub view class used for nested collections */\n NestedDCDCEViewClass : DC_LI.NestedDCDCEListItemView.extend({\n foldoutPanelClass : PairCollectionView\n }),\n\n /** string rep */\n toString : function(){\n return 'ListOfListsCollectionView(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';\n }\n});\n\n\n//==============================================================================\n return {\n CollectionView : CollectionView,\n ListCollectionView : ListCollectionView,\n PairCollectionView : PairCollectionView,\n ListOfPairsCollectionView : ListOfPairsCollectionView,\n ListOfListsCollectionView : ListOfListsCollectionView\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/collection/collection-view.js\n ** module id = 68\n ** module chunks = 3\n **/","define([\n \"mvc/dataset/states\",\n \"mvc/dataset/dataset-li\",\n \"mvc/tag\",\n \"mvc/annotation\",\n \"ui/fa-icon-button\",\n \"mvc/base-mvc\",\n \"utils/localization\"\n], function( STATES, DATASET_LI, TAGS, ANNOTATIONS, faIconButton, BASE_MVC, _l ){\n\n'use strict';\n//==============================================================================\nvar _super = DATASET_LI.DatasetListItemView;\n/** @class Editing view for DatasetAssociation.\n */\nvar DatasetListItemEdit = _super.extend(\n/** @lends DatasetListItemEdit.prototype */{\n\n /** set up: options */\n initialize : function( attributes ){\n _super.prototype.initialize.call( this, attributes );\n this.hasUser = attributes.hasUser;\n\n /** allow user purge of dataset files? */\n this.purgeAllowed = attributes.purgeAllowed || false;\n\n //TODO: move to HiddenUntilActivatedViewMixin\n /** should the tags editor be shown or hidden initially? */\n this.tagsEditorShown = attributes.tagsEditorShown || false;\n /** should the tags editor be shown or hidden initially? */\n this.annotationEditorShown = attributes.annotationEditorShown || false;\n },\n\n // ......................................................................... titlebar actions\n /** In this override, add the other two primary actions: edit and delete */\n _renderPrimaryActions : function(){\n var actions = _super.prototype._renderPrimaryActions.call( this );\n if( this.model.get( 'state' ) === STATES.NOT_VIEWABLE ){\n return actions;\n }\n // render the display, edit attr and delete icon-buttons\n return _super.prototype._renderPrimaryActions.call( this ).concat([\n this._renderEditButton(),\n this._renderDeleteButton()\n ]);\n },\n\n //TODO: move titleButtons into state renderers, remove state checks in the buttons\n\n /** Render icon-button to edit the attributes (format, permissions, etc.) this dataset. */\n _renderEditButton : function(){\n // don't show edit while uploading, in-accessible\n // DO show if in error (ala previous history panel)\n if( ( this.model.get( 'state' ) === STATES.DISCARDED )\n || ( !this.model.get( 'accessible' ) ) ){\n return null;\n }\n\n var purged = this.model.get( 'purged' ),\n deleted = this.model.get( 'deleted' ),\n editBtnData = {\n title : _l( 'Edit attributes' ),\n href : this.model.urls.edit,\n target : this.linkTarget,\n faIcon : 'fa-pencil',\n classes : 'edit-btn'\n };\n\n // disable if purged or deleted and explain why in the tooltip\n if( deleted || purged ){\n editBtnData.disabled = true;\n if( purged ){\n editBtnData.title = _l( 'Cannot edit attributes of datasets removed from disk' );\n } else if( deleted ){\n editBtnData.title = _l( 'Undelete dataset to edit attributes' );\n }\n\n // disable if still uploading or new\n } else if( _.contains( [ STATES.UPLOAD, STATES.NEW ], this.model.get( 'state' ) ) ){\n editBtnData.disabled = true;\n editBtnData.title = _l( 'This dataset is not yet editable' );\n }\n return faIconButton( editBtnData );\n },\n\n /** Render icon-button to delete this hda. */\n _renderDeleteButton : function(){\n // don't show delete if...\n if( ( !this.model.get( 'accessible' ) ) ){\n return null;\n }\n\n var self = this,\n deletedAlready = this.model.isDeletedOrPurged();\n return faIconButton({\n title : !deletedAlready? _l( 'Delete' ) : _l( 'Dataset is already deleted' ),\n disabled : deletedAlready,\n faIcon : 'fa-times',\n classes : 'delete-btn',\n onclick : function() {\n // ...bler... tooltips being left behind in DOM (hover out never called on deletion)\n self.$el.find( '.icon-btn.delete-btn' ).trigger( 'mouseout' );\n self.model[ 'delete' ]();\n }\n });\n },\n\n // ......................................................................... details\n /** In this override, add tags and annotations controls, make the ? dbkey a link to editing page */\n _renderDetails : function(){\n //TODO: generalize to be allow different details for each state\n var $details = _super.prototype._renderDetails.call( this ),\n state = this.model.get( 'state' );\n\n if( !this.model.isDeletedOrPurged() && _.contains([ STATES.OK, STATES.FAILED_METADATA ], state ) ){\n this._renderTags( $details );\n this._renderAnnotation( $details );\n this._makeDbkeyEditLink( $details );\n }\n\n this._setUpBehaviors( $details );\n return $details;\n },\n\n /** Add less commonly used actions in the details section based on state */\n _renderSecondaryActions : function(){\n var actions = _super.prototype._renderSecondaryActions.call( this );\n switch( this.model.get( 'state' ) ){\n case STATES.UPLOAD:\n case STATES.NOT_VIEWABLE:\n return actions;\n case STATES.ERROR:\n // error button comes first\n actions.unshift( this._renderErrButton() );\n return actions.concat([ this._renderRerunButton() ]);\n case STATES.OK:\n case STATES.FAILED_METADATA:\n return actions.concat([ this._renderRerunButton(), this._renderVisualizationsButton() ]);\n }\n return actions.concat([ this._renderRerunButton() ]);\n },\n\n /** Render icon-button to report an error on this dataset to the galaxy admin. */\n _renderErrButton : function(){\n return faIconButton({\n title : _l( 'View or report this error' ),\n href : this.model.urls.report_error,\n classes : 'report-error-btn',\n target : this.linkTarget,\n faIcon : 'fa-bug'\n });\n },\n\n /** Render icon-button to re-run the job that created this dataset. */\n _renderRerunButton : function(){\n var creating_job = this.model.get( 'creating_job' );\n if( this.model.get( 'rerunnable' ) ){\n return faIconButton({\n title : _l( 'Run this job again' ),\n href : this.model.urls.rerun,\n classes : 'rerun-btn',\n target : this.linkTarget,\n faIcon : 'fa-refresh',\n onclick : function( ev ) {\n ev.preventDefault();\n // create webpack split point in order to load the tool form async\n // TODO: split not working (tool loads fine)\n require([ 'mvc/tool/tool-form' ], function( ToolForm ){\n var form = new ToolForm.View({ 'job_id' : creating_job });\n form.deferred.execute( function(){\n Galaxy.app.display( form );\n });\n });\n }\n });\n }\n },\n\n /** Render an icon-button or popupmenu of links based on the applicable visualizations */\n _renderVisualizationsButton : function(){\n //TODO: someday - lazyload visualizations\n var visualizations = this.model.get( 'visualizations' );\n if( ( this.model.isDeletedOrPurged() )\n || ( !this.hasUser )\n || ( !this.model.hasData() )\n || ( _.isEmpty( visualizations ) ) ){\n return null;\n }\n if( !_.isObject( visualizations[0] ) ){\n this.warn( 'Visualizations have been switched off' );\n return null;\n }\n\n var $visualizations = $( this.templates.visualizations( visualizations, this ) );\n //HACK: need to re-write those directed at galaxy_main with linkTarget\n $visualizations.find( '[target=\"galaxy_main\"]').attr( 'target', this.linkTarget );\n // use addBack here to include the root $visualizations elem (for the case of 1 visualization)\n this._addScratchBookFn( $visualizations.find( '.visualization-link' ).addBack( '.visualization-link' ) );\n return $visualizations;\n },\n\n /** add scratchbook functionality to visualization links */\n _addScratchBookFn : function( $links ){\n var li = this;\n $links.click( function( ev ){\n if( Galaxy.frame && Galaxy.frame.active ){\n Galaxy.frame.add({\n title : 'Visualization',\n url : $( this ).attr( 'href' )\n });\n ev.preventDefault();\n ev.stopPropagation();\n }\n });\n },\n\n //TODO: if possible move these to readonly view - but display the owner's tags/annotation (no edit)\n /** Render the tags list/control */\n _renderTags : function( $where ){\n if( !this.hasUser ){ return; }\n var view = this;\n this.tagsEditor = new TAGS.TagsEditor({\n model : this.model,\n el : $where.find( '.tags-display' ),\n onshowFirstTime : function(){ this.render(); },\n // persist state on the hda view (and not the editor) since these are currently re-created each time\n onshow : function(){ view.tagsEditorShown = true; },\n onhide : function(){ view.tagsEditorShown = false; },\n $activator : faIconButton({\n title : _l( 'Edit dataset tags' ),\n classes : 'tag-btn',\n faIcon : 'fa-tags'\n }).appendTo( $where.find( '.actions .right' ) )\n });\n if( this.tagsEditorShown ){ this.tagsEditor.toggle( true ); }\n },\n\n /** Render the annotation display/control */\n _renderAnnotation : function( $where ){\n if( !this.hasUser ){ return; }\n var view = this;\n this.annotationEditor = new ANNOTATIONS.AnnotationEditor({\n model : this.model,\n el : $where.find( '.annotation-display' ),\n onshowFirstTime : function(){ this.render(); },\n // persist state on the hda view (and not the editor) since these are currently re-created each time\n onshow : function(){ view.annotationEditorShown = true; },\n onhide : function(){ view.annotationEditorShown = false; },\n $activator : faIconButton({\n title : _l( 'Edit dataset annotation' ),\n classes : 'annotate-btn',\n faIcon : 'fa-comment'\n }).appendTo( $where.find( '.actions .right' ) )\n });\n if( this.annotationEditorShown ){ this.annotationEditor.toggle( true ); }\n },\n\n /** If the format/dbkey/genome_build isn't set, make the display a link to the edit page */\n _makeDbkeyEditLink : function( $details ){\n // make the dbkey a link to editing\n if( this.model.get( 'metadata_dbkey' ) === '?'\n && !this.model.isDeletedOrPurged() ){\n var editableDbkey = $( '?' )\n .attr( 'href', this.model.urls.edit )\n .attr( 'target', this.linkTarget );\n $details.find( '.dbkey .value' ).replaceWith( editableDbkey );\n }\n },\n\n // ......................................................................... events\n /** event map */\n events : _.extend( _.clone( _super.prototype.events ), {\n 'click .undelete-link' : '_clickUndeleteLink',\n 'click .purge-link' : '_clickPurgeLink',\n\n 'click .edit-btn' : function( ev ){ this.trigger( 'edit', this, ev ); },\n 'click .delete-btn' : function( ev ){ this.trigger( 'delete', this, ev ); },\n 'click .rerun-btn' : function( ev ){ this.trigger( 'rerun', this, ev ); },\n 'click .report-err-btn' : function( ev ){ this.trigger( 'report-err', this, ev ); },\n 'click .visualization-btn' : function( ev ){ this.trigger( 'visualize', this, ev ); },\n 'click .dbkey a' : function( ev ){ this.trigger( 'edit', this, ev ); }\n }),\n\n /** listener for item undelete (in the messages section) */\n _clickUndeleteLink : function( ev ){\n this.model.undelete();\n return false;\n },\n\n /** listener for item purge (in the messages section) */\n _clickPurgeLink : function( ev ){\n if( confirm( _l( 'This will permanently remove the data in your dataset. Are you sure?' ) ) ){\n this.model.purge();\n }\n return false;\n },\n\n // ......................................................................... misc\n /** string rep */\n toString : function(){\n var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n return 'HDAEditView(' + modelString + ')';\n }\n});\n\n\n// ............................................................................ TEMPLATES\n/** underscore templates */\nDatasetListItemEdit.prototype.templates = (function(){\n\n var warnings = _.extend( {}, _super.prototype.templates.warnings, {\n failed_metadata : BASE_MVC.wrapTemplate([\n // in this override, provide a link to the edit page\n '<% if( dataset.state === \"failed_metadata\" ){ %>',\n '',\n '<% } %>'\n ], 'dataset' ),\n\n deleted : BASE_MVC.wrapTemplate([\n // in this override, provide links to undelete or purge the dataset\n '<% if( dataset.deleted && !dataset.purged ){ %>',\n // deleted not purged\n '
                                  ',\n _l( 'This dataset has been deleted' ),\n '
                                  ', _l( 'Undelete it' ), '',\n '<% if( view.purgeAllowed ){ %>',\n '
                                  ',\n _l( 'Permanently remove it from disk' ),\n '',\n '<% } %>',\n '
                                  ',\n '<% } %>'\n ], 'dataset' )\n });\n\n var visualizationsTemplate = BASE_MVC.wrapTemplate([\n '<% if( visualizations.length === 1 ){ %>',\n '\"',\n ' target=\"<%- visualizations[0].target %>\" title=\"', _l( 'Visualize in' ),\n ' <%- visualizations[0].html %>\">',\n '',\n '',\n\n '<% } else { %>',\n '
                                  ',\n '',\n '',\n '',\n '',\n '
                                  ',\n '<% } %>'\n ], 'visualizations' );\n\n return _.extend( {}, _super.prototype.templates, {\n warnings : warnings,\n visualizations : visualizationsTemplate\n });\n}());\n\n\n//==============================================================================\n return {\n DatasetListItemEdit : DatasetListItemEdit\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/dataset/dataset-li-edit.js\n ** module id = 69\n ** module chunks = 3\n **/","define([\n \"mvc/dataset/states\",\n \"mvc/base-mvc\",\n \"utils/localization\"\n], function( STATES, BASE_MVC, _l ){\n'use strict';\n\nvar logNamespace = 'dataset';\n//==============================================================================\nvar searchableMixin = BASE_MVC.SearchableModelMixin;\n/** @class base model for any DatasetAssociation (HDAs, LDDAs, DatasetCollectionDAs).\n * No knowledge of what type (HDA/LDDA/DCDA) should be needed here.\n * The DA's are made searchable (by attribute) by mixing in SearchableModelMixin.\n */\nvar DatasetAssociation = Backbone.Model\n .extend( BASE_MVC.LoggableMixin )\n .extend( BASE_MVC.mixin( searchableMixin, /** @lends DatasetAssociation.prototype */{\n _logNamespace : logNamespace,\n\n /** default attributes for a model */\n defaults : {\n state : STATES.NEW,\n deleted : false,\n purged : false,\n name : '(unnamed dataset)',\n accessible : true,\n // sniffed datatype (sam, tabular, bed, etc.)\n data_type : '',\n file_ext : '',\n file_size : 0,\n\n // array of associated file types (eg. [ 'bam_index', ... ])\n meta_files : [],\n\n misc_blurb : '',\n misc_info : '',\n\n tags : []\n // do NOT default on annotation, as this default is valid and will be passed on 'save'\n // which is incorrect behavior when the model is only partially fetched (annos are not passed in summary data)\n //annotation : ''\n },\n\n /** instance vars and listeners */\n initialize : function( attributes, options ){\n this.debug( this + '(Dataset).initialize', attributes, options );\n\n //!! this state is not in trans.app.model.Dataset.states - set it here -\n if( !this.get( 'accessible' ) ){\n this.set( 'state', STATES.NOT_VIEWABLE );\n }\n\n /** Datasets rely/use some web controllers - have the model generate those URLs on startup */\n this.urls = this._generateUrls();\n\n this._setUpListeners();\n },\n\n /** returns misc. web urls for rendering things like re-run, display, etc. */\n _generateUrls : function(){\n var id = this.get( 'id' );\n if( !id ){ return {}; }\n var urls = {\n 'purge' : 'datasets/' + id + '/purge_async',\n 'display' : 'datasets/' + id + '/display/?preview=True',\n 'edit' : 'datasets/' + id + '/edit',\n 'download' : 'datasets/' + id + '/display?to_ext=' + this.get( 'file_ext' ),\n 'report_error' : 'dataset/errors?id=' + id,\n 'rerun' : 'tool_runner/rerun?id=' + id,\n 'show_params' : 'datasets/' + id + '/show_params',\n 'visualization' : 'visualization',\n 'meta_download' : 'dataset/get_metadata_file?hda_id=' + id + '&metadata_name='\n };\n _.each( urls, function( value, key ){\n urls[ key ] = Galaxy.root + value;\n });\n this.urls = urls;\n return urls;\n },\n\n /** set up any event listeners\n * event: state:ready fired when this DA moves into/is already in a ready state\n */\n _setUpListeners : function(){\n // if the state has changed and the new state is a ready state, fire an event\n this.on( 'change:state', function( currModel, newState ){\n this.log( this + ' has changed state:', currModel, newState );\n if( this.inReadyState() ){\n this.trigger( 'state:ready', currModel, newState, this.previous( 'state' ) );\n }\n });\n // the download url (currently) relies on having a correct file extension\n this.on( 'change:id change:file_ext', function( currModel ){\n this._generateUrls();\n });\n },\n\n // ........................................................................ common queries\n /** override to add urls */\n toJSON : function(){\n var json = Backbone.Model.prototype.toJSON.call( this );\n //console.warn( 'returning json?' );\n //return json;\n return _.extend( json, {\n urls : this.urls\n });\n },\n\n /** Is this dataset deleted or purged? */\n isDeletedOrPurged : function(){\n return ( this.get( 'deleted' ) || this.get( 'purged' ) );\n },\n\n /** Is this dataset in a 'ready' state; where 'Ready' states are states where no\n * processing (for the ds) is left to do on the server.\n */\n inReadyState : function(){\n var ready = _.contains( STATES.READY_STATES, this.get( 'state' ) );\n return ( this.isDeletedOrPurged() || ready );\n },\n\n /** Does this model already contain detailed data (as opposed to just summary level data)? */\n hasDetails : function(){\n // if it's inaccessible assume it has everything it needs\n if( !this.get( 'accessible' ) ){ return true; }\n return this.has( 'annotation' );\n },\n\n /** Convenience function to match dataset.has_data. */\n hasData : function(){\n return ( this.get( 'file_size' ) > 0 );\n },\n\n // ........................................................................ ajax\n fetch : function( options ){\n var dataset = this;\n return Backbone.Model.prototype.fetch.call( this, options )\n .always( function(){\n dataset._generateUrls();\n });\n },\n\n /** override to use actual Dates objects for create/update times */\n parse : function( response, options ){\n var parsed = Backbone.Model.prototype.parse.call( this, response, options );\n if( parsed.create_time ){\n parsed.create_time = new Date( parsed.create_time );\n }\n if( parsed.update_time ){\n parsed.update_time = new Date( parsed.update_time );\n }\n return parsed;\n },\n\n /** override to wait by default */\n save : function( attrs, options ){\n options = options || {};\n options.wait = _.isUndefined( options.wait ) ? true : options.wait;\n return Backbone.Model.prototype.save.call( this, attrs, options );\n },\n\n //NOTE: subclasses of DA's will need to implement url and urlRoot in order to have these work properly\n /** save this dataset, _Mark_ing it as deleted (just a flag) */\n 'delete' : function( options ){\n if( this.get( 'deleted' ) ){ return jQuery.when(); }\n return this.save( { deleted: true }, options );\n },\n /** save this dataset, _Mark_ing it as undeleted */\n undelete : function( options ){\n if( !this.get( 'deleted' ) || this.get( 'purged' ) ){ return jQuery.when(); }\n return this.save( { deleted: false }, options );\n },\n\n /** remove the file behind this dataset from the filesystem (if permitted) */\n purge : function _purge( options ){\n //TODO: use, override model.destroy, HDA.delete({ purge: true })\n if( this.get( 'purged' ) ){ return jQuery.when(); }\n options = options || {};\n options.url = this.urls.purge;\n\n //TODO: ideally this would be a DELETE call to the api\n // using purge async for now\n var hda = this,\n xhr = jQuery.ajax( options );\n xhr.done( function( message, status, responseObj ){\n hda.set({ deleted: true, purged: true });\n });\n xhr.fail( function( xhr, status, message ){\n // Exception messages are hidden within error page including: '...not allowed in this Galaxy instance.'\n // unbury and re-add to xhr\n var error = _l( \"Unable to purge dataset\" );\n var messageBuriedInUnfortunatelyFormattedError = ( 'Removal of datasets by users '\n + 'is not allowed in this Galaxy instance' );\n if( xhr.responseJSON && xhr.responseJSON.error ){\n error = xhr.responseJSON.error;\n } else if( xhr.responseText.indexOf( messageBuriedInUnfortunatelyFormattedError ) !== -1 ){\n error = messageBuriedInUnfortunatelyFormattedError;\n }\n xhr.responseText = error;\n hda.trigger( 'error', hda, xhr, options, _l( error ), { error: error } );\n });\n return xhr;\n },\n\n // ........................................................................ searching\n /** what attributes of an HDA will be used in a text search */\n searchAttributes : [\n 'name', 'file_ext', 'genome_build', 'misc_blurb', 'misc_info', 'annotation', 'tags'\n ],\n\n /** our attr keys don't often match the labels we display to the user - so, when using\n * attribute specifiers ('name=\"bler\"') in a term, allow passing in aliases for the\n * following attr keys.\n */\n searchAliases : {\n title : 'name',\n format : 'file_ext',\n database : 'genome_build',\n blurb : 'misc_blurb',\n description : 'misc_blurb',\n info : 'misc_info',\n tag : 'tags'\n },\n\n // ........................................................................ misc\n /** String representation */\n toString : function(){\n var nameAndId = this.get( 'id' ) || '';\n if( this.get( 'name' ) ){\n nameAndId = '\"' + this.get( 'name' ) + '\",' + nameAndId;\n }\n return 'Dataset(' + nameAndId + ')';\n }\n}));\n\n\n//==============================================================================\n/** @class Backbone collection for dataset associations.\n */\nvar DatasetAssociationCollection = Backbone.Collection.extend( BASE_MVC.LoggableMixin ).extend(\n/** @lends HistoryContents.prototype */{\n _logNamespace : logNamespace,\n\n model : DatasetAssociation,\n\n /** root api url */\n urlRoot : Galaxy.root + 'api/datasets',\n\n /** url fn */\n url : function(){\n return this.urlRoot;\n },\n\n // ........................................................................ common queries\n /** Get the ids of every item in this collection\n * @returns array of encoded ids\n */\n ids : function(){\n return this.map( function( item ){ return item.get('id'); });\n },\n\n /** Get contents that are not ready\n * @returns array of content models\n */\n notReady : function(){\n return this.filter( function( content ){\n return !content.inReadyState();\n });\n },\n\n /** return true if any datasets don't have details */\n haveDetails : function(){\n return this.all( function( dataset ){ return dataset.hasDetails(); });\n },\n\n // ........................................................................ ajax\n /** using a queue, perform ajaxFn on each of the models in this collection */\n ajaxQueue : function( ajaxFn, options ){\n var deferred = jQuery.Deferred(),\n startingLength = this.length,\n responses = [];\n\n if( !startingLength ){\n deferred.resolve([]);\n return deferred;\n }\n\n // use reverse order (stylistic choice)\n var ajaxFns = this.chain().reverse().map( function( dataset, i ){\n return function(){\n var xhr = ajaxFn.call( dataset, options );\n // if successful, notify using the deferred to allow tracking progress\n xhr.done( function( response ){\n deferred.notify({ curr: i, total: startingLength, response: response, model: dataset });\n });\n // (regardless of previous error or success) if not last ajax call, shift and call the next\n // if last fn, resolve deferred\n xhr.always( function( response ){\n responses.push( response );\n if( ajaxFns.length ){\n ajaxFns.shift()();\n } else {\n deferred.resolve( responses );\n }\n });\n };\n }).value();\n // start the queue\n ajaxFns.shift()();\n\n return deferred;\n },\n\n // ........................................................................ sorting/filtering\n /** return a new collection of datasets whose attributes contain the substring matchesWhat */\n matches : function( matchesWhat ){\n return this.filter( function( dataset ){\n return dataset.matches( matchesWhat );\n });\n },\n\n /** String representation. */\n toString : function(){\n return ([ 'DatasetAssociationCollection(', this.length, ')' ].join( '' ));\n }\n});\n\n\n//==============================================================================\n return {\n DatasetAssociation : DatasetAssociation,\n DatasetAssociationCollection : DatasetAssociationCollection\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/dataset/dataset-model.js\n ** module id = 70\n ** module chunks = 3\n **/","define([\n \"mvc/dataset/dataset-li\",\n \"mvc/base-mvc\",\n \"utils/localization\"\n], function( DATASET_LI, BASE_MVC, _l ){\n\n'use strict';\n\n//==============================================================================\nvar _super = DATASET_LI.DatasetListItemView;\n/** @class Read only view for HistoryDatasetAssociation.\n * Since there are no controls on the HDAView to hide the dataset,\n * the primary thing this class does (currently) is override templates\n * to render the HID.\n */\nvar HDAListItemView = _super.extend(\n/** @lends HDAListItemView.prototype */{\n\n className : _super.prototype.className + \" history-content\",\n\n initialize : function( attributes, options ){\n _super.prototype.initialize.call( this, attributes, options );\n },\n\n // ......................................................................... misc\n /** String representation */\n toString : function(){\n var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n return 'HDAListItemView(' + modelString + ')';\n }\n});\n\n// ............................................................................ TEMPLATES\n/** underscore templates */\nHDAListItemView.prototype.templates = (function(){\n\n var titleBarTemplate = BASE_MVC.wrapTemplate([\n // adding the hid display to the title\n '
                                  ',\n '',\n '
                                  ',\n //TODO: remove whitespace and use margin-right\n '<%- dataset.hid %> ',\n '<%- dataset.name %>',\n '
                                  ',\n '
                                  '\n ], 'dataset' );\n\n var warnings = _.extend( {}, _super.prototype.templates.warnings, {\n hidden : BASE_MVC.wrapTemplate([\n // add a warning when hidden\n '<% if( !dataset.visible ){ %>',\n '
                                  ',\n _l( 'This dataset has been hidden' ),\n '
                                  ',\n '<% } %>'\n ], 'dataset' )\n });\n\n return _.extend( {}, _super.prototype.templates, {\n titleBar : titleBarTemplate,\n warnings : warnings\n });\n}());\n\n\n\n//==============================================================================\n return {\n HDAListItemView : HDAListItemView\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/history/hda-li.js\n ** module id = 71\n ** module chunks = 3\n **/","define([\n \"mvc/dataset/dataset-model\",\n \"mvc/history/history-content-model\",\n \"mvc/base-mvc\",\n \"utils/localization\"\n], function( DATASET, HISTORY_CONTENT, BASE_MVC, _l ){\n'use strict';\n\n//==============================================================================\nvar _super = DATASET.DatasetAssociation,\n hcontentMixin = HISTORY_CONTENT.HistoryContentMixin;\n/** @class (HDA) model for a Galaxy dataset contained in and related to a history.\n */\nvar HistoryDatasetAssociation = _super.extend( BASE_MVC.mixin( hcontentMixin,\n/** @lends HistoryDatasetAssociation.prototype */{\n\n /** default attributes for a model */\n defaults : _.extend( {}, _super.prototype.defaults, hcontentMixin.defaults, {\n history_content_type: 'dataset',\n model_class : 'HistoryDatasetAssociation'\n }),\n}));\n\n//==============================================================================\n return {\n HistoryDatasetAssociation : HistoryDatasetAssociation\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/history/hda-model.js\n ** module id = 72\n ** module chunks = 3\n **/","define([\n \"mvc/dataset/states\",\n \"mvc/collection/collection-li\",\n \"mvc/collection/collection-view\",\n \"mvc/base-mvc\",\n \"utils/localization\"\n], function( STATES, DC_LI, DC_VIEW, BASE_MVC, _l ){\n\n'use strict';\n\n//==============================================================================\nvar _super = DC_LI.DCListItemView;\n/** @class Read only view for HistoryDatasetCollectionAssociation (a dataset collection inside a history).\n */\nvar HDCAListItemView = _super.extend(\n/** @lends HDCAListItemView.prototype */{\n\n className : _super.prototype.className + \" history-content\",\n\n /** event listeners */\n _setUpListeners : function(){\n _super.prototype._setUpListeners.call( this );\n\n this.listenTo( this.model, {\n 'change:populated change:visible' : function( model, options ){ this.render(); },\n });\n },\n\n /** Override to provide the proper collections panels as the foldout */\n _getFoldoutPanelClass : function(){\n switch( this.model.get( 'collection_type' ) ){\n case 'list':\n return DC_VIEW.ListCollectionView;\n case 'paired':\n return DC_VIEW.PairCollectionView;\n case 'list:paired':\n return DC_VIEW.ListOfPairsCollectionView;\n case 'list:list':\n return DC_VIEW.ListOfListsCollectionView;\n }\n throw new TypeError( 'Uknown collection_type: ' + this.model.get( 'collection_type' ) );\n },\n\n /** In this override, add the state as a class for use with state-based CSS */\n _swapNewRender : function( $newRender ){\n _super.prototype._swapNewRender.call( this, $newRender );\n //TODO: model currently has no state\n var state = !this.model.get( 'populated' ) ? STATES.RUNNING : STATES.OK;\n //if( this.model.has( 'state' ) ){\n this.$el.addClass( 'state-' + state );\n //}\n return this.$el;\n },\n\n // ......................................................................... misc\n /** String representation */\n toString : function(){\n var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n return 'HDCAListItemView(' + modelString + ')';\n }\n});\n\n/** underscore templates */\nHDCAListItemView.prototype.templates = (function(){\n\n var warnings = _.extend( {}, _super.prototype.templates.warnings, {\n hidden : BASE_MVC.wrapTemplate([\n // add a warning when hidden\n '<% if( !collection.visible ){ %>',\n '
                                  ',\n _l( 'This collection has been hidden' ),\n '
                                  ',\n '<% } %>'\n ], 'collection' )\n });\n\n// could steal this from hda-base (or use mixed content)\n var titleBarTemplate = BASE_MVC.wrapTemplate([\n // adding the hid display to the title\n '
                                  ',\n '',\n '
                                  ',\n //TODO: remove whitespace and use margin-right\n '<%- collection.hid %> ',\n '<%- collection.name %>',\n '
                                  ',\n '
                                  ',\n '
                                  '\n ], 'collection' );\n\n return _.extend( {}, _super.prototype.templates, {\n warnings : warnings,\n titleBar : titleBarTemplate\n });\n}());\n\n\n//==============================================================================\n return {\n HDCAListItemView : HDCAListItemView\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/history/hdca-li.js\n ** module id = 73\n ** module chunks = 3\n **/","define([\n \"mvc/dataset/states\",\n \"mvc/base-mvc\",\n \"utils/localization\"\n], function( STATES, BASE_MVC, _l ){\n'use strict';\n\n//==============================================================================\n/** @class Mixin for HistoryContents content (HDAs, HDCAs).\n */\nvar HistoryContentMixin = {\n\n /** default attributes for a model */\n defaults : {\n /** parent (containing) history */\n history_id : null,\n /** some content_type (HistoryContents can contain mixed model classes) */\n history_content_type: null,\n /** indicating when/what order the content was generated in the context of the history */\n hid : null,\n /** whether the user wants the content shown (visible) */\n visible : true\n },\n\n // ........................................................................ mixed content element\n // In order to be part of a MIXED bbone collection, we can't rely on the id\n // (which may collide btwn models of different classes)\n // Instead, use type_id which prefixes the history_content_type so the bbone collection can differentiate\n idAttribute : 'type_id',\n\n // ........................................................................ common queries\n /** the more common alias of visible */\n hidden : function(){\n return !this.get( 'visible' );\n },\n\n//TODO: remove\n /** based on includeDeleted, includeHidden (gen. from the container control),\n * would this ds show in the list of ds's?\n * @param {Boolean} includeDeleted are we showing deleted hdas?\n * @param {Boolean} includeHidden are we showing hidden hdas?\n */\n isVisible : function( includeDeleted, includeHidden ){\n var isVisible = true;\n if( ( !includeDeleted )\n && ( this.get( 'deleted' ) || this.get( 'purged' ) ) ){\n isVisible = false;\n }\n if( ( !includeHidden )\n && ( !this.get( 'visible' ) ) ){\n isVisible = false;\n }\n return isVisible;\n },\n\n // ........................................................................ ajax\n //TODO?: these are probably better done on the leaf classes\n /** history content goes through the 'api/histories' API */\n urlRoot: Galaxy.root + 'api/histories/',\n\n /** full url spec. for this content */\n url : function(){\n var url = this.urlRoot + this.get( 'history_id' ) + '/contents/'\n + this.get('history_content_type') + 's/' + this.get( 'id' );\n return url;\n },\n\n /** save this content as not visible */\n hide : function( options ){\n if( !this.get( 'visible' ) ){ return jQuery.when(); }\n return this.save( { visible: false }, options );\n },\n /** save this content as visible */\n unhide : function( options ){\n if( this.get( 'visible' ) ){ return jQuery.when(); }\n return this.save( { visible: true }, options );\n },\n\n // ........................................................................ misc\n toString : function(){\n return ([ this.get( 'type_id' ), this.get( 'hid' ), this.get( 'name' ) ].join(':'));\n }\n};\n\n\n//==============================================================================\n return {\n HistoryContentMixin : HistoryContentMixin\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/history/history-content-model.js\n ** module id = 74\n ** module chunks = 3\n **/","\ndefine([\n \"mvc/history/history-contents\",\n \"mvc/history/history-preferences\",\n \"mvc/base/controlled-fetch-collection\",\n \"utils/utils\",\n \"mvc/base-mvc\",\n \"utils/localization\"\n], function( HISTORY_CONTENTS, HISTORY_PREFS, CONTROLLED_FETCH_COLLECTION, UTILS, BASE_MVC, _l ){\n'use strict';\n\n//==============================================================================\n/** @class Model for a Galaxy history resource - both a record of user\n * tool use and a collection of the datasets those tools produced.\n * @name History\n * @augments Backbone.Model\n */\nvar History = Backbone.Model\n .extend( BASE_MVC.LoggableMixin )\n .extend( BASE_MVC.mixin( BASE_MVC.SearchableModelMixin, /** @lends History.prototype */{\n _logNamespace : 'history',\n\n /** ms between fetches when checking running jobs/datasets for updates */\n UPDATE_DELAY : 4000,\n\n // values from api (may need more)\n defaults : {\n model_class : 'History',\n id : null,\n name : 'Unnamed History',\n state : 'new',\n\n deleted : false,\n contents_active : {},\n contents_states : {},\n },\n\n urlRoot: Galaxy.root + 'api/histories',\n\n contentsClass : HISTORY_CONTENTS.HistoryContents,\n\n /** What model fields to search with */\n searchAttributes : [\n 'name', 'annotation', 'tags'\n ],\n\n /** Adding title and singular tag */\n searchAliases : {\n title : 'name',\n tag : 'tags'\n },\n\n // ........................................................................ set up/tear down\n /** Set up the model\n * @param {Object} historyJSON model data for this History\n * @param {Object} options any extra settings including logger\n */\n initialize : function( historyJSON, options ){\n options = options || {};\n this.logger = options.logger || null;\n this.log( this + \".initialize:\", historyJSON, options );\n\n /** HistoryContents collection of the HDAs contained in this history. */\n this.contents = new this.contentsClass( [], {\n history : this,\n historyId : this.get( 'id' ),\n order : options.order,\n });\n\n this._setUpListeners();\n this._setUpCollectionListeners();\n\n /** cached timeout id for the dataset updater */\n this.updateTimeoutId = null;\n },\n\n /** set up any event listeners for this history including those to the contained HDAs\n * events: error:contents if an error occurred with the contents collection\n */\n _setUpListeners : function(){\n // if the model's id changes ('current' or null -> an actual id), update the contents history_id\n return this.on({\n 'error' : function( model, xhr, options, msg, details ){\n this.clearUpdateTimeout();\n },\n 'change:id' : function( model, newId ){\n if( this.contents ){\n this.contents.historyId = newId;\n }\n },\n });\n },\n\n /** event handlers for the contents submodels */\n _setUpCollectionListeners : function(){\n if( !this.contents ){ return this; }\n // bubble up errors\n return this.listenTo( this.contents, {\n 'error' : function(){\n this.trigger.apply( this, jQuery.makeArray( arguments ) );\n },\n });\n },\n\n // ........................................................................ derived attributes\n /** */\n contentsShown : function(){\n var contentsActive = this.get( 'contents_active' );\n var shown = contentsActive.active || 0;\n shown += this.contents.includeDeleted? contentsActive.deleted : 0;\n shown += this.contents.includeHidden? contentsActive.hidden : 0;\n return shown;\n },\n\n /** convert size in bytes to a more human readable version */\n nice_size : function(){\n var size = this.get( 'size' );\n return size? UTILS.bytesToString( size, true, 2 ) : _l( '(empty)' );\n },\n\n /** override to add nice_size */\n toJSON : function(){\n return _.extend( Backbone.Model.prototype.toJSON.call( this ), {\n nice_size : this.nice_size()\n });\n },\n\n /** override to allow getting nice_size */\n get : function( key ){\n if( key === 'nice_size' ){\n return this.nice_size();\n }\n return Backbone.Model.prototype.get.apply( this, arguments );\n },\n\n // ........................................................................ common queries\n /** T/F is this history owned by the current user (Galaxy.user)\n * Note: that this will return false for an anon user even if the history is theirs.\n */\n ownedByCurrUser : function(){\n // no currUser\n if( !Galaxy || !Galaxy.user ){\n return false;\n }\n // user is anon or history isn't owned\n if( Galaxy.user.isAnonymous() || Galaxy.user.id !== this.get( 'user_id' ) ){\n return false;\n }\n return true;\n },\n\n /** Return the number of running jobs assoc with this history (note: unknown === 0) */\n numOfUnfinishedJobs : function(){\n var unfinishedJobIds = this.get( 'non_ready_jobs' );\n return unfinishedJobIds? unfinishedJobIds.length : 0;\n },\n\n /** Return the number of running hda/hdcas in this history (note: unknown === 0) */\n numOfUnfinishedShownContents : function(){\n return this.contents.runningAndActive().length || 0;\n },\n\n // ........................................................................ updates\n _fetchContentRelatedAttributes : function(){\n var contentRelatedAttrs = [ 'size', 'non_ready_jobs', 'contents_active', 'hid_counter' ];\n return this.fetch({ data : $.param({ keys : contentRelatedAttrs.join( ',' ) }) });\n },\n\n /** check for any changes since the last time we updated (or fetch all if ) */\n refresh : function( options ){\n // console.log( this + '.refresh' );\n options = options || {};\n var self = this;\n\n // note if there was no previous update time, all summary contents will be fetched\n var lastUpdateTime = self.lastUpdateTime;\n // if we don't flip this, then a fully-fetched list will not be re-checked via fetch\n this.contents.allFetched = false;\n var fetchFn = self.contents.currentPage !== 0\n ? function(){ return self.contents.fetchPage( 0 ); }\n : function(){ return self.contents.fetchUpdated( lastUpdateTime ); };\n // note: if there was no previous update time, all summary contents will be fetched\n return fetchFn()\n .done( function( response, status, xhr ){\n var serverResponseDatetime;\n try {\n serverResponseDatetime = new Date( xhr.getResponseHeader( 'Date' ) );\n } catch( err ){}\n self.lastUpdateTime = serverResponseDatetime || new Date();\n self.checkForUpdates( options );\n });\n },\n\n /** continuously fetch updated contents every UPDATE_DELAY ms if this history's datasets or jobs are unfinished */\n checkForUpdates : function( options ){\n // console.log( this + '.checkForUpdates' );\n options = options || {};\n var delay = this.UPDATE_DELAY;\n var self = this;\n if( !self.id ){ return; }\n\n function _delayThenUpdate(){\n // prevent buildup of updater timeouts by clearing previous if any, then set new and cache id\n self.clearUpdateTimeout();\n self.updateTimeoutId = setTimeout( function(){\n self.refresh( options );\n }, delay );\n }\n\n // if there are still datasets in the non-ready state, recurse into this function with the new time\n var nonReadyContentCount = this.numOfUnfinishedShownContents();\n // console.log( 'nonReadyContentCount:', nonReadyContentCount );\n if( nonReadyContentCount > 0 ){\n _delayThenUpdate();\n\n } else {\n // no datasets are running, but currently runnning jobs may still produce new datasets\n // see if the history has any running jobs and continue to update if so\n // (also update the size for the user in either case)\n self._fetchContentRelatedAttributes()\n .done( function( historyData ){\n // console.log( 'non_ready_jobs:', historyData.non_ready_jobs );\n if( self.numOfUnfinishedJobs() > 0 ){\n _delayThenUpdate();\n\n } else {\n // otherwise, let listeners know that all updates have stopped\n self.trigger( 'ready' );\n }\n });\n }\n },\n\n /** clear the timeout and the cached timeout id */\n clearUpdateTimeout : function(){\n if( this.updateTimeoutId ){\n clearTimeout( this.updateTimeoutId );\n this.updateTimeoutId = null;\n }\n },\n\n // ........................................................................ ajax\n /** override to use actual Dates objects for create/update times */\n parse : function( response, options ){\n var parsed = Backbone.Model.prototype.parse.call( this, response, options );\n if( parsed.create_time ){\n parsed.create_time = new Date( parsed.create_time );\n }\n if( parsed.update_time ){\n parsed.update_time = new Date( parsed.update_time );\n }\n return parsed;\n },\n\n /** fetch this histories data (using options) then it's contents (using contentsOptions) */\n fetchWithContents : function( options, contentsOptions ){\n options = options || {};\n var self = this;\n\n // console.log( this + '.fetchWithContents' );\n // TODO: push down to a base class\n options.view = 'dev-detailed';\n\n // fetch history then use history data to fetch (paginated) contents\n return this.fetch( options ).then( function getContents( history ){\n self.contents.history = self;\n self.contents.setHistoryId( history.id );\n return self.fetchContents( contentsOptions );\n });\n },\n\n /** fetch this histories contents, adjusting options based on the stored history preferences */\n fetchContents : function( options ){\n options = options || {};\n var self = this;\n\n // we're updating, reset the update time\n self.lastUpdateTime = new Date();\n return self.contents.fetchCurrentPage( options );\n },\n\n /** save this history, _Mark_ing it as deleted (just a flag) */\n _delete : function( options ){\n if( this.get( 'deleted' ) ){ return jQuery.when(); }\n return this.save( { deleted: true }, options );\n },\n /** purge this history, _Mark_ing it as purged and removing all dataset data from the server */\n purge : function( options ){\n if( this.get( 'purged' ) ){ return jQuery.when(); }\n return this.save( { deleted: true, purged: true }, options );\n },\n /** save this history, _Mark_ing it as undeleted */\n undelete : function( options ){\n if( !this.get( 'deleted' ) ){ return jQuery.when(); }\n return this.save( { deleted: false }, options );\n },\n\n /** Make a copy of this history on the server\n * @param {Boolean} current if true, set the copy as the new current history (default: true)\n * @param {String} name name of new history (default: none - server sets to: Copy of )\n * @fires copied passed this history and the response JSON from the copy\n * @returns {xhr}\n */\n copy : function( current, name, allDatasets ){\n current = ( current !== undefined )?( current ):( true );\n if( !this.id ){\n throw new Error( 'You must set the history ID before copying it.' );\n }\n\n var postData = { history_id : this.id };\n if( current ){\n postData.current = true;\n }\n if( name ){\n postData.name = name;\n }\n if( !allDatasets ){\n postData.all_datasets = false;\n }\n postData.view = 'dev-detailed';\n\n var history = this;\n var copy = jQuery.post( this.urlRoot, postData );\n // if current - queue to setAsCurrent before firing 'copied'\n if( current ){\n return copy.then( function( response ){\n var newHistory = new History( response );\n return newHistory.setAsCurrent()\n .done( function(){\n history.trigger( 'copied', history, response );\n });\n });\n }\n return copy.done( function( response ){\n history.trigger( 'copied', history, response );\n });\n },\n\n setAsCurrent : function(){\n var history = this,\n xhr = jQuery.getJSON( Galaxy.root + 'history/set_as_current?id=' + this.id );\n\n xhr.done( function(){\n history.trigger( 'set-as-current', history );\n });\n return xhr;\n },\n\n // ........................................................................ misc\n toString : function(){\n return 'History(' + this.get( 'id' ) + ',' + this.get( 'name' ) + ')';\n }\n}));\n\n\n//==============================================================================\nvar _collectionSuper = CONTROLLED_FETCH_COLLECTION.InfinitelyScrollingCollection;\n/** @class A collection of histories (per user)\n * that maintains the current history as the first in the collection.\n * New or copied histories become the current history.\n */\nvar HistoryCollection = _collectionSuper.extend( BASE_MVC.LoggableMixin ).extend({\n _logNamespace : 'history',\n\n model : History,\n /** @type {String} initial order used by collection */\n order : 'update_time',\n /** @type {Number} limit used for the first fetch (or a reset) */\n limitOnFirstFetch : 10,\n /** @type {Number} limit used for each subsequent fetch */\n limitPerFetch : 10,\n\n initialize : function( models, options ){\n options = options || {};\n this.log( 'HistoryCollection.initialize', models, options );\n _collectionSuper.prototype.initialize.call( this, models, options );\n\n /** @type {boolean} should deleted histories be included */\n this.includeDeleted = options.includeDeleted || false;\n\n /** @type {String} encoded id of the history that's current */\n this.currentHistoryId = options.currentHistoryId;\n\n this.setUpListeners();\n // note: models are sent to reset *after* this fn ends; up to this point\n // the collection *is empty*\n },\n\n urlRoot : Galaxy.root + 'api/histories',\n url : function(){ return this.urlRoot; },\n\n /** set up reflexive event handlers */\n setUpListeners : function setUpListeners(){\n return this.on({\n // when a history is deleted, remove it from the collection (if optionally set to do so)\n 'change:deleted' : function( history ){\n // TODO: this becomes complicated when more filters are used\n this.debug( 'change:deleted', this.includeDeleted, history.get( 'deleted' ) );\n if( !this.includeDeleted && history.get( 'deleted' ) ){\n this.remove( history );\n }\n },\n // listen for a history copy, setting it to current\n 'copied' : function( original, newData ){\n this.setCurrent( new History( newData, [] ) );\n },\n // when a history is made current, track the id in the collection\n 'set-as-current' : function( history ){\n var oldCurrentId = this.currentHistoryId;\n this.trigger( 'no-longer-current', oldCurrentId );\n this.currentHistoryId = history.id;\n }\n });\n },\n\n /** override to change view */\n _buildFetchData : function( options ){\n return _.extend( _collectionSuper.prototype._buildFetchData.call( this, options ), {\n view : 'dev-detailed'\n });\n },\n\n /** override to filter out deleted and purged */\n _buildFetchFilters : function( options ){\n var superFilters = _collectionSuper.prototype._buildFetchFilters.call( this, options ) || {};\n var filters = {};\n if( !this.includeDeleted ){\n filters.deleted = false;\n filters.purged = false;\n } else {\n // force API to return both deleted and non\n //TODO: when the API is updated, remove this\n filters.deleted = null;\n }\n return _.defaults( superFilters, filters );\n },\n\n /** override to fetch current as well (as it may be outside the first 10, etc.) */\n fetchFirst : function( options ){\n var self = this;\n // TODO: batch?\n var xhr = $.when();\n if( this.currentHistoryId ){\n xhr = _collectionSuper.prototype.fetchFirst.call( self, {\n silent: true,\n limit : 1,\n filters: {\n // without these a deleted current history will return [] here and block the other xhr\n 'purged' : '',\n 'deleted' : '',\n 'encoded_id-in' : this.currentHistoryId,\n }\n });\n }\n return xhr.then( function(){\n options = options || {};\n options.offset = 0;\n return self.fetchMore( options );\n });\n },\n\n /** @type {Object} map of collection available sorting orders containing comparator fns */\n comparators : _.extend( _.clone( _collectionSuper.prototype.comparators ), {\n 'name' : BASE_MVC.buildComparator( 'name', { ascending: true }),\n 'name-dsc' : BASE_MVC.buildComparator( 'name', { ascending: false }),\n 'size' : BASE_MVC.buildComparator( 'size', { ascending: false }),\n 'size-asc' : BASE_MVC.buildComparator( 'size', { ascending: true }),\n }),\n\n /** override to always have the current history first */\n sort : function( options ){\n options = options || {};\n var silent = options.silent;\n var currentHistory = this.remove( this.get( this.currentHistoryId ) );\n _collectionSuper.prototype.sort.call( this, _.defaults({ silent: true }, options ) );\n this.unshift( currentHistory, { silent: true });\n if( !silent ){\n this.trigger( 'sort', this, options );\n }\n return this;\n },\n\n /** create a new history and by default set it to be the current history */\n create : function create( data, hdas, historyOptions, xhrOptions ){\n //TODO: .create is actually a collection function that's overridden here\n var collection = this,\n xhr = jQuery.getJSON( Galaxy.root + 'history/create_new_current' );\n return xhr.done( function( newData ){\n collection.setCurrent( new History( newData, [], historyOptions || {} ) );\n });\n },\n\n /** set the current history to the given history, placing it first in the collection.\n * Pass standard bbone options for use in unshift.\n * @triggers new-current passed history and this collection\n */\n setCurrent : function( history, options ){\n options = options || {};\n // new histories go in the front\n this.unshift( history, options );\n this.currentHistoryId = history.get( 'id' );\n if( !options.silent ){\n this.trigger( 'new-current', history, this );\n }\n return this;\n },\n\n toString: function toString(){\n return 'HistoryCollection(' + this.length + ',current:' + this.currentHistoryId + ')';\n }\n});\n\n\n//==============================================================================\nreturn {\n History : History,\n HistoryCollection : HistoryCollection\n};});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/history/history-model.js\n ** module id = 75\n ** module chunks = 3\n **/","define([\n \"mvc/list/list-item\",\n \"ui/loading-indicator\",\n \"mvc/base-mvc\",\n \"utils/localization\",\n \"ui/search-input\"\n], function( LIST_ITEM, LoadingIndicator, BASE_MVC, _l ){\n\n'use strict';\n\nvar logNamespace = 'list';\n/* ============================================================================\nTODO:\n\n============================================================================ */\n/** @class View for a list/collection of models and the sub-views of those models.\n * Sub-views must (at least have the interface if not) inherit from ListItemView.\n * (For a list panel that also includes some 'container' model (History->HistoryContents)\n * use ModelWithListPanel)\n *\n * Allows for:\n * searching collection/sub-views\n * selecting/multi-selecting sub-views\n *\n * Currently used:\n * for dataset/dataset-choice\n * as superclass of ModelListPanel\n */\nvar ListPanel = Backbone.View.extend( BASE_MVC.LoggableMixin ).extend(/** @lends ListPanel.prototype */{\n _logNamespace : logNamespace,\n\n /** class to use for constructing the sub-views */\n viewClass : LIST_ITEM.ListItemView,\n /** class to used for constructing collection of sub-view models */\n collectionClass : Backbone.Collection,\n\n tagName : 'div',\n className : 'list-panel',\n\n /** (in ms) that jquery effects will use */\n fxSpeed : 'fast',\n\n /** string to display when the collection has no contents */\n emptyMsg : _l( 'This list is empty' ),\n /** displayed when no items match the search terms */\n noneFoundMsg : _l( 'No matching items found' ),\n /** string used for search placeholder */\n searchPlaceholder : _l( 'search' ),\n\n // ......................................................................... SET UP\n /** Set up the view, set up storage, bind listeners to HistoryContents events\n * @param {Object} attributes optional settings for the list\n */\n initialize : function( attributes, options ){\n attributes = attributes || {};\n // set the logger if requested\n if( attributes.logger ){\n this.logger = attributes.logger;\n }\n this.log( this + '.initialize:', attributes );\n\n // ---- instance vars\n /** how quickly should jquery fx run? */\n this.fxSpeed = _.has( attributes, 'fxSpeed' )?( attributes.fxSpeed ):( this.fxSpeed );\n\n /** filters for displaying subviews */\n this.filters = [];\n /** current search terms */\n this.searchFor = attributes.searchFor || '';\n\n /** loading indicator */\n // this.indicator = new LoadingIndicator( this.$el );\n\n /** currently showing selectors on items? */\n this.selecting = ( attributes.selecting !== undefined )? attributes.selecting : true;\n //this.selecting = false;\n\n /** cached selected item.model.ids to persist btwn renders */\n this.selected = attributes.selected || [];\n /** the last selected item.model.id */\n this.lastSelected = null;\n\n /** are sub-views draggable */\n this.dragItems = attributes.dragItems || false;\n\n /** list item view class (when passed models) */\n this.viewClass = attributes.viewClass || this.viewClass;\n\n /** list item views */\n this.views = [];\n /** list item models */\n this.collection = attributes.collection || this._createDefaultCollection();\n\n /** filter fns run over collection items to see if they should show in the list */\n this.filters = attributes.filters || [];\n\n /** override $scrollContainer fn via attributes - fn should return jq for elem to call scrollTo on */\n this.$scrollContainer = attributes.$scrollContainer || this.$scrollContainer;\n\n /** @type {String} generic title */\n this.title = attributes.title || '';\n /** @type {String} generic subtitle */\n this.subtitle = attributes.subtitle || '';\n\n this._setUpListeners();\n },\n\n // ------------------------------------------------------------------------ listeners\n /** create any event listeners for the list */\n _setUpListeners : function(){\n this.off();\n\n //TODO: move errorHandler down into list-view from history-view or\n // pass to global error handler (Galaxy)\n this.on({\n error: function( model, xhr, options, msg, details ){\n //this.errorHandler( model, xhr, options, msg, details );\n console.error( model, xhr, options, msg, details );\n },\n // show hide the loading indicator\n loading: function(){\n this._showLoadingIndicator( 'loading...', 40 );\n },\n 'loading-done': function(){\n this._hideLoadingIndicator( 40 );\n },\n });\n\n // throw the first render up as a diff namespace using once (for outside consumption)\n this.once( 'rendered', function(){\n this.trigger( 'rendered:initial', this );\n });\n\n this._setUpCollectionListeners();\n this._setUpViewListeners();\n return this;\n },\n\n /** create and return a collection for when none is initially passed */\n _createDefaultCollection : function(){\n // override\n return new this.collectionClass([]);\n },\n\n /** listening for collection events */\n _setUpCollectionListeners : function(){\n this.log( this + '._setUpCollectionListeners', this.collection );\n this.stopListening( this.collection );\n\n // bubble up error events\n this.listenTo( this.collection, {\n error : function( model, xhr, options, msg, details ){\n this.trigger( 'error', model, xhr, options, msg, details );\n },\n update : function( collection, options ){\n var changes = options.changes;\n // console.info( collection + ', update:', changes, '\\noptions:', options );\n // more than one: render everything\n if( options.renderAll || ( changes.added.length + changes.removed.length > 1 ) ){\n return this.renderItems();\n }\n // otherwise, let the single add/remove handlers do it\n if( changes.added.length === 1 ){\n return this.addItemView( _.first( changes.added ), collection, options );\n }\n if( changes.removed.length === 1 ){\n return this.removeItemView( _.first( changes.removed ), collection, options );\n }\n }\n });\n return this;\n },\n\n /** listening for sub-view events that bubble up with the 'view:' prefix */\n _setUpViewListeners : function(){\n this.log( this + '._setUpViewListeners' );\n\n // shift to select a range\n this.on({\n 'view:selected': function( view, ev ){\n if( ev && ev.shiftKey && this.lastSelected ){\n var lastSelectedView = this.viewFromModelId( this.lastSelected );\n if( lastSelectedView ){\n this.selectRange( view, lastSelectedView );\n }\n } else if( ev && ev.altKey && !this.selecting ){\n this.showSelectors();\n }\n this.selected.push( view.model.id );\n this.lastSelected = view.model.id;\n },\n\n 'view:de-selected': function( view, ev ){\n this.selected = _.without( this.selected, view.model.id );\n }\n });\n },\n\n // ------------------------------------------------------------------------ rendering\n /** Render this content, set up ui.\n * @param {Number or String} speed the speed of the render\n */\n render : function( speed ){\n this.log( this + '.render', speed );\n var $newRender = this._buildNewRender();\n this._setUpBehaviors( $newRender );\n this._queueNewRender( $newRender, speed );\n return this;\n },\n\n /** Build a temp div containing the new children for the view's $el. */\n _buildNewRender : function(){\n this.debug( this + '(ListPanel)._buildNewRender' );\n var $newRender = $( this.templates.el( {}, this ) );\n this._renderControls( $newRender );\n this._renderTitle( $newRender );\n this._renderSubtitle( $newRender );\n this._renderSearch( $newRender );\n this.renderItems( $newRender );\n return $newRender;\n },\n\n /** Build a temp div containing the new children for the view's $el. */\n _renderControls : function( $newRender ){\n this.debug( this + '(ListPanel)._renderControls' );\n var $controls = $( this.templates.controls( {}, this ) );\n $newRender.find( '.controls' ).replaceWith( $controls );\n return $controls;\n },\n\n /** return a jQuery object containing the title DOM */\n _renderTitle : function( $where ){\n //$where = $where || this.$el;\n //$where.find( '.title' ).replaceWith( ... )\n },\n\n /** return a jQuery object containing the subtitle DOM (if any) */\n _renderSubtitle : function( $where ){\n //$where = $where || this.$el;\n //$where.find( '.title' ).replaceWith( ... )\n },\n\n /** Fade out the old el, swap in the new contents, then fade in.\n * @param {Number or String} speed jq speed to use for rendering effects\n * @fires rendered when rendered\n */\n _queueNewRender : function( $newRender, speed ) {\n speed = ( speed === undefined )?( this.fxSpeed ):( speed );\n var panel = this;\n panel.log( '_queueNewRender:', $newRender, speed );\n\n $( panel ).queue( 'fx', [\n function( next ){\n panel.$el.fadeOut( speed, next );\n },\n function( next ){\n panel._swapNewRender( $newRender );\n next();\n },\n function( next ){\n panel.$el.fadeIn( speed, next );\n },\n function( next ){\n panel.trigger( 'rendered', panel );\n next();\n }\n ]);\n },\n\n /** empty out the current el, move the $newRender's children in */\n _swapNewRender : function( $newRender ){\n this.$el.empty().attr( 'class', this.className ).append( $newRender.children() );\n if( this.selecting ){ this.showSelectors( 0 ); }\n return this;\n },\n\n /** Set up any behaviors, handlers (ep. plugins) that need to be called when the entire view has been built but\n * not attached to the page yet.\n */\n _setUpBehaviors : function( $where ){\n $where = $where || this.$el;\n this.$controls( $where ).find('[title]').tooltip();\n // set up the pupup for actions available when multi selecting\n this._renderMultiselectActionMenu( $where );\n return this;\n },\n\n /** render a menu containing the actions available to sets of selected items */\n _renderMultiselectActionMenu : function( $where ){\n $where = $where || this.$el;\n var $menu = $where.find( '.list-action-menu' ),\n actions = this.multiselectActions();\n if( !actions.length ){\n return $menu.empty();\n }\n\n var $newMenu = $([\n '
                                  ',\n '',\n '
                                    ', '
                                  ',\n '
                                  '\n ].join(''));\n var $actions = actions.map( function( action ){\n var html = [ '
                                • ', action.html, '
                                • ' ].join( '' );\n return $( html ).click( function( ev ){\n ev.preventDefault();\n return action.func( ev );\n });\n });\n $newMenu.find( 'ul' ).append( $actions );\n $menu.replaceWith( $newMenu );\n return $newMenu;\n },\n\n /** return a list of plain objects used to render multiselect actions menu. Each object should have:\n * html: an html string used as the anchor contents\n * func: a function called when the anchor is clicked (passed the click event)\n */\n multiselectActions : function(){\n return [];\n },\n\n // ------------------------------------------------------------------------ sub-$element shortcuts\n /** the scroll container for this panel - can be $el, $el.parent(), or grandparent depending on context */\n $scrollContainer : function( $where ){\n // override or set via attributes.$scrollContainer\n return ( $where || this.$el ).parent().parent();\n },\n /** convenience selector for the section that displays the list controls */\n $controls : function( $where ){\n return ( $where || this.$el ).find( '> .controls' );\n },\n /** list-items: where the subviews are contained in the view's dom */\n $list : function( $where ){\n return ( $where || this.$el ).find( '> .list-items' );\n },\n /** container where list messages are attached */\n $messages : function( $where ){\n //TODO: controls isn't really correct here (only for ModelListPanel)\n return ( $where || this.$el ).find( '> .controls .messages' );\n },\n /** the message displayed when no views can be shown (no views, none matching search) */\n $emptyMessage : function( $where ){\n return ( $where || this.$el ).find( '> .empty-message' );\n },\n\n // ------------------------------------------------------------------------ hda sub-views\n /** render the subviews for the list's collection */\n renderItems : function( $whereTo ){\n $whereTo = $whereTo || this.$el;\n var panel = this;\n panel.log( this + '.renderItems', $whereTo );\n\n var $list = panel.$list( $whereTo );\n panel.freeViews();\n // console.log( 'views freed' );\n //TODO:? cache and re-use views?\n var shownModels = panel._filterCollection();\n // console.log( 'models filtered:', shownModels );\n\n panel.views = shownModels.map( function( itemModel ){\n var view = panel._createItemView( itemModel );\n return view;\n });\n\n $list.empty();\n // console.log( 'list emptied' );\n if( panel.views.length ){\n panel._attachItems( $whereTo );\n // console.log( 'items attached' );\n }\n panel._renderEmptyMessage( $whereTo ).toggle( !panel.views.length );\n panel.trigger( 'views:ready', panel.views );\n\n // console.log( '------------------------------------------- rendering items' );\n return panel.views;\n },\n\n /** Filter the collection to only those models that should be currently viewed */\n _filterCollection : function(){\n // override this\n var panel = this;\n return panel.collection.filter( _.bind( panel._filterItem, panel ) );\n },\n\n /** Should the model be viewable in the current state?\n * Checks against this.filters and this.searchFor\n */\n _filterItem : function( model ){\n // override this\n var panel = this;\n return ( _.every( panel.filters.map( function( fn ){ return fn.call( model ); }) ) )\n && ( !panel.searchFor || model.matchesAll( panel.searchFor ) );\n },\n\n /** Create a view for a model and set up it's listeners */\n _createItemView : function( model ){\n var ViewClass = this._getItemViewClass( model );\n var options = _.extend( this._getItemViewOptions( model ), {\n model : model\n });\n var view = new ViewClass( options );\n this._setUpItemViewListeners( view );\n return view;\n },\n\n /** Free a view for a model. Note: does not remove it from the DOM */\n _destroyItemView : function( view ){\n this.stopListening( view );\n this.views = _.without( this.views, view );\n },\n\n _destroyItemViews : function( view ){\n var self = this;\n self.views.forEach( function( v ){\n self.stopListening( v );\n });\n self.views = [];\n return self;\n },\n\n /** free any sub-views the list has */\n freeViews : function(){\n return this._destroyItemViews();\n },\n\n /** Get the bbone view class based on the model */\n _getItemViewClass : function( model ){\n // override this\n return this.viewClass;\n },\n\n /** Get the options passed to the new view based on the model */\n _getItemViewOptions : function( model ){\n // override this\n return {\n //logger : this.logger,\n fxSpeed : this.fxSpeed,\n expanded : false,\n selectable : this.selecting,\n selected : _.contains( this.selected, model.id ),\n draggable : this.dragItems\n };\n },\n\n /** Set up listeners for new models */\n _setUpItemViewListeners : function( view ){\n var panel = this;\n // send all events to the panel, re-namspaceing them with the view prefix\n this.listenTo( view, 'all', function(){\n var args = Array.prototype.slice.call( arguments, 0 );\n args[0] = 'view:' + args[0];\n panel.trigger.apply( panel, args );\n });\n\n // drag multiple - hijack ev.setData to add all selected items\n this.listenTo( view, 'draggable:dragstart', function( ev, v ){\n //TODO: set multiple drag data here\n var json = {},\n selected = this.getSelectedModels();\n if( selected.length ){\n json = selected.toJSON();\n } else {\n json = [ v.model.toJSON() ];\n }\n ev.dataTransfer.setData( 'text', JSON.stringify( json ) );\n //ev.dataTransfer.setDragImage( v.el, 60, 60 );\n }, this );\n\n return panel;\n },\n\n /** Attach views in this.views to the model based on $whereTo */\n _attachItems : function( $whereTo ){\n var self = this;\n // console.log( '_attachItems:', $whereTo, this.$list( $whereTo ) );\n //ASSUMES: $list has been emptied\n this.$list( $whereTo ).append( this.views.map( function( view ){\n return self._renderItemView$el( view );\n }));\n return this;\n },\n\n /** get a given subview's $el (or whatever may wrap it) and return it */\n _renderItemView$el : function( view ){\n // useful to wrap and override\n return view.render(0).$el;\n },\n\n /** render the empty/none-found message */\n _renderEmptyMessage : function( $whereTo ){\n this.debug( '_renderEmptyMessage', $whereTo, this.searchFor );\n var text = this.searchFor? this.noneFoundMsg : this.emptyMsg;\n return this.$emptyMessage( $whereTo ).text( text );\n },\n\n /** expand all item views */\n expandAll : function(){\n _.each( this.views, function( view ){\n view.expand();\n });\n },\n\n /** collapse all item views */\n collapseAll : function(){\n _.each( this.views, function( view ){\n view.collapse();\n });\n },\n\n // ------------------------------------------------------------------------ collection/views syncing\n /** Add a view (if the model should be viewable) to the panel */\n addItemView : function( model, collection, options ){\n // console.log( this + '.addItemView:', model );\n var panel = this;\n // get the index of the model in the list of filtered models shown by this list\n // in order to insert the view in the proper place\n //TODO:? potentially expensive\n var modelIndex = panel._filterCollection().indexOf( model );\n if( modelIndex === -1 ){ return undefined; }\n var view = panel._createItemView( model );\n // console.log( 'adding and rendering:', modelIndex, view.toString() );\n\n $( view ).queue( 'fx', [\n function( next ){\n // hide the empty message first if only view\n if( panel.$emptyMessage().is( ':visible' ) ){\n panel.$emptyMessage().fadeOut( panel.fxSpeed, next );\n } else {\n next();\n }\n },\n function( next ){\n panel._attachView( view, modelIndex );\n next();\n }\n ]);\n return view;\n },\n\n /** internal fn to add view (to both panel.views and panel.$list) */\n _attachView : function( view, modelIndex, useFx ){\n // console.log( this + '._attachView:', view, modelIndex, useFx );\n useFx = _.isUndefined( useFx )? true : useFx;\n modelIndex = modelIndex || 0;\n var panel = this;\n\n // use the modelIndex to splice into views and insert at the proper index in the DOM\n panel.views.splice( modelIndex, 0, view );\n panel._insertIntoListAt( modelIndex, panel._renderItemView$el( view ).hide() );\n\n panel.trigger( 'view:attached', view );\n if( useFx ){\n view.$el.slideDown( panel.fxSpeed, function(){\n panel.trigger( 'view:attached:rendered' );\n });\n } else {\n view.$el.show();\n panel.trigger( 'view:attached:rendered' );\n }\n return view;\n },\n\n /** insert a jq object as a child of list-items at the specified *DOM index* */\n _insertIntoListAt : function( index, $what ){\n // console.log( this + '._insertIntoListAt:', index, $what );\n var $list = this.$list();\n if( index === 0 ){\n $list.prepend( $what );\n } else {\n $list.children().eq( index - 1 ).after( $what );\n }\n return $what;\n },\n\n /** Remove a view from the panel (if found) */\n removeItemView : function( model, collection, options ){\n var panel = this;\n var view = _.find( panel.views, function( v ){ return v.model === model; });\n if( !view ){ return undefined; }\n panel.views = _.without( panel.views, view );\n panel.trigger( 'view:removed', view );\n\n // potentially show the empty message if no views left\n // use anonymous queue here - since remove can happen multiple times\n $({}).queue( 'fx', [\n function( next ){\n view.$el.fadeOut( panel.fxSpeed, next );\n },\n function( next ){\n view.remove();\n panel.trigger( 'view:removed:rendered' );\n if( !panel.views.length ){\n panel._renderEmptyMessage().fadeIn( panel.fxSpeed, next );\n } else {\n next();\n }\n }\n ]);\n return view;\n },\n\n /** get views based on model.id */\n viewFromModelId : function( id ){\n return _.find( this.views, function( v ){ return v.model.id === id; });\n },\n\n /** get views based on model */\n viewFromModel : function( model ){\n return model ? this.viewFromModelId( model.id ) : undefined;\n },\n\n /** get views based on model properties */\n viewsWhereModel : function( properties ){\n return this.views.filter( function( view ){\n return _.isMatch( view.model.attributes, properties );\n });\n },\n\n /** A range of views between (and including) viewA and viewB */\n viewRange : function( viewA, viewB ){\n if( viewA === viewB ){ return ( viewA )?( [ viewA ] ):( [] ); }\n\n var indexA = this.views.indexOf( viewA ),\n indexB = this.views.indexOf( viewB );\n\n // handle not found\n if( indexA === -1 || indexB === -1 ){\n if( indexA === indexB ){ return []; }\n return ( indexA === -1 )?( [ viewB ] ):( [ viewA ] );\n }\n // reverse if indeces are\n //note: end inclusive\n return ( indexA < indexB )?\n this.views.slice( indexA, indexB + 1 ) :\n this.views.slice( indexB, indexA + 1 );\n },\n\n // ------------------------------------------------------------------------ searching\n /** render a search input for filtering datasets shown\n * (see SearchableMixin in base-mvc for implementation of the actual searching)\n * return will start the search\n * esc will clear the search\n * clicking the clear button will clear the search\n * uses searchInput in ui.js\n */\n _renderSearch : function( $where ){\n $where.find( '.controls .search-input' ).searchInput({\n placeholder : this.searchPlaceholder,\n initialVal : this.searchFor,\n onfirstsearch : _.bind( this._firstSearch, this ),\n onsearch : _.bind( this.searchItems, this ),\n onclear : _.bind( this.clearSearch, this )\n });\n return $where;\n },\n\n /** What to do on the first search entered */\n _firstSearch : function( searchFor ){\n // override to load model details if necc.\n this.log( 'onFirstSearch', searchFor );\n return this.searchItems( searchFor );\n },\n\n /** filter view list to those that contain the searchFor terms */\n searchItems : function( searchFor, force ){\n this.log( 'searchItems', searchFor, this.searchFor, force );\n if( !force && this.searchFor === searchFor ){ return this; }\n this.searchFor = searchFor;\n this.renderItems();\n this.trigger( 'search:searching', searchFor, this );\n var $search = this.$( '> .controls .search-query' );\n if( $search.val() !== searchFor ){\n $search.val( searchFor );\n }\n return this;\n },\n\n /** clear the search filters and show all views that are normally shown */\n clearSearch : function( searchFor ){\n //this.log( 'onSearchClear', this );\n this.searchFor = '';\n this.trigger( 'search:clear', this );\n this.$( '> .controls .search-query' ).val( '' );\n this.renderItems();\n return this;\n },\n\n // ------------------------------------------------------------------------ selection\n /** @type Integer when the number of list item views is >= to this, don't animate selectors */\n THROTTLE_SELECTOR_FX_AT : 20,\n\n /** show selectors on all visible itemViews and associated controls */\n showSelectors : function( speed ){\n speed = ( speed !== undefined )?( speed ):( this.fxSpeed );\n this.selecting = true;\n this.$( '.list-actions' ).slideDown( speed );\n speed = this.views.length >= this.THROTTLE_SELECTOR_FX_AT? 0 : speed;\n _.each( this.views, function( view ){\n view.showSelector( speed );\n });\n //this.selected = [];\n //this.lastSelected = null;\n },\n\n /** hide selectors on all visible itemViews and associated controls */\n hideSelectors : function( speed ){\n speed = ( speed !== undefined )?( speed ):( this.fxSpeed );\n this.selecting = false;\n this.$( '.list-actions' ).slideUp( speed );\n speed = this.views.length >= this.THROTTLE_SELECTOR_FX_AT? 0 : speed;\n _.each( this.views, function( view ){\n view.hideSelector( speed );\n });\n this.selected = [];\n this.lastSelected = null;\n },\n\n /** show or hide selectors on all visible itemViews and associated controls */\n toggleSelectors : function(){\n if( !this.selecting ){\n this.showSelectors();\n } else {\n this.hideSelectors();\n }\n },\n\n /** select all visible items */\n selectAll : function( event ){\n _.each( this.views, function( view ){\n view.select( event );\n });\n },\n\n /** deselect all visible items */\n deselectAll : function( event ){\n this.lastSelected = null;\n _.each( this.views, function( view ){\n view.deselect( event );\n });\n },\n\n /** select a range of datasets between A and B */\n selectRange : function( viewA, viewB ){\n var range = this.viewRange( viewA, viewB );\n _.each( range, function( view ){\n view.select();\n });\n return range;\n },\n\n /** return an array of all currently selected itemViews */\n getSelectedViews : function(){\n return _.filter( this.views, function( v ){\n return v.selected;\n });\n },\n\n /** return a collection of the models of all currenly selected items */\n getSelectedModels : function(){\n // console.log( '(getSelectedModels)' );\n return new this.collection.constructor( _.map( this.getSelectedViews(), function( view ){\n return view.model;\n }));\n },\n\n // ------------------------------------------------------------------------ loading indicator\n /** hide the $el and display a loading indicator (in the $el's parent) when loading new data */\n _showLoadingIndicator : function( msg, speed, callback ){\n this.debug( '_showLoadingIndicator', this.indicator, msg, speed, callback );\n speed = ( speed !== undefined )?( speed ):( this.fxSpeed );\n if( !this.indicator ){\n this.indicator = new LoadingIndicator( this.$el );\n this.debug( '\\t created', this.indicator );\n }\n if( !this.$el.is( ':visible' ) ){\n this.indicator.show( 0, callback );\n } else {\n this.$el.fadeOut( speed );\n this.indicator.show( msg, speed, callback );\n }\n },\n\n /** hide the loading indicator */\n _hideLoadingIndicator : function( speed, callback ){\n this.debug( '_hideLoadingIndicator', this.indicator, speed, callback );\n speed = ( speed !== undefined )?( speed ):( this.fxSpeed );\n if( this.indicator ){\n this.indicator.hide( speed, callback );\n }\n },\n\n // ------------------------------------------------------------------------ scrolling\n /** get the current scroll position of the panel in its parent */\n scrollPosition : function(){\n return this.$scrollContainer().scrollTop();\n },\n\n /** set the current scroll position of the panel in its parent */\n scrollTo : function( pos, speed ){\n speed = speed || 0;\n this.$scrollContainer().animate({ scrollTop: pos }, speed );\n return this;\n },\n\n /** Scrolls the panel to the top. */\n scrollToTop : function( speed ){\n return this.scrollTo( 0, speed );\n },\n\n /** scroll to the given view in list-items */\n scrollToItem : function( view, speed ){\n if( !view ){ return this; }\n return this;\n },\n\n /** Scrolls the panel to show the content with the given id. */\n scrollToId : function( id, speed ){\n return this.scrollToItem( this.viewFromModelId( id ), speed );\n },\n\n // ------------------------------------------------------------------------ panel events\n /** event map */\n events : {\n 'click .select-all' : 'selectAll',\n 'click .deselect-all' : 'deselectAll'\n },\n\n // ------------------------------------------------------------------------ misc\n /** Return a string rep of the panel */\n toString : function(){\n return 'ListPanel(' + this.collection + ')';\n }\n});\n\n// ............................................................................ TEMPLATES\n/** underscore templates */\nListPanel.prototype.templates = (function(){\n\n var elTemplate = BASE_MVC.wrapTemplate([\n // temp container\n '
                                  ',\n '
                                  ',\n '
                                  ',\n '
                                  ',\n '
                                  '\n ]);\n\n var controlsTemplate = BASE_MVC.wrapTemplate([\n '
                                  ',\n '
                                  ',\n '
                                  <%- view.title %>
                                  ',\n '
                                  ',\n '
                                  <%- view.subtitle %>
                                  ',\n // buttons, controls go here\n '
                                  ',\n // deleted msg, etc.\n '
                                  ',\n\n '
                                  ',\n '
                                  ',\n '
                                  ',\n\n // show when selectors are shown\n '
                                  ',\n '
                                  ',\n '',\n '',\n '
                                  ',\n '
                                  ',\n '
                                  ',\n '
                                  ',\n '
                                  '\n ]);\n\n return {\n el : elTemplate,\n controls : controlsTemplate\n };\n}());\n\n\n//=============================================================================\n/** View for a model that has a sub-collection (e.g. History, DatasetCollection)\n * Allows:\n * the model to be reset\n * auto assign panel.collection to panel.model[ panel.modelCollectionKey ]\n *\n */\nvar ModelListPanel = ListPanel.extend({\n\n /** key of attribute in model to assign to this.collection */\n modelCollectionKey : 'contents',\n\n initialize : function( attributes ){\n ListPanel.prototype.initialize.call( this, attributes );\n this.selecting = ( attributes.selecting !== undefined )? attributes.selecting : false;\n\n this.setModel( this.model, attributes );\n },\n\n /** release/free/shutdown old models and set up panel for new models\n * @fires new-model with the panel as parameter\n */\n setModel : function( model, attributes ){\n attributes = attributes || {};\n this.debug( this + '.setModel:', model, attributes );\n\n this.freeModel();\n this.freeViews();\n\n if( model ){\n var oldModelId = this.model? this.model.get( 'id' ): null;\n\n // set up the new model with user, logger, storage, events\n this.model = model;\n if( this.logger ){\n this.model.logger = this.logger;\n }\n this._setUpModelListeners();\n\n //TODO: relation btwn model, collection becoming tangled here\n // free the collection, and assign the new collection to either\n // the model[ modelCollectionKey ], attributes.collection, or an empty vanilla collection\n this.stopListening( this.collection );\n this.collection = this.model[ this.modelCollectionKey ]\n || attributes.collection\n || this._createDefaultCollection();\n this._setUpCollectionListeners();\n\n if( oldModelId && model.get( 'id' ) !== oldModelId ){\n this.trigger( 'new-model', this );\n }\n }\n return this;\n },\n\n /** free the current model and all listeners for it, free any views for the model */\n freeModel : function(){\n // stop/release the previous model, and clear cache to sub-views\n if( this.model ){\n this.stopListening( this.model );\n //TODO: see base-mvc\n //this.model.free();\n //this.model = null;\n }\n return this;\n },\n\n // ------------------------------------------------------------------------ listening\n /** listening for model events */\n _setUpModelListeners : function(){\n // override\n this.log( this + '._setUpModelListeners', this.model );\n // bounce model errors up to the panel\n this.listenTo( this.model, 'error', function(){\n var args = Array.prototype.slice.call( arguments, 0 );\n //args.unshift( 'model:error' );\n args.unshift( 'error' );\n this.trigger.apply( this, args );\n }, this );\n\n // debugging\n if( this.logger ){\n this.listenTo( this.model, 'all', function( event ){\n this.info( this + '(model)', event, arguments );\n });\n }\n return this;\n },\n\n /** Build a temp div containing the new children for the view's $el.\n */\n _renderControls : function( $newRender ){\n this.debug( this + '(ModelListPanel)._renderControls' );\n var json = this.model? this.model.toJSON() : {},\n $controls = $( this.templates.controls( json, this ) );\n $newRender.find( '.controls' ).replaceWith( $controls );\n return $controls;\n },\n\n // ------------------------------------------------------------------------ misc\n /** Return a string rep of the panel */\n toString : function(){\n return 'ModelListPanel(' + this.model + ')';\n }\n});\n\n// ............................................................................ TEMPLATES\n/** underscore templates */\nModelListPanel.prototype.templates = (function(){\n\n var controlsTemplate = BASE_MVC.wrapTemplate([\n '
                                  ',\n '
                                  ',\n //TODO: this is really the only difference - consider factoring titlebar out\n '
                                  <%- model.name %>
                                  ',\n '
                                  ',\n '
                                  <%- view.subtitle %>
                                  ',\n '
                                  ',\n '
                                  ',\n\n '
                                  ',\n '
                                  ',\n '
                                  ',\n\n '
                                  ',\n '
                                  ',\n '',\n '',\n '
                                  ',\n '
                                  ',\n '
                                  ',\n '
                                  ',\n '
                                  '\n ]);\n\n return _.extend( _.clone( ListPanel.prototype.templates ), {\n controls : controlsTemplate\n });\n}());\n\n\n//=============================================================================\n return {\n ListPanel : ListPanel,\n ModelListPanel : ModelListPanel\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/list/list-view.js\n ** module id = 76\n ** module chunks = 3\n **/","define([\n \"mvc/base-mvc\",\n \"utils/localization\"\n], function( baseMVC, _l ){\n// =============================================================================\n/** A view on any model that has a 'tags' attribute (a list of tag strings)\n * Incorporates the select2 jQuery plugin for tags display/editing:\n * http://ivaynberg.github.io/select2/\n */\nvar TagsEditor = Backbone.View\n .extend( baseMVC.LoggableMixin )\n .extend( baseMVC.HiddenUntilActivatedViewMixin ).extend({\n\n tagName : 'div',\n className : 'tags-display',\n\n /** Set up listeners, parse options */\n initialize : function( options ){\n //console.debug( this, options );\n // only listen to the model only for changes to tags - re-render\n this.listenTo( this.model, 'change:tags', function(){\n this.render();\n });\n this.hiddenUntilActivated( options.$activator, options );\n },\n\n /** Build the DOM elements, call select to on the created input, and set up behaviors */\n render : function(){\n var view = this;\n this.$el.html( this._template() );\n\n this.$input().select2({\n placeholder : 'Add tags',\n width : '100%',\n tags : function(){\n // initialize possible tags in the dropdown based on all the tags the user has used so far\n return view._getTagsUsed();\n }\n });\n\n this._setUpBehaviors();\n return this;\n },\n\n /** @returns {String} the html text used to build the view's DOM */\n _template : function(){\n return [\n //TODO: make prompt optional\n '',\n // set up initial tags by adding as CSV to input vals (necc. to init select2)\n ''\n ].join( '' );\n },\n\n /** @returns {String} the sorted, comma-separated tags from the model */\n tagsToCSV : function(){\n var tagsArray = this.model.get( 'tags' );\n if( !_.isArray( tagsArray ) || _.isEmpty( tagsArray ) ){\n return '';\n }\n return tagsArray.map( function( tag ){\n return _.escape( tag );\n }).sort().join( ',' );\n },\n\n /** @returns {jQuery} the input for this view */\n $input : function(){\n return this.$el.find( 'input.tags-input' );\n },\n\n /** @returns {String[]} all tags used by the current user */\n _getTagsUsed : function(){\n//TODO: global\n return Galaxy.user.get( 'tags_used' );\n },\n\n /** set up any event listeners on the view's DOM (mostly handled by select2) */\n _setUpBehaviors : function(){\n var view = this;\n this.$input().on( 'change', function( event ){\n // save the model's tags in either remove or added event\n view.model.save({ tags: event.val }, { silent: true });\n // if it's new, add the tag to the users tags\n if( event.added ){\n //??: solve weird behavior in FF on test.galaxyproject.org where\n // event.added.text is string object: 'String{ 0=\"o\", 1=\"n\", 2=\"e\" }'\n view._addNewTagToTagsUsed( event.added.text + '' );\n }\n });\n },\n\n /** add a new tag (if not already there) to the list of all tags used by the user\n * @param {String} newTag the tag to add to the list of used\n */\n _addNewTagToTagsUsed : function( newTag ){\n//TODO: global\n var tagsUsed = Galaxy.user.get( 'tags_used' );\n if( !_.contains( tagsUsed, newTag ) ){\n tagsUsed.push( newTag );\n tagsUsed.sort();\n Galaxy.user.set( 'tags_used', tagsUsed );\n }\n },\n\n /** shut down event listeners and remove this view's DOM */\n remove : function(){\n this.$input.off();\n this.stopListening( this.model );\n Backbone.View.prototype.remove.call( this );\n },\n\n /** string rep */\n toString : function(){ return [ 'TagsEditor(', this.model + '', ')' ].join(''); }\n});\n\n// =============================================================================\nreturn {\n TagsEditor : TagsEditor\n};\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/tag.js\n ** module id = 77\n ** module chunks = 3\n **/","define([\n \"utils/localization\"\n], function( _l ){\n'use strict';\n\n//TODO: toastr is another possibility - I didn't see where I might add details, tho\n\n/* ============================================================================\nError modals meant to replace the o-so-easy alerts.\n\nThese are currently styled as errormessages but use the Galaxy.modal\ninfrastructure to be shown/closed. They're capable of showing details in a\ntogglable dropdown and the details are formatted in a pre.\n\nExample:\n errorModal( 'Heres a message', 'A Title', { some_details: 'here' });\n errorModal( 'Heres a message' ); // no details, title is 'Error'\n\nThere are three specialized forms:\n offlineErrorModal a canned response for when there's no connection\n badGatewayErrorModal canned response for when Galaxy is restarting\n ajaxErrorModal plugable into any Backbone class as an\n error event handler by accepting the error args: model, xhr, options\n\nExamples:\n if( navigator.offLine ){ offlineErrorModal(); }\n if( xhr.status === 502 ){ badGatewayErrorModal(); }\n this.listenTo( this.model, 'error', ajaxErrorModal );\n\n============================================================================ */\n\nvar CONTACT_MSG = _l( 'Please contact a Galaxy administrator if the problem persists.' );\nvar DEFAULT_AJAX_ERR_MSG = _l( 'An error occurred while updating information with the server.' );\nvar DETAILS_MSG = _l( 'The following information can assist the developers in finding the source of the error:' );\n\n/** private helper that builds the modal and handles adding details */\nfunction _errorModal( message, title, details ){\n // create and return the modal, adding details button only if needed\n Galaxy.modal.show({\n title : title,\n body : message,\n closing_events : true,\n buttons : { Ok: function(){ Galaxy.modal.hide(); } },\n });\n Galaxy.modal.$el.addClass( 'error-modal' );\n\n if( details ){\n Galaxy.modal.$( '.error-details' ).add( Galaxy.modal.$( 'button:contains(\"Details\")' ) ).remove();\n $( '
                                  ' ).addClass( 'error-details' )\n .hide().appendTo( Galaxy.modal.$( '.modal-content' ) )\n .append([\n $( '

                                  ' ).text( DETAILS_MSG ),\n $( '

                                  ' ).text( JSON.stringify( details, null, '  ' ) )\n            ]);\n\n        $( '' )\n            .appendTo( Galaxy.modal.$( '.buttons' ) )\n            .click( function(){ Galaxy.modal.$( '.error-details' ).toggle(); });\n    }\n    return Galaxy.modal;\n}\n\n/** Display a modal showing an error message but fallback to alert if there's no modal */\nfunction errorModal( message, title, details ){\n    if( !message ){ return; }\n\n    message = _l( message );\n    title = _l( title ) || _l( 'Error:' );\n    if( window.Galaxy && Galaxy.modal ){\n        return _errorModal( message, title, details );\n    }\n\n    alert( title + '\\n\\n' + message );\n    console.log( 'error details:', JSON.stringify( details ) );\n}\n\n\n// ----------------------------------------------------------------------------\n/** display a modal when the user may be offline */\nfunction offlineErrorModal(){\n    return errorModal(\n        _l( 'You appear to be offline. Please check your connection and try again.' ),\n        _l( 'Offline?' )\n    );\n}\n\n\n// ----------------------------------------------------------------------------\n/** 502 messages that should be displayed when galaxy is restarting */\nfunction badGatewayErrorModal(){\n    return errorModal(\n        _l( 'Galaxy is currently unreachable. Please try again in a few minutes.' ) + ' ' + CONTACT_MSG,\n        _l( 'Cannot connect to Galaxy' )\n    );\n}\n\n\n// ----------------------------------------------------------------------------\n/** display a modal (with details) about a failed Backbone ajax operation */\nfunction ajaxErrorModal( model, xhr, options, message, title ){\n    message = message || DEFAULT_AJAX_ERR_MSG;\n    message += ' ' + CONTACT_MSG;\n    title = title || _l( 'An error occurred' );\n    var details = _ajaxDetails( model, xhr, options );\n    return errorModal( message, title, details );\n}\n\n/** build details which may help debugging the ajax call */\nfunction _ajaxDetails( model, xhr, options ){\n    return {\n//TODO: still can't manage Raven id\n        raven       : _.result( window.Raven, 'lastEventId' ),\n        userAgent   : navigator.userAgent,\n        onLine      : navigator.onLine,\n        version     : _.result( Galaxy.config, 'version_major' ),\n        xhr         : _.omit( xhr, _.functions( xhr ) ),\n        options     : _.omit( options, 'xhr' ),\n        // add ajax data from Galaxy object cache\n        url         : _.result( Galaxy.lastAjax, 'url' ),\n        data        : _.result( Galaxy.lastAjax, 'data' ),\n        // backbone stuff (auto-redacting email for user)\n        model       : _.result( model, 'toJSON' , model + '' ),\n        user        : _.omit( _.result( Galaxy.user, 'toJSON' ), 'email' ),\n    };\n}\n\n\n//=============================================================================\n    return {\n        errorModal          : errorModal,\n        offlineErrorModal   : offlineErrorModal,\n        badGatewayErrorModal: badGatewayErrorModal,\n        ajaxErrorModal      : ajaxErrorModal\n    };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/ui/error-modal.js\n ** module id = 78\n ** module chunks = 3\n **/","define([\n    //jquery\n    //backbone\n], function(){\n// =============================================================================\n/**\n * view for a popup menu\n */\nvar PopupMenu = Backbone.View.extend({\n//TODO: maybe better as singleton off the Galaxy obj\n    /** Cache the desired button element and options, set up the button click handler\n     *  NOTE: attaches this view as HTML/jQ data on the button for later use.\n     */\n    initialize: function( $button, options ){\n        // default settings\n        this.$button = $button;\n        if( !this.$button.length ){\n            this.$button = $( '
                                  ' );\n }\n this.options = options || [];\n this.$button.data( 'popupmenu', this );\n\n // set up button click -> open menu behavior\n var menu = this;\n this.$button.click( function( event ){\n // if there's already a menu open, remove it\n $( '.popmenu-wrapper' ).remove();\n menu._renderAndShow( event );\n return false;\n });\n },\n\n // render the menu, append to the page body at the click position, and set up the 'click-away' handlers, show\n _renderAndShow: function( clickEvent ){\n this.render();\n this.$el.appendTo( 'body' ).css( this._getShownPosition( clickEvent )).show();\n this._setUpCloseBehavior();\n },\n\n // render the menu\n // this menu doesn't attach itself to the DOM ( see _renderAndShow )\n render: function(){\n // render the menu body absolute and hidden, fill with template\n this.$el.addClass( 'popmenu-wrapper' ).hide()\n .css({ position : 'absolute' })\n .html( this.template( this.$button.attr( 'id' ), this.options ));\n\n // set up behavior on each link/anchor elem\n if( this.options.length ){\n var menu = this;\n //precondition: there should be one option per li\n this.$el.find( 'li' ).each( function( i, li ){\n var option = menu.options[i];\n\n // if the option has 'func', call that function when the anchor is clicked\n if( option.func ){\n $( this ).children( 'a.popupmenu-option' ).click( function( event ){\n option.func.call( menu, event, option );\n // We must preventDefault otherwise clicking \"cancel\"\n // on a purge or something still navigates and causes\n // the action.\n event.preventDefault();\n // bubble up so that an option click will call the close behavior\n });\n }\n });\n }\n return this;\n },\n\n template : function( id, options ){\n return [\n '
                                    ', this._templateOptions( options ), '
                                  '\n ].join( '' );\n },\n\n _templateOptions : function( options ){\n if( !options.length ){\n return '
                                • (no options)
                                • ';\n }\n return _.map( options, function( option ){\n if( option.divider ){\n return '
                                • ';\n } else if( option.header ){\n return [ '
                                • ', option.html, '
                                • ' ].join( '' );\n }\n var href = option.href || 'javascript:void(0);',\n target = ( option.target )?( ' target=\"' + option.target + '\"' ):( '' ),\n check = ( option.checked )?( '' ):( '' );\n return [\n '
                                • ',\n check, option.html,\n '
                                • '\n ].join( '' );\n }).join( '' );\n },\n\n // get the absolute position/offset for the menu\n _getShownPosition : function( clickEvent ){\n\n // display menu horiz. centered on click...\n var menuWidth = this.$el.width();\n var x = clickEvent.pageX - menuWidth / 2 ;\n\n // adjust to handle horiz. scroll and window dimensions ( draw entirely on visible screen area )\n x = Math.min( x, $( document ).scrollLeft() + $( window ).width() - menuWidth - 5 );\n x = Math.max( x, $( document ).scrollLeft() + 5 );\n return {\n top: clickEvent.pageY,\n left: x\n };\n },\n\n // bind an event handler to all available frames so that when anything is clicked\n // the menu is removed from the DOM and the event handler unbinds itself\n _setUpCloseBehavior: function(){\n var menu = this;\n//TODO: alternately: focus hack, blocking overlay, jquery.blockui\n\n // function to close popup and unbind itself\n function closePopup( event ){\n $( document ).off( 'click.close_popup' );\n if( window && window.parent !== window ){\n try {\n $( window.parent.document ).off( \"click.close_popup\" );\n } catch( err ){}\n } else {\n try {\n $( 'iframe#galaxy_main' ).contents().off( \"click.close_popup\" );\n } catch( err ){}\n }\n menu.remove();\n }\n\n $( 'html' ).one( \"click.close_popup\", closePopup );\n if( window && window.parent !== window ){\n try {\n $( window.parent.document ).find( 'html' ).one( \"click.close_popup\", closePopup );\n } catch( err ){}\n } else {\n try {\n $( 'iframe#galaxy_main' ).contents().one( \"click.close_popup\", closePopup );\n } catch( err ){}\n }\n },\n\n // add a menu option/item at the given index\n addItem: function( item, index ){\n // append to end if no index\n index = ( index >= 0 ) ? index : this.options.length;\n this.options.splice( index, 0, item );\n return this;\n },\n\n // remove a menu option/item at the given index\n removeItem: function( index ){\n if( index >=0 ){\n this.options.splice( index, 1 );\n }\n return this;\n },\n\n // search for a menu option by its html\n findIndexByHtml: function( html ){\n for( var i = 0; i < this.options.length; i++ ){\n if( _.has( this.options[i], 'html' ) && ( this.options[i].html === html )){\n return i;\n }\n }\n return null;\n },\n\n // search for a menu option by its html\n findItemByHtml: function( html ){\n return this.options[( this.findIndexByHtml( html ))];\n },\n\n // string representation\n toString: function(){\n return 'PopupMenu';\n }\n});\n/** shortcut to new for when you don't need to preserve the ref */\nPopupMenu.create = function _create( $button, options ){\n return new PopupMenu( $button, options );\n};\n\n// -----------------------------------------------------------------------------\n// the following class functions are bridges from the original make_popupmenu and make_popup_menus\n// to the newer backbone.js PopupMenu\n\n/** Create a PopupMenu from simple map initial_options activated by clicking button_element.\n * Converts initial_options to object array used by PopupMenu.\n * @param {jQuery|DOMElement} button_element element which, when clicked, activates menu\n * @param {Object} initial_options map of key -> values, where\n * key is option text, value is fn to call when option is clicked\n * @returns {PopupMenu} the PopupMenu created\n */\nPopupMenu.make_popupmenu = function( button_element, initial_options ){\n var convertedOptions = [];\n _.each( initial_options, function( optionVal, optionKey ){\n var newOption = { html: optionKey };\n\n // keys with null values indicate: header\n if( optionVal === null ){ // !optionVal? (null only?)\n newOption.header = true;\n\n // keys with function values indicate: a menu option\n } else if( jQuery.type( optionVal ) === 'function' ){\n newOption.func = optionVal;\n }\n //TODO:?? any other special optionVals?\n // there was no divider option originally\n convertedOptions.push( newOption );\n });\n return new PopupMenu( $( button_element ), convertedOptions );\n};\n\n/** Find all anchors in $parent (using selector) and covert anchors into a PopupMenu options map.\n * @param {jQuery} $parent the element that contains the links to convert to options\n * @param {String} selector jq selector string to find links\n * @returns {Object[]} the options array to initialize a PopupMenu\n */\n//TODO: lose parent and selector, pass in array of links, use map to return options\nPopupMenu.convertLinksToOptions = function( $parent, selector ){\n $parent = $( $parent );\n selector = selector || 'a';\n var options = [];\n $parent.find( selector ).each( function( elem, i ){\n var option = {}, $link = $( elem );\n\n // convert link text to the option text (html) and the href into the option func\n option.html = $link.text();\n if( $link.attr( 'href' ) ){\n var linkHref = $link.attr( 'href' ),\n linkTarget = $link.attr( 'target' ),\n confirmText = $link.attr( 'confirm' );\n\n option.func = function(){\n // if there's a \"confirm\" attribute, throw up a confirmation dialog, and\n // if the user cancels - do nothing\n if( ( confirmText ) && ( !confirm( confirmText ) ) ){ return; }\n\n // if there's no confirm attribute, or the user accepted the confirm dialog:\n switch( linkTarget ){\n // relocate the center panel\n case '_parent':\n window.parent.location = linkHref;\n break;\n\n // relocate the entire window\n case '_top':\n window.top.location = linkHref;\n break;\n\n // relocate this panel\n default:\n window.location = linkHref;\n }\n };\n }\n options.push( option );\n });\n return options;\n};\n\n/** Create a single popupmenu from existing DOM button and anchor elements\n * @param {jQuery} $buttonElement the element that when clicked will open the menu\n * @param {jQuery} $menuElement the element that contains the anchors to convert into a menu\n * @param {String} menuElementLinkSelector jq selector string used to find anchors to be made into menu options\n * @returns {PopupMenu} the PopupMenu (Backbone View) that can render, control the menu\n */\nPopupMenu.fromExistingDom = function( $buttonElement, $menuElement, menuElementLinkSelector ){\n $buttonElement = $( $buttonElement );\n $menuElement = $( $menuElement );\n var options = PopupMenu.convertLinksToOptions( $menuElement, menuElementLinkSelector );\n // we're done with the menu (having converted it to an options map)\n $menuElement.remove();\n return new PopupMenu( $buttonElement, options );\n};\n\n/** Create all popupmenus within a document or a more specific element\n * @param {DOMElement} parent the DOM element in which to search for popupmenus to build (defaults to document)\n * @param {String} menuSelector jq selector string to find popupmenu menu elements (defaults to \"div[popupmenu]\")\n * @param {Function} buttonSelectorBuildFn the function to build the jq button selector.\n * Will be passed $menuElement, parent.\n * (Defaults to return '#' + $menuElement.attr( 'popupmenu' ); )\n * @returns {PopupMenu[]} array of popupmenus created\n */\nPopupMenu.make_popup_menus = function( parent, menuSelector, buttonSelectorBuildFn ){\n parent = parent || document;\n // orig. Glx popupmenu menus have a (non-std) attribute 'popupmenu'\n // which contains the id of the button that activates the menu\n menuSelector = menuSelector || 'div[popupmenu]';\n // default to (orig. Glx) matching button to menu by using the popupmenu attr of the menu as the id of the button\n buttonSelectorBuildFn = buttonSelectorBuildFn || function( $menuElement, parent ){\n return '#' + $menuElement.attr( 'popupmenu' );\n };\n\n // aggregate and return all PopupMenus\n var popupMenusCreated = [];\n $( parent ).find( menuSelector ).each( function(){\n var $menuElement = $( this ),\n $buttonElement = $( parent ).find( buttonSelectorBuildFn( $menuElement, parent ) );\n popupMenusCreated.push( PopupMenu.fromDom( $buttonElement, $menuElement ) );\n $buttonElement.addClass( 'popup' );\n });\n return popupMenusCreated;\n};\n\n\n// =============================================================================\n return PopupMenu;\n});\n\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/ui/popup-menu.js\n ** module id = 79\n ** module chunks = 3\n **/","/** This renders the content of the ftp popup **/\ndefine( [ 'utils/utils' ], function( Utils ) {\n return Backbone.View.extend({\n initialize: function( options ) {\n var self = this;\n this.options = Utils.merge( options, {\n class_add : 'upload-icon-button fa fa-square-o',\n class_remove : 'upload-icon-button fa fa-check-square-o',\n class_partial : 'upload-icon-button fa fa-minus-square-o',\n collection : null,\n onchange : function() {},\n onadd : function() {},\n onremove : function() {}\n } );\n this.collection = this.options.collection;\n this.setElement( this._template() );\n this.rows = [];\n Utils.get({\n url : Galaxy.root + 'api/remote_files',\n success : function( ftp_files ) { self._fill( ftp_files ) },\n error : function() { self._fill(); }\n });\n },\n\n /** Fill table with ftp entries */\n _fill: function( ftp_files ) {\n if ( ftp_files && ftp_files.length > 0 ) {\n this.$( '.upload-ftp-content' ).html( $( this._templateTable() ) );\n var size = 0;\n for ( index in ftp_files ) {\n this.rows.push( this._add( ftp_files[ index ] ) );\n size += ftp_files[ index ].size;\n }\n this.$( '.upload-ftp-number' ).html( ftp_files.length + ' files' );\n this.$( '.upload-ftp-disk' ).html( Utils.bytesToString ( size, true ) );\n if ( this.collection ) {\n var self = this;\n this.$( '._has_collection' ).show();\n this.$select_all = this.$( '.upload-selectall' ).addClass( this.options.class_add );\n this.$select_all.on( 'click', function() {\n var add = self.$select_all.hasClass( self.options.class_add );\n for ( index in ftp_files ) {\n var ftp_file = ftp_files[ index ];\n var model_index = self._find( ftp_file );\n if( !model_index && add || model_index && !add ) {\n self.rows[ index ].trigger( 'click' );\n }\n }\n });\n this._refresh();\n }\n } else {\n this.$( '.upload-ftp-content' ).html( $( this._templateInfo() ) );\n }\n this.$( '.upload-ftp-wait' ).hide();\n },\n\n /** Add file to table */\n _add: function( ftp_file ) {\n var self = this;\n var $it = $( this._templateRow( ftp_file ) );\n var $icon = $it.find( '.icon' );\n this.$( 'tbody' ).append( $it );\n if ( this.collection ) {\n $icon.addClass( this._find( ftp_file ) ? this.options.class_remove : this.options.class_add );\n $it.on('click', function() {\n var model_index = self._find( ftp_file );\n $icon.removeClass();\n if ( !model_index ) {\n self.options.onadd( ftp_file );\n $icon.addClass( self.options.class_remove );\n } else {\n self.options.onremove( model_index );\n $icon.addClass( self.options.class_add );\n }\n self._refresh();\n });\n } else {\n $it.on('click', function() { self.options.onchange( ftp_file ) } );\n }\n return $it;\n },\n\n /** Refresh select all button state */\n _refresh: function() {\n var filtered = this.collection.where( { file_mode: 'ftp', enabled: true } );\n this.$select_all.removeClass();\n if ( filtered.length == 0 ) {\n this.$select_all.addClass( this.options.class_add );\n } else {\n this.$select_all.addClass( filtered.length == this.rows.length ? this.options.class_remove : this.options.class_partial );\n }\n },\n\n /** Get model index */\n _find: function( ftp_file ) {\n var item = this.collection.findWhere({\n file_path : ftp_file.path,\n file_mode : 'ftp',\n enabled : true\n });\n return item && item.get('id');\n },\n\n /** Template of row */\n _templateRow: function( options ) {\n return '' +\n '
                                  ' +\n '' + options.path + '' +\n '' + Utils.bytesToString( options.size ) + '' +\n '' + options.ctime + '' +\n '';\n },\n\n /** Template of table */\n _templateTable: function() {\n return 'Available files: ' +\n '' +\n '' +\n '  ' +\n '' +\n '' +\n '' +\n '' +\n '' +\n '' +\n '' +\n '' +\n '' +\n '' +\n '' +\n '' +\n '' +\n '
                                  NameSizeCreated
                                  ';\n },\n\n /** Template of info message */\n _templateInfo: function() {\n return '
                                  ' +\n 'Your FTP directory does not contain any files.' +\n '
                                  ';\n },\n\n /** Template of main view */\n _template: function() {\n return '
                                  ' +\n '
                                  ' +\n '
                                  This Galaxy server allows you to upload files via FTP. To upload some files, log in to the FTP server at ' + this.options.ftp_upload_site + ' using your Galaxy credentials (email address and password).
                                  ' +\n '
                                  ' +\n '
                                  ';\n }\n });\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/upload/upload-ftp.js\n ** module id = 80\n ** module chunks = 3\n **/","/** This renders the content of the settings popup, allowing users to specify flags i.e. for space-to-tab conversion **/\ndefine( [ 'utils/utils' ], function( Utils ) {\n return Backbone.View.extend({\n options: {\n class_check : 'fa-check-square-o',\n class_uncheck : 'fa-square-o',\n parameters : [{\n id : 'space_to_tab',\n title : 'Convert spaces to tabs',\n },{\n id : 'to_posix_lines',\n title : 'Use POSIX standard'\n }]\n },\n\n initialize: function( options ) {\n var self = this;\n this.model = options.model;\n this.setElement( $( '
                                  ' ).addClass( 'upload-settings' ) );\n this.$el.append( $( '
                                  ' ).addClass( 'upload-settings-cover' ) );\n this.$el.append( $( '' ).addClass( 'upload-settings-table ui-table-striped' ).append( '' ) );\n this.$cover = this.$( '.upload-settings-cover' );\n this.$table = this.$( '.upload-settings-table > tbody' );\n this.listenTo ( this.model, 'change', this.render, this );\n this.model.trigger( 'change' );\n },\n\n render: function() {\n var self = this;\n this.$table.empty();\n _.each( this.options.parameters, function( parameter ) {\n var $checkbox = $( '
                                  ' ).addClass( 'upload-' + parameter.id + ' upload-icon-button fa' )\n .addClass( self.model.get( parameter.id ) && self.options.class_check || self.options.class_uncheck )\n .on( 'click', function() {\n self.model.get( 'enabled' ) && self.model.set( parameter.id, !self.model.get( parameter.id ) )\n });\n self.$table.append( $( '
                                  ' ).append( $( '' +\n '-1&&(f=p.split("."),p=f.shift(),f.sort()),o=p.indexOf(":")<0&&"on"+p,t=t[vt.expando]?t:new vt.Event(p,"object"==typeof t&&t),t.isTrigger=r?2:3,t.namespace=f.join("."),t.rnamespace=t.namespace?new RegExp("(^|\\.)"+f.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,t.result=void 0,t.target||(t.target=n),e=null==e?[t]:vt.makeArray(e,[t]),u=vt.event.special[p]||{},r||!u.trigger||u.trigger.apply(n,e)!==!1)){if(!r&&!u.noBubble&&!vt.isWindow(n)){for(l=u.delegateType||p,ee.test(l+p)||(a=a.parentNode);a;a=a.parentNode)d.push(a),h=a;h===(n.ownerDocument||at)&&d.push(h.defaultView||h.parentWindow||i)}for(c=0;(a=d[c++])&&!t.isPropagationStopped();)t.type=c>1?l:u.bindType||p,s=(vt._data(a,"events")||{})[t.type]&&vt._data(a,"handle"),s&&s.apply(a,e),s=o&&a[o],s&&s.apply&&Lt(a)&&(t.result=s.apply(a,e),t.result===!1&&t.preventDefault());if(t.type=p,!r&&!t.isDefaultPrevented()&&(!u._default||u._default.apply(d.pop(),e)===!1)&&Lt(n)&&o&&n[p]&&!vt.isWindow(n)){h=n[o],h&&(n[o]=null),vt.event.triggered=p;try{n[p]()}catch(g){}vt.event.triggered=void 0,h&&(n[o]=h)}return t.result}},dispatch:function(t){t=vt.event.fix(t);var e,i,n,r,s,o=[],a=lt.call(arguments),l=(vt._data(this,"events")||{})[t.type]||[],u=vt.event.special[t.type]||{};if(a[0]=t,t.delegateTarget=this,!u.preDispatch||u.preDispatch.call(this,t)!==!1){for(o=vt.event.handlers.call(this,t,l),e=0;(r=o[e++])&&!t.isPropagationStopped();)for(t.currentTarget=r.elem,i=0;(s=r.handlers[i++])&&!t.isImmediatePropagationStopped();)(!t.rnamespace||t.rnamespace.test(s.namespace))&&(t.handleObj=s,t.data=s.data,n=((vt.event.special[s.origType]||{}).handle||s.handler).apply(r.elem,a),void 0!==n&&(t.result=n)===!1&&(t.preventDefault(),t.stopPropagation()));return u.postDispatch&&u.postDispatch.call(this,t),t.result}},handlers:function(t,e){var i,n,r,s,o=[],a=e.delegateCount,l=t.target;if(a&&l.nodeType&&("click"!==t.type||isNaN(t.button)||t.button<1))for(;l!=this;l=l.parentNode||this)if(1===l.nodeType&&(l.disabled!==!0||"click"!==t.type)){for(n=[],i=0;a>i;i++)s=e[i],r=s.selector+" ",void 0===n[r]&&(n[r]=s.needsContext?vt(r,this).index(l)>-1:vt.find(r,this,null,[l]).length),n[r]&&n.push(s);n.length&&o.push({elem:l,handlers:n})}return a]","i"),se=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi,oe=/\s*$/g,he=v(at),ce=he.appendChild(at.createElement("div"));vt.extend({htmlPrefilter:function(t){return t.replace(se,"<$1>")},clone:function(t,e,i){var n,r,s,o,a,l=vt.contains(t.ownerDocument,t);if(gt.html5Clone||vt.isXMLDoc(t)||!re.test("<"+t.nodeName+">")?s=t.cloneNode(!0):(ce.innerHTML=t.outerHTML,ce.removeChild(s=ce.firstChild)),!(gt.noCloneEvent&>.noCloneChecked||1!==t.nodeType&&11!==t.nodeType||vt.isXMLDoc(t)))for(n=y(s),a=y(t),o=0;null!=(r=a[o]);++o)n[o]&&O(r,n[o]);if(e)if(i)for(a=a||y(t),n=n||y(s),o=0;null!=(r=a[o]);o++)N(r,n[o]);else N(t,s);return n=y(s,"script"),n.length>0&&b(n,!l&&y(t,"script")),n=a=r=null,s},cleanData:function(t,e){for(var i,n,r,s,o=0,a=vt.expando,l=vt.cache,u=gt.attributes,h=vt.event.special;null!=(i=t[o]);o++)if((e||Lt(i))&&(r=i[a],s=r&&l[r])){if(s.events)for(n in s.events)h[n]?vt.event.remove(i,n):vt.removeEvent(i,n,s.handle);l[r]&&(delete l[r],u||"undefined"==typeof i.removeAttribute?i[a]=void 0:i.removeAttribute(a),ot.push(r))}}}),vt.fn.extend({domManip:M,detach:function(t){return D(this,t,!0)},remove:function(t){return D(this,t)},text:function(t){return zt(this,function(t){return void 0===t?vt.text(this):this.empty().append((this[0]&&this[0].ownerDocument||at).createTextNode(t))},null,t,arguments.length)},append:function(){return M(this,arguments,function(t){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var e=T(this,t);e.appendChild(t)}})},prepend:function(){return M(this,arguments,function(t){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var e=T(this,t);e.insertBefore(t,e.firstChild)}})},before:function(){return M(this,arguments,function(t){this.parentNode&&this.parentNode.insertBefore(t,this)})},after:function(){return M(this,arguments,function(t){this.parentNode&&this.parentNode.insertBefore(t,this.nextSibling)})},empty:function(){for(var t,e=0;null!=(t=this[e]);e++){for(1===t.nodeType&&vt.cleanData(y(t,!1));t.firstChild;)t.removeChild(t.firstChild);t.options&&vt.nodeName(t,"select")&&(t.options.length=0)}return this},clone:function(t,e){return t=null==t?!1:t,e=null==e?t:e,this.map(function(){return vt.clone(this,t,e)})},html:function(t){return zt(this,function(t){var e=this[0]||{},i=0,n=this.length;if(void 0===t)return 1===e.nodeType?e.innerHTML.replace(ne,""):void 0;if("string"==typeof t&&!oe.test(t)&&(gt.htmlSerialize||!re.test(t))&&(gt.leadingWhitespace||!Gt.test(t))&&!Yt[(Ut.exec(t)||["",""])[1].toLowerCase()]){t=vt.htmlPrefilter(t);try{for(;n>i;i++)e=this[i]||{},1===e.nodeType&&(vt.cleanData(y(e,!1)),e.innerHTML=t);e=0}catch(r){}}e&&this.empty().append(t)},null,t,arguments.length)},replaceWith:function(){var t=[];return M(this,arguments,function(e){var i=this.parentNode;vt.inArray(this,t)<0&&(vt.cleanData(y(this)),i&&i.replaceChild(e,this))},t)}}),vt.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(t,e){vt.fn[t]=function(t){for(var i,n=0,r=[],s=vt(t),o=s.length-1;o>=n;n++)i=n===o?this:this.clone(!0),vt(s[n])[e](i),ht.apply(r,i.get());return this.pushStack(r)}});var de,pe={HTML:"block",BODY:"block"},fe=/^margin/,ge=new RegExp("^("+$t+")(?!px)[a-z%]+$","i"),me=function(t,e,i,n){var r,s,o={};for(s in e)o[s]=t.style[s],t.style[s]=e[s];r=i.apply(t,n||[]);for(s in e)t.style[s]=o[s];return r},ve=at.documentElement;!function(){function t(){var t,h,c=at.documentElement;c.appendChild(l),u.style.cssText="-webkit-box-sizing:border-box;box-sizing:border-box;position:relative;display:block;margin:auto;border:1px;padding:1px;top:1%;width:50%",e=r=a=!1,n=o=!0,i.getComputedStyle&&(h=i.getComputedStyle(u),e="1%"!==(h||{}).top,a="2px"===(h||{}).marginLeft,r="4px"===(h||{width:"4px"}).width,u.style.marginRight="50%",n="4px"===(h||{marginRight:"4px"}).marginRight,t=u.appendChild(at.createElement("div")),t.style.cssText=u.style.cssText="-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;display:block;margin:0;border:0;padding:0",t.style.marginRight=t.style.width="0",u.style.width="1px",o=!parseFloat((i.getComputedStyle(t)||{}).marginRight),u.removeChild(t)),u.style.display="none",s=0===u.getClientRects().length,s&&(u.style.display="",u.innerHTML="
                                  ' ).append( $checkbox ) )\n .append( $( '' ).append( parameter.title ) ) )\n });\n this.$cover[ this.model.get( 'enabled' ) && 'hide' || 'show' ]();\n }\n });\n});\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/upload/upload-settings.js\n ** module id = 81\n ** module chunks = 3\n **/","(function (factory) {\n if (typeof define === 'function' && define.amd) {\n define([], factory);\n } else {\n // Browser globals\n factory(jQuery);\n }\n\n}(function () {\n//=============================================================================\n\n jQuery.fn.extend({\n hoverhighlight : function $hoverhighlight( scope, color ){\n scope = scope || 'body';\n if( !this.length ){ return this; }\n\n $( this ).each( function(){\n var $this = $( this ),\n targetSelector = $this.data( 'target' );\n\n if( targetSelector ){\n $this.mouseover( function( ev ){\n $( targetSelector, scope ).css({\n background: color\n });\n })\n .mouseout( function( ev ){\n $( targetSelector ).css({\n background: ''\n });\n });\n }\n });\n return this;\n }\n });\n}));\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/ui/hoverhighlight.js\n ** module id = 84\n ** module chunks = 3\n **/","// from: https://raw.githubusercontent.com/umdjs/umd/master/jqueryPlugin.js\n// Uses AMD or browser globals to create a jQuery plugin.\n(function (factory) {\n if (typeof define === 'function' && define.amd) {\n //TODO: So...this turns out to be an all or nothing thing. If I load jQuery in the define below, it will\n // (of course) wipe the old jquery *and all the plugins loaded into it*. So the define below *is still\n // relying on jquery being loaded globally* in order to preserve plugins.\n define([], factory);\n } else {\n // Browser globals\n factory(jQuery);\n }\n\n}(function () {\n var _l = window._l || function( s ){ return s; };\n\n //TODO: consolidate with tool menu functionality, use there\n\n /** searchInput: (jQuery plugin)\n * Creates a search input, a clear button, and loading indicator\n * within the selected node.\n *\n * When the user either presses return or enters some minimal number\n * of characters, a callback is called. Pressing ESC when the input\n * is focused will clear the input and call a separate callback.\n */\n function searchInput( parentNode, options ){\n var KEYCODE_ESC = 27,\n KEYCODE_RETURN = 13,\n $parentNode = $( parentNode ),\n firstSearch = true,\n defaults = {\n initialVal : '',\n name : 'search',\n placeholder : 'search',\n classes : '',\n onclear : function(){},\n onfirstsearch : null,\n onsearch : function( inputVal ){},\n minSearchLen : 0,\n escWillClear : true,\n oninit : function(){}\n };\n\n // .................................................................... input rendering and events\n // visually clear the search, trigger an event, and call the callback\n function clearSearchInput( event ){\n var $input = $( this ).parent().children( 'input' );\n $input.val( '' ).trigger( 'searchInput.clear' ).blur();\n options.onclear();\n }\n\n // search for searchTerms, trigger an event, call the appropo callback (based on whether this is the first)\n function search( event, searchTerms ){\n if( !searchTerms ){\n return clearSearchInput();\n }\n $( this ).trigger( 'search.search', searchTerms );\n if( typeof options.onfirstsearch === 'function' && firstSearch ){\n firstSearch = false;\n options.onfirstsearch( searchTerms );\n } else {\n options.onsearch( searchTerms );\n }\n }\n\n // .................................................................... input rendering and events\n function inputTemplate(){\n // class search-query is bootstrap 2.3 style that now lives in base.less\n return [ '' ].join( '' );\n }\n\n // the search input that responds to keyboard events and displays the search value\n function $input(){\n return $( inputTemplate() )\n // select all text on a focus\n .focus( function( event ){\n $( this ).select();\n })\n // attach behaviors to esc, return if desired, search on some min len string\n .keyup( function( event ){\n event.preventDefault();\n event.stopPropagation();\n\n // esc key will clear if desired\n if( event.which === KEYCODE_ESC && options.escWillClear ){\n clearSearchInput.call( this, event );\n\n } else {\n var searchTerms = $( this ).val();\n // return key or the search string len > minSearchLen (if not 0) triggers search\n if( ( event.which === KEYCODE_RETURN )\n || ( options.minSearchLen && searchTerms.length >= options.minSearchLen ) ){\n search.call( this, event, searchTerms );\n }\n }\n })\n .val( options.initialVal );\n }\n\n // .................................................................... clear button rendering and events\n // a button for clearing the search bar, placed on the right hand side\n function $clearBtn(){\n return $([ '' ].join('') )\n .tooltip({ placement: 'bottom' })\n .click( function( event ){\n clearSearchInput.call( this, event );\n });\n }\n\n // .................................................................... loadingIndicator rendering\n // a button for clearing the search bar, placed on the right hand side\n function $loadingIndicator(){\n return $([ '' ].join('') )\n .hide().tooltip({ placement: 'bottom' });\n }\n\n // .................................................................... commands\n // visually swap the load, clear buttons\n function toggleLoadingIndicator(){\n $parentNode.find( '.search-loading' ).toggle();\n $parentNode.find( '.search-clear' ).toggle();\n }\n\n // .................................................................... init\n // string command (not constructor)\n if( jQuery.type( options ) === 'string' ){\n if( options === 'toggle-loading' ){\n toggleLoadingIndicator();\n }\n return $parentNode;\n }\n\n // initial render\n if( jQuery.type( options ) === 'object' ){\n options = jQuery.extend( true, {}, defaults, options );\n }\n //NOTE: prepended\n return $parentNode.addClass( 'search-input' ).prepend([ $input(), $clearBtn(), $loadingIndicator() ]);\n }\n\n // as jq plugin\n jQuery.fn.extend({\n searchInput : function $searchInput( options ){\n return this.each( function(){\n return searchInput( this, options );\n });\n }\n });\n}));\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/ui/search-input.js\n ** module id = 85\n ** module chunks = 3\n **/","define([], function(){\n// Alphanumeric/natural sort fn\nfunction naturalSort(a, b) {\n // setup temp-scope variables for comparison evauluation\n var re = /(-?[0-9\\.]+)/g,\n x = a.toString().toLowerCase() || '',\n y = b.toString().toLowerCase() || '',\n nC = String.fromCharCode(0),\n xN = x.replace( re, nC + '$1' + nC ).split(nC),\n yN = y.replace( re, nC + '$1' + nC ).split(nC),\n xD = (new Date(x)).getTime(),\n yD = xD ? (new Date(y)).getTime() : null;\n // natural sorting of dates\n if ( yD ) {\n if ( xD < yD ) { return -1; }\n else if ( xD > yD ) { return 1; }\n }\n // natural sorting through split numeric strings and default strings\n var oFxNcL, oFyNcL;\n for ( var cLoc = 0, numS = Math.max(xN.length, yN.length); cLoc < numS; cLoc++ ) {\n oFxNcL = parseFloat(xN[cLoc]) || xN[cLoc];\n oFyNcL = parseFloat(yN[cLoc]) || yN[cLoc];\n if (oFxNcL < oFyNcL) { return -1; }\n else if (oFxNcL > oFyNcL) { return 1; }\n }\n return 0;\n}\n\nreturn naturalSort;\n})\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/utils/natural-sort.js\n ** module id = 87\n ** module chunks = 3\n **/","/*\n galaxy upload plugins - requires FormData and XMLHttpRequest\n*/\n;(function($){\n // add event properties\n jQuery.event.props.push(\"dataTransfer\");\n\n /**\n Posts file data to the API\n */\n $.uploadpost = function (config) {\n // parse options\n var cnf = $.extend({}, {\n data : {},\n success : function() {},\n error : function() {},\n progress : function() {},\n url : null,\n maxfilesize : 2048,\n error_filesize : 'File exceeds 2GB. Please use a FTP client.',\n error_default : 'Please make sure the file is available.',\n error_server : 'Upload request failed.',\n error_login : 'Uploads require you to log in.'\n }, config);\n\n // link data\n var data = cnf.data;\n\n // check errors\n if (data.error_message) {\n cnf.error(data.error_message);\n return;\n }\n\n // construct form data\n var form = new FormData();\n for (var key in data.payload) {\n form.append(key, data.payload[key]);\n }\n\n // add files to submission\n var sizes = 0;\n for (var key in data.files) {\n var d = data.files[key];\n form.append(d.name, d.file, d.file.name);\n sizes += d.file.size;\n }\n\n // check file size, unless it's an ftp file\n if (sizes > 1048576 * cnf.maxfilesize) {\n cnf.error(cnf.error_filesize);\n return;\n }\n\n // prepare request\n xhr = new XMLHttpRequest();\n xhr.open('POST', cnf.url, true);\n xhr.setRequestHeader('Accept', 'application/json');\n xhr.setRequestHeader('Cache-Control', 'no-cache');\n xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');\n\n // captures state changes\n xhr.onreadystatechange = function() {\n // check for request completed, server connection closed\n if (xhr.readyState == xhr.DONE) {\n // parse response\n var response = null;\n if (xhr.responseText) {\n try {\n response = jQuery.parseJSON(xhr.responseText);\n } catch (e) {\n response = xhr.responseText;\n }\n }\n // pass any error to the error option\n if (xhr.status < 200 || xhr.status > 299) {\n var text = xhr.statusText;\n if (xhr.status == 403) {\n text = cnf.error_login;\n } else if (xhr.status == 0) {\n text = cnf.error_server;\n } else if (!text) {\n text = cnf.error_default;\n }\n cnf.error(text + ' (' + xhr.status + ')');\n } else {\n cnf.success(response);\n }\n }\n }\n\n // prepare upload progress\n xhr.upload.addEventListener('progress', function(e) {\n if (e.lengthComputable) {\n cnf.progress(Math.round((e.loaded * 100) / e.total));\n }\n }, false);\n\n // send request\n Galaxy.emit.debug('uploadbox::uploadpost()', 'Posting following data.', cnf);\n xhr.send(form);\n }\n\n /**\n Handles the upload events drag/drop etc.\n */\n $.fn.uploadinput = function(options) {\n // initialize\n var el = this;\n var opts = $.extend({}, {\n ondragover : function() {},\n ondragleave : function() {},\n onchange : function() {},\n multiple : false\n }, options);\n\n // append hidden upload field\n var $input = $('');\n el.append($input.change(function (e) {\n opts.onchange(e.target.files);\n $(this).val('');\n }));\n\n // drag/drop events\n el.on('drop', function (e) {\n opts.ondragleave(e);\n if(e.dataTransfer) {\n opts.onchange(e.dataTransfer.files);\n e.preventDefault();\n }\n });\n el.on('dragover', function (e) {\n e.preventDefault();\n opts.ondragover(e);\n });\n el.on('dragleave', function (e) {\n e.stopPropagation();\n opts.ondragleave(e);\n });\n\n // exports\n return {\n dialog: function () {\n $input.trigger('click');\n }\n }\n }\n\n /**\n Handles the upload queue and events such as drag/drop etc.\n */\n $.fn.uploadbox = function(options) {\n // parse options\n var opts = $.extend({}, {\n dragover : function() {},\n dragleave : function() {},\n announce : function(d) {},\n initialize : function(d) {},\n progress : function(d, m) {},\n success : function(d, m) {},\n error : function(d, m) { alert(m); },\n complete : function() {}\n }, options);\n\n // file queue\n var queue = {};\n\n // queue index/length counter\n var queue_index = 0;\n var queue_length = 0;\n\n // indicates if queue is currently running\n var queue_running = false;\n var queue_stop = false;\n\n // element\n var uploadinput = $(this).uploadinput({\n multiple : true,\n onchange : function(files) { add(files); },\n ondragover : options.ondragover,\n ondragleave : options.ondragleave\n });\n\n // add new files to upload queue\n function add(files) {\n if (files && files.length && !queue_running) {\n var current_index = queue_index;\n _.each(files, function(file, key) {\n if (file.mode !== 'new' && _.filter(queue, function(f) {\n return f.name === file.name && f.size === file.size;\n }).length) {\n file.duplicate = true;\n }\n });\n _.each(files, function(file) {\n if (!file.duplicate) {\n var index = String(queue_index++);\n queue[index] = file;\n opts.announce(index, queue[index]);\n queue_length++;\n }\n });\n return current_index;\n }\n }\n\n // remove file from queue\n function remove(index) {\n if (queue[index]) {\n delete queue[index];\n queue_length--;\n }\n }\n\n // process an upload, recursive\n function process() {\n // validate\n if (queue_length == 0 || queue_stop) {\n queue_stop = false;\n queue_running = false;\n opts.complete();\n return;\n } else {\n queue_running = true;\n }\n\n // get an identifier from the queue\n var index = -1;\n for (var key in queue) {\n index = key;\n break;\n }\n\n // get current file from queue\n var file = queue[index];\n\n // remove from queue\n remove(index)\n\n // create and submit data\n $.uploadpost({\n url : opts.url,\n data : opts.initialize(index),\n success : function(message) { opts.success(index, message); process();},\n error : function(message) { opts.error(index, message); process();},\n progress : function(percentage) { opts.progress(index, percentage); }\n });\n }\n\n /*\n public interface\n */\n\n // open file browser for selection\n function select() {\n uploadinput.dialog();\n }\n\n // remove all entries from queue\n function reset(index) {\n for (index in queue) {\n remove(index);\n }\n }\n\n // initiate upload process\n function start() {\n if (!queue_running) {\n queue_running = true;\n process();\n }\n }\n\n // stop upload process\n function stop() {\n queue_stop = true;\n }\n\n // set options\n function configure(options) {\n opts = $.extend({}, opts, options);\n return opts;\n }\n\n // verify browser compatibility\n function compatible() {\n return window.File && window.FormData && window.XMLHttpRequest && window.FileList;\n }\n\n // export functions\n return {\n 'select' : select,\n 'add' : add,\n 'remove' : remove,\n 'start' : start,\n 'stop' : stop,\n 'reset' : reset,\n 'configure' : configure,\n 'compatible' : compatible\n };\n }\n})(jQuery);\n\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/utils/uploadbox.js\n ** module id = 88\n ** module chunks = 3\n **/","var RightPanel = require( 'layout/panel' ).RightPanel,\n Ui = require( 'mvc/ui/ui-misc' ),\n historyOptionsMenu = require( 'mvc/history/options-menu' );\n CurrentHistoryView = require( 'mvc/history/history-view-edit-current' ).CurrentHistoryView,\n _l = require( 'utils/localization' );\n\n/** the right hand panel in the analysis page that shows the current history */\nvar HistoryPanel = RightPanel.extend({\n\n title : _l( 'History' ),\n\n initialize : function( options ){\n RightPanel.prototype.initialize.call( this, options );\n this.options = _.pick( options, 'userIsAnonymous', 'allow_user_dataset_purge', 'galaxyRoot' );\n\n // view of the current history\n this.historyView = new CurrentHistoryView({\n className : CurrentHistoryView.prototype.className + ' middle',\n purgeAllowed : options.allow_user_dataset_purge,\n linkTarget : 'galaxy_main'\n });\n },\n\n /** override to change footer selector */\n $toggleButton : function(){\n return this.$( '.footer > .panel-collapse' );\n },\n\n render : function(){\n RightPanel.prototype.render.call( this );\n this.optionsMenu = historyOptionsMenu( this.$( '#history-options-button' ), {\n anonymous : this.options.userIsAnonymous,\n purgeAllowed : this.options.allow_user_dataset_purge,\n root : this.options.galaxyRoot\n });\n this.$( '> .header .buttons [title]' ).tooltip({ placement: 'bottom' });\n this.historyView.setElement( this.$( '.history-panel' ) );\n this.$el.attr( 'class', 'history-right-panel' );\n },\n\n /** override to add buttons */\n _templateHeader: function( data ){\n var historyUrl = this.options.galaxyRoot + 'history';\n var multiUrl = this.options.galaxyRoot + 'history/view_multiple';\n return [\n '
                                  ',\n '
                                  ',\n // this button re-fetches the history and contents and re-renders the history panel\n '',\n // opens a drop down menu with history related functions (like view all, delete, share, etc.)\n '',\n !this.options.userIsAnonymous?\n [ '' ].join('') : '',\n '
                                  ',\n '
                                  ', _.escape( this.title ), '
                                  ',\n '
                                  ',\n ].join('');\n },\n\n /** add history view div */\n _templateBody : function( data ){\n return [\n '
                                  ',\n ].join('');\n },\n\n /** override to use simplified selector */\n _templateFooter: function( data ){\n return [\n '
                                  ',\n '
                                  ',\n '
                                  ',\n '
                                  ',\n ].join('');\n },\n\n events : {\n 'click #history-refresh-button' : '_clickRefresh',\n // override to change footer selector\n 'mousedown .footer > .drag' : '_mousedownDragHandler',\n 'click .footer > .panel-collapse' : 'toggle'\n },\n\n _clickRefresh : function( ev ){\n ev.preventDefault();\n this.historyView.loadCurrentHistory();\n },\n\n toString : function(){ return 'HistoryPanel'; }\n});\n\nmodule.exports = HistoryPanel;\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/apps/history-panel.js\n ** module id = 89\n ** module chunks = 3\n **/","var LeftPanel = require( 'layout/panel' ).LeftPanel,\n Tools = require( 'mvc/tool/tools' ),\n Upload = require( 'mvc/upload/upload-view' ),\n _l = require( 'utils/localization' );\n\n/* Builds the tool menu panel on the left of the analysis page */\nvar ToolPanel = LeftPanel.extend({\n\n title : _l( 'Tools' ),\n\n initialize: function( options ){\n LeftPanel.prototype.initialize.call( this, options );\n this.log( this + '.initialize:', options );\n\n /** @type {Object[]} descriptions of user's workflows to be shown in the tool menu */\n this.stored_workflow_menu_entries = options.stored_workflow_menu_entries || [];\n\n // create tool search, tool panel, and tool panel view.\n var tool_search = new Tools.ToolSearch({\n search_url : options.search_url,\n hidden : false\n });\n var tools = new Tools.ToolCollection( options.toolbox );\n this.tool_panel = new Tools.ToolPanel({\n tool_search : tool_search,\n tools : tools,\n layout : options.toolbox_in_panel\n });\n this.tool_panel_view = new Tools.ToolPanelView({ model: this.tool_panel });\n\n // add upload modal\n this.uploadButton = new Upload({\n nginx_upload_path : options.nginx_upload_path,\n ftp_upload_site : options.ftp_upload_site,\n default_genome : options.default_genome,\n default_extension : options.default_extension,\n });\n },\n\n render : function(){\n var self = this;\n LeftPanel.prototype.render.call( self );\n self.$( '.panel-header-buttons' ).append( self.uploadButton.$el );\n\n // if there are tools, render panel and display everything\n if (self.tool_panel.get( 'layout' ).size() > 0) {\n self.tool_panel_view.render();\n //TODO: why the hide/show?\n self.$( '.toolMenu' ).show();\n }\n self.$( '.toolMenuContainer' ).prepend( self.tool_panel_view.$el );\n\n self._renderWorkflowMenu();\n\n // if a tool link has the minsizehint attribute, handle it here (gen. by hiding the tool panel)\n self.$( 'a[minsizehint]' ).click( function() {\n if ( parent.handle_minwidth_hint ) {\n parent.handle_minwidth_hint( $( self ).attr( 'minsizehint' ) );\n }\n });\n },\n\n /** build the dom for the workflow portion of the tool menu */\n _renderWorkflowMenu : function(){\n var self = this;\n // add internal workflow list\n self.$( '#internal-workflows' ).append( self._templateTool({\n title : _l( 'All workflows' ),\n href : 'workflow/list_for_run'\n }));\n _.each( self.stored_workflow_menu_entries, function( menu_entry ){\n self.$( '#internal-workflows' ).append( self._templateTool({\n title : menu_entry.stored_workflow.name,\n href : 'workflow/run?id=' + menu_entry.encoded_stored_workflow_id\n }));\n });\n },\n\n /** build a link to one tool */\n _templateTool: function( tool ) {\n return [\n '
                                  ',\n // global\n '', tool.title, '',\n '
                                  '\n ].join('');\n },\n\n /** override to include inital menu dom and workflow section */\n _templateBody : function(){\n return [\n '
                                  ',\n '
                                  ',\n '
                                  ',\n '
                                  ',\n '', _l( 'Search did not match any tools.' ), '',\n '
                                  ',\n '
                                  ',\n '
                                  ',\n '
                                  ',\n '
                                  ',\n '', _l( 'Workflows' ), '',\n '
                                  ',\n '
                                  ',\n '
                                  ',\n '
                                  ',\n '
                                  ',\n '
                                  '\n ].join('');\n },\n\n toString : function(){ return 'ToolPanel'; }\n});\n\nmodule.exports = ToolPanel;\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/apps/tool-panel.js\n ** module id = 90\n ** module chunks = 3\n **/","define([\n \"mvc/collection/collection-li\",\n \"mvc/dataset/dataset-li-edit\",\n \"mvc/base-mvc\",\n \"utils/localization\"\n], function( DC_LI, DATASET_LI_EDIT, BASE_MVC, _l ){\n\n'use strict';\n//==============================================================================\nvar DCListItemView = DC_LI.DCListItemView;\n/** @class Edit view for DatasetCollection.\n */\nvar DCListItemEdit = DCListItemView.extend(\n/** @lends DCListItemEdit.prototype */{\n\n /** override to add linkTarget */\n initialize : function( attributes ){\n DCListItemView.prototype.initialize.call( this, attributes );\n },\n\n // ......................................................................... misc\n /** String representation */\n toString : function(){\n var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n return 'DCListItemEdit(' + modelString + ')';\n }\n});\n\n\n//==============================================================================\nvar DCEListItemView = DC_LI.DCEListItemView;\n/** @class Read only view for DatasetCollectionElement.\n */\nvar DCEListItemEdit = DCEListItemView.extend(\n/** @lends DCEListItemEdit.prototype */{\n//TODO: this might be expendable - compacted with HDAListItemView\n\n /** set up */\n initialize : function( attributes ){\n DCEListItemView.prototype.initialize.call( this, attributes );\n },\n\n // ......................................................................... misc\n /** String representation */\n toString : function(){\n var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n return 'DCEListItemEdit(' + modelString + ')';\n }\n});\n\n\n//==============================================================================\n// NOTE: this does not inherit from DatasetDCEListItemView as you would expect\n//TODO: but should - if we can find something simpler than using diamond\n/** @class Editable view for a DatasetCollectionElement that is also an DatasetAssociation\n * (a dataset contained in a dataset collection).\n */\nvar DatasetDCEListItemEdit = DATASET_LI_EDIT.DatasetListItemEdit.extend(\n/** @lends DatasetDCEListItemEdit.prototype */{\n\n /** set up */\n initialize : function( attributes ){\n DATASET_LI_EDIT.DatasetListItemEdit.prototype.initialize.call( this, attributes );\n },\n\n // NOTE: this does not inherit from DatasetDCEListItemView - so we duplicate this here\n //TODO: fix\n /** In this override, only get details if in the ready state.\n * Note: fetch with no 'change' event triggering to prevent automatic rendering.\n */\n _fetchModelDetails : function(){\n var view = this;\n if( view.model.inReadyState() && !view.model.hasDetails() ){\n return view.model.fetch({ silent: true });\n }\n return jQuery.when();\n },\n\n /** Override to remove delete button */\n _renderDeleteButton : function(){\n return null;\n },\n\n // ......................................................................... misc\n /** String representation */\n toString : function(){\n var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n return 'DatasetDCEListItemEdit(' + modelString + ')';\n }\n});\n\n// ............................................................................ TEMPLATES\n/** underscore templates */\nDatasetDCEListItemEdit.prototype.templates = (function(){\n\n return _.extend( {}, DATASET_LI_EDIT.DatasetListItemEdit.prototype.templates, {\n titleBar : DC_LI.DatasetDCEListItemView.prototype.templates.titleBar\n });\n}());\n\n\n//==============================================================================\n/** @class Read only view for a DatasetCollectionElement that is also a DatasetCollection\n * (a nested DC).\n */\nvar NestedDCDCEListItemEdit = DC_LI.NestedDCDCEListItemView.extend(\n/** @lends NestedDCDCEListItemEdit.prototype */{\n\n /** String representation */\n toString : function(){\n var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n return 'NestedDCDCEListItemEdit(' + modelString + ')';\n }\n});\n\n\n//==============================================================================\n return {\n DCListItemEdit : DCListItemEdit,\n DCEListItemEdit : DCEListItemEdit,\n DatasetDCEListItemEdit : DatasetDCEListItemEdit,\n NestedDCDCEListItemEdit : NestedDCDCEListItemEdit\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/collection/collection-li-edit.js\n ** module id = 106\n ** module chunks = 3\n **/","define([\n \"mvc/collection/collection-view\",\n \"mvc/collection/collection-model\",\n \"mvc/collection/collection-li-edit\",\n \"mvc/base-mvc\",\n \"utils/localization\",\n \"ui/editable-text\",\n], function( DC_VIEW, DC_MODEL, DC_EDIT, BASE_MVC, _l ){\n\n'use strict';\n/* =============================================================================\nTODO:\n\n============================================================================= */\n/** @class editable View/Controller for a dataset collection.\n */\nvar _super = DC_VIEW.CollectionView;\nvar CollectionViewEdit = _super.extend(\n/** @lends CollectionView.prototype */{\n //MODEL is either a DatasetCollection (or subclass) or a DatasetCollectionElement (list of pairs)\n\n /** logger used to record this.log messages, commonly set to console */\n //logger : console,\n\n /** sub view class used for datasets */\n DatasetDCEViewClass : DC_EDIT.DatasetDCEListItemEdit,\n /** sub view class used for nested collections */\n NestedDCDCEViewClass: DC_EDIT.NestedDCDCEListItemEdit,\n\n // ......................................................................... SET UP\n /** Set up the view, set up storage, bind listeners to HistoryContents events\n * @param {Object} attributes optional settings for the panel\n */\n initialize : function( attributes ){\n _super.prototype.initialize.call( this, attributes );\n },\n\n /** In this override, make the collection name editable\n */\n _setUpBehaviors : function( $where ){\n $where = $where || this.$el;\n _super.prototype._setUpBehaviors.call( this, $where );\n if( !this.model ){ return; }\n\n // anon users shouldn't have access to any of the following\n if( !Galaxy.user || Galaxy.user.isAnonymous() ){\n return;\n }\n\n //TODO: extract\n var panel = this,\n nameSelector = '> .controls .name';\n $where.find( nameSelector )\n .attr( 'title', _l( 'Click to rename collection' ) )\n .tooltip({ placement: 'bottom' })\n .make_text_editable({\n on_finish: function( newName ){\n var previousName = panel.model.get( 'name' );\n if( newName && newName !== previousName ){\n panel.$el.find( nameSelector ).text( newName );\n panel.model.save({ name: newName })\n .fail( function(){\n panel.$el.find( nameSelector ).text( panel.model.previous( 'name' ) );\n });\n } else {\n panel.$el.find( nameSelector ).text( previousName );\n }\n }\n });\n },\n\n // ........................................................................ misc\n /** string rep */\n toString : function(){\n return 'CollectionViewEdit(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';\n }\n});\n\n\n// =============================================================================\n/** @class non-editable, read-only View/Controller for a dataset collection. */\nvar ListCollectionViewEdit = CollectionViewEdit.extend(\n/** @lends ListCollectionView.prototype */{\n\n //TODO: not strictly needed - due to switch in CollectionView._getContentClass\n /** sub view class used for datasets */\n DatasetDCEViewClass : DC_EDIT.DatasetDCEListItemEdit,\n\n // ........................................................................ misc\n /** string rep */\n toString : function(){\n return 'ListCollectionViewEdit(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';\n }\n});\n\n\n// =============================================================================\n/** @class Editable, read-only View/Controller for a dataset collection. */\nvar PairCollectionViewEdit = ListCollectionViewEdit.extend(\n/** @lends PairCollectionViewEdit.prototype */{\n\n // ........................................................................ misc\n /** string rep */\n toString : function(){\n return 'PairCollectionViewEdit(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';\n }\n});\n\n\n// =============================================================================\n/** @class Editable (roughly since these collections are immutable),\n * View/Controller for a dataset collection.\n */\nvar NestedPairCollectionViewEdit = PairCollectionViewEdit.extend(\n/** @lends NestedPairCollectionViewEdit.prototype */{\n\n /** Override to remove the editable text from the name/identifier - these collections are considered immutable */\n _setUpBehaviors : function( $where ){\n _super.prototype._setUpBehaviors.call( this, $where );\n },\n\n // ........................................................................ misc\n /** string rep */\n toString : function(){\n return 'NestedPairCollectionViewEdit(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';\n }\n});\n\n\n// =============================================================================\n/** @class editable, View/Controller for a list of pairs dataset collection. */\nvar ListOfPairsCollectionViewEdit = CollectionViewEdit.extend(\n/** @lends ListOfPairsCollectionView.prototype */{\n\n //TODO: not strictly needed - due to switch in CollectionView._getContentClass\n /** sub view class used for nested collections */\n NestedDCDCEViewClass : DC_EDIT.NestedDCDCEListItemEdit.extend({\n foldoutPanelClass : NestedPairCollectionViewEdit\n }),\n\n // ........................................................................ misc\n /** string rep */\n toString : function(){\n return 'ListOfPairsCollectionViewEdit(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';\n }\n});\n\n\n// =============================================================================\n/** @class View/Controller for a list of lists dataset collection. */\nvar ListOfListsCollectionViewEdit = CollectionViewEdit.extend(\n/** @lends ListOfListsCollectionView.prototype */{\n\n //TODO: not strictly needed - due to switch in CollectionView._getContentClass\n /** sub view class used for nested collections */\n NestedDCDCEViewClass : DC_EDIT.NestedDCDCEListItemEdit.extend({\n foldoutPanelClass : NestedPairCollectionViewEdit\n }),\n\n // ........................................................................ misc\n /** string rep */\n toString : function(){\n return 'ListOfListsCollectionViewEdit(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';\n }\n});\n\n\n//==============================================================================\n return {\n CollectionViewEdit : CollectionViewEdit,\n ListCollectionViewEdit : ListCollectionViewEdit,\n PairCollectionViewEdit : PairCollectionViewEdit,\n ListOfPairsCollectionViewEdit : ListOfPairsCollectionViewEdit,\n ListOfListsCollectionViewEdit : ListOfListsCollectionViewEdit\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/collection/collection-view-edit.js\n ** module id = 107\n ** module chunks = 3\n **/","define([\n \"utils/levenshtein\",\n \"utils/natural-sort\",\n \"mvc/collection/list-collection-creator\",\n \"mvc/base-mvc\",\n \"utils/localization\",\n \"ui/hoverhighlight\"\n], function( levenshteinDistance, naturalSort, LIST_COLLECTION_CREATOR, baseMVC, _l ){\n\n'use strict';\n\nvar logNamespace = 'collections';\n/* ============================================================================\nTODO:\n\n\nPROGRAMMATICALLY:\ncurrPanel.once( 'rendered', function(){\n currPanel.showSelectors();\n currPanel.selectAll();\n _.last( currPanel.actionsPopup.options ).func();\n});\n\n============================================================================ */\n/** A view for paired datasets in the collections creator.\n */\nvar PairView = Backbone.View.extend( baseMVC.LoggableMixin ).extend({\n _logNamespace : logNamespace,\n\n tagName : 'li',\n className : 'dataset paired',\n\n initialize : function( attributes ){\n this.pair = attributes.pair || {};\n },\n\n template : _.template([\n '<%- pair.forward.name %>',\n '',\n '<%- pair.name %>',\n '',\n '<%- pair.reverse.name %>'\n ].join('')),\n\n render : function(){\n this.$el\n .attr( 'draggable', true )\n .data( 'pair', this.pair )\n .html( this.template({ pair: this.pair }) )\n .addClass( 'flex-column-container' );\n return this;\n },\n\n events : {\n 'dragstart' : '_dragstart',\n 'dragend' : '_dragend',\n 'dragover' : '_sendToParent',\n 'drop' : '_sendToParent'\n },\n\n /** dragging pairs for re-ordering */\n _dragstart : function( ev ){\n ev.currentTarget.style.opacity = '0.4';\n if( ev.originalEvent ){ ev = ev.originalEvent; }\n\n ev.dataTransfer.effectAllowed = 'move';\n ev.dataTransfer.setData( 'text/plain', JSON.stringify( this.pair ) );\n\n this.$el.parent().trigger( 'pair.dragstart', [ this ] );\n },\n\n /** dragging pairs for re-ordering */\n _dragend : function( ev ){\n ev.currentTarget.style.opacity = '1.0';\n this.$el.parent().trigger( 'pair.dragend', [ this ] );\n },\n\n /** manually bubble up an event to the parent/container */\n _sendToParent : function( ev ){\n this.$el.parent().trigger( ev );\n },\n\n /** string rep */\n toString : function(){\n return 'PairView(' + this.pair.name + ')';\n }\n});\n\n\n// ============================================================================\n/** returns an autopair function that uses the provided options.match function */\nfunction autoPairFnBuilder( options ){\n options = options || {};\n options.createPair = options.createPair || function _defaultCreatePair( params ){\n params = params || {};\n var a = params.listA.splice( params.indexA, 1 )[0],\n b = params.listB.splice( params.indexB, 1 )[0],\n aInBIndex = params.listB.indexOf( a ),\n bInAIndex = params.listA.indexOf( b );\n if( aInBIndex !== -1 ){ params.listB.splice( aInBIndex, 1 ); }\n if( bInAIndex !== -1 ){ params.listA.splice( bInAIndex, 1 ); }\n return this._pair( a, b, { silent: true });\n };\n // compile these here outside of the loop\n var _regexps = [];\n function getRegExps(){\n if( !_regexps.length ){\n _regexps = [\n new RegExp( this.filters[0] ),\n new RegExp( this.filters[1] )\n ];\n }\n return _regexps;\n }\n // mangle params as needed\n options.preprocessMatch = options.preprocessMatch || function _defaultPreprocessMatch( params ){\n var regexps = getRegExps.call( this );\n return _.extend( params, {\n matchTo : params.matchTo.name.replace( regexps[0], '' ),\n possible : params.possible.name.replace( regexps[1], '' )\n });\n };\n\n return function _strategy( params ){\n this.debug( 'autopair _strategy ---------------------------' );\n params = params || {};\n var listA = params.listA,\n listB = params.listB,\n indexA = 0, indexB,\n bestMatch = {\n score : 0.0,\n index : null\n },\n paired = [];\n //console.debug( 'params:', JSON.stringify( params, null, ' ' ) );\n this.debug( 'starting list lens:', listA.length, listB.length );\n this.debug( 'bestMatch (starting):', JSON.stringify( bestMatch, null, ' ' ) );\n\n while( indexA < listA.length ){\n var matchTo = listA[ indexA ];\n bestMatch.score = 0.0;\n\n for( indexB=0; indexB= scoreThreshold ){\n //console.debug( 'autoPairFnBuilder.strategy', listA[ indexA ].name, listB[ bestMatch.index ].name );\n paired.push( options.createPair.call( this, {\n listA : listA,\n indexA : indexA,\n listB : listB,\n indexB : bestMatch.index\n }));\n //console.debug( 'list lens now:', listA.length, listB.length );\n } else {\n indexA += 1;\n }\n if( !listA.length || !listB.length ){\n return paired;\n }\n }\n this.debug( 'paired:', JSON.stringify( paired, null, ' ' ) );\n this.debug( 'autopair _strategy ---------------------------' );\n return paired;\n };\n}\n\n\n// ============================================================================\n/** An interface for building collections of paired datasets.\n */\nvar PairedCollectionCreator = Backbone.View.extend( baseMVC.LoggableMixin ).extend({\n _logNamespace : logNamespace,\n\n className: 'list-of-pairs-collection-creator collection-creator flex-row-container',\n\n /** set up initial options, instance vars, behaviors, and autopair (if set to do so) */\n initialize : function( attributes ){\n this.metric( 'PairedCollectionCreator.initialize', attributes );\n //this.debug( '-- PairedCollectionCreator:', attributes );\n\n attributes = _.defaults( attributes, {\n datasets : [],\n filters : this.DEFAULT_FILTERS,\n automaticallyPair : true,\n strategy : 'lcs',\n matchPercentage : 0.9,\n twoPassAutopairing : true\n });\n\n /** unordered, original list */\n this.initialList = attributes.datasets;\n\n /** is this from a history? if so, what's its id? */\n this.historyId = attributes.historyId;\n\n /** which filters should be used initially? (String[2] or name in commonFilters) */\n this.filters = this.commonFilters[ attributes.filters ] || this.commonFilters[ this.DEFAULT_FILTERS ];\n if( _.isArray( attributes.filters ) ){\n this.filters = attributes.filters;\n }\n\n /** try to auto pair the unpaired datasets on load? */\n this.automaticallyPair = attributes.automaticallyPair;\n\n /** what method to use for auto pairing (will be passed aggression level) */\n this.strategy = this.strategies[ attributes.strategy ] || this.strategies[ this.DEFAULT_STRATEGY ];\n if( _.isFunction( attributes.strategy ) ){\n this.strategy = attributes.strategy;\n }\n\n /** distance/mismatch level allowed for autopairing */\n this.matchPercentage = attributes.matchPercentage;\n\n /** try to autopair using simple first, then this.strategy on the remainder */\n this.twoPassAutopairing = attributes.twoPassAutopairing;\n\n /** remove file extensions (\\.*) from created pair names? */\n this.removeExtensions = true;\n //this.removeExtensions = false;\n\n /** fn to call when the cancel button is clicked (scoped to this) - if falsy, no btn is displayed */\n this.oncancel = attributes.oncancel;\n /** fn to call when the collection is created (scoped to this) */\n this.oncreate = attributes.oncreate;\n\n /** fn to call when the cancel button is clicked (scoped to this) - if falsy, no btn is displayed */\n this.autoscrollDist = attributes.autoscrollDist || 24;\n\n /** is the unpaired panel shown? */\n this.unpairedPanelHidden = false;\n /** is the paired panel shown? */\n this.pairedPanelHidden = false;\n\n /** DOM elements currently being dragged */\n this.$dragging = null;\n\n /** Used for blocking UI events during ajax/operations (don't post twice) */\n this.blocking = false;\n\n this._setUpBehaviors();\n this._dataSetUp();\n },\n\n /** map of common filter pairs by name */\n commonFilters : {\n illumina : [ '_1', '_2' ],\n Rs : [ '_R1', '_R2' ]\n },\n /** which commonFilter to use by default */\n DEFAULT_FILTERS : 'illumina',\n\n /** map of name->fn for autopairing */\n strategies : {\n 'simple' : 'autopairSimple',\n 'lcs' : 'autopairLCS',\n 'levenshtein' : 'autopairLevenshtein'\n },\n /** default autopair strategy name */\n DEFAULT_STRATEGY : 'lcs',\n\n // ------------------------------------------------------------------------ process raw list\n /** set up main data: cache initialList, sort, and autopair */\n _dataSetUp : function(){\n //this.debug( '-- _dataSetUp' );\n\n this.paired = [];\n this.unpaired = [];\n\n this.selectedIds = [];\n\n // sort initial list, add ids if needed, and save new working copy to unpaired\n this._sortInitialList();\n this._ensureIds();\n this.unpaired = this.initialList.slice( 0 );\n\n if( this.automaticallyPair ){\n this.autoPair();\n this.once( 'rendered:initial', function(){\n this.trigger( 'autopair' );\n });\n }\n },\n\n /** sort initial list */\n _sortInitialList : function(){\n //this.debug( '-- _sortInitialList' );\n this._sortDatasetList( this.initialList );\n },\n\n /** sort a list of datasets */\n _sortDatasetList : function( list ){\n // currently only natural sort by name\n list.sort( function( a, b ){ return naturalSort( a.name, b.name ); });\n return list;\n },\n\n /** add ids to dataset objs in initial list if none */\n _ensureIds : function(){\n this.initialList.forEach( function( dataset ){\n if( !dataset.hasOwnProperty( 'id' ) ){\n dataset.id = _.uniqueId();\n }\n });\n return this.initialList;\n },\n\n /** split initial list into two lists, those that pass forward filters & those passing reverse */\n _splitByFilters : function(){\n var regexFilters = this.filters.map( function( stringFilter ){\n return new RegExp( stringFilter );\n }),\n split = [ [], [] ];\n\n function _filter( unpaired, filter ){\n return filter.test( unpaired.name );\n //return dataset.name.indexOf( filter ) >= 0;\n }\n this.unpaired.forEach( function _filterEach( unpaired ){\n // 90% of the time this seems to work, but:\n //TODO: this treats *all* strings as regex which may confuse people - possibly check for // surrounding?\n // would need explanation in help as well\n regexFilters.forEach( function( filter, i ){\n if( _filter( unpaired, filter ) ){\n split[i].push( unpaired );\n }\n });\n });\n return split;\n },\n\n /** add a dataset to the unpaired list in it's proper order */\n _addToUnpaired : function( dataset ){\n // currently, unpaired is natural sorted by name, use binary search to find insertion point\n var binSearchSortedIndex = function( low, hi ){\n if( low === hi ){ return low; }\n\n var mid = Math.floor( ( hi - low ) / 2 ) + low,\n compared = naturalSort( dataset.name, this.unpaired[ mid ].name );\n\n if( compared < 0 ){\n return binSearchSortedIndex( low, mid );\n } else if( compared > 0 ){\n return binSearchSortedIndex( mid + 1, hi );\n }\n // walk the equal to find the last\n while( this.unpaired[ mid ] && this.unpaired[ mid ].name === dataset.name ){ mid++; }\n return mid;\n\n }.bind( this );\n\n this.unpaired.splice( binSearchSortedIndex( 0, this.unpaired.length ), 0, dataset );\n },\n\n // ------------------------------------------------------------------------ auto pairing\n /** two passes to automatically create pairs:\n * use both simpleAutoPair, then the fn mentioned in strategy\n */\n autoPair : function( strategy ){\n // split first using exact matching\n var split = this._splitByFilters(),\n paired = [];\n if( this.twoPassAutopairing ){\n paired = this.autopairSimple({\n listA : split[0],\n listB : split[1]\n });\n split = this._splitByFilters();\n }\n\n // uncomment to see printlns while running tests\n //this.debug = function(){ console.log.apply( console, arguments ); };\n\n // then try the remainder with something less strict\n strategy = strategy || this.strategy;\n split = this._splitByFilters();\n paired = paired.concat( this[ strategy ].call( this, {\n listA : split[0],\n listB : split[1]\n }));\n return paired;\n },\n\n /** autopair by exact match */\n autopairSimple : autoPairFnBuilder({\n scoreThreshold: function(){ return 1.0; },\n match : function _match( params ){\n params = params || {};\n if( params.matchTo === params.possible ){\n return {\n index: params.index,\n score: 1.0\n };\n }\n return params.bestMatch;\n }\n }),\n\n /** autopair by levenshtein edit distance scoring */\n autopairLevenshtein : autoPairFnBuilder({\n scoreThreshold: function(){ return this.matchPercentage; },\n match : function _matches( params ){\n params = params || {};\n var distance = levenshteinDistance( params.matchTo, params.possible ),\n score = 1.0 - ( distance / ( Math.max( params.matchTo.length, params.possible.length ) ) );\n if( score > params.bestMatch.score ){\n return {\n index: params.index,\n score: score\n };\n }\n return params.bestMatch;\n }\n }),\n\n /** autopair by longest common substrings scoring */\n autopairLCS : autoPairFnBuilder({\n scoreThreshold: function(){ return this.matchPercentage; },\n match : function _matches( params ){\n params = params || {};\n var match = this._naiveStartingAndEndingLCS( params.matchTo, params.possible ).length,\n score = match / ( Math.max( params.matchTo.length, params.possible.length ) );\n if( score > params.bestMatch.score ){\n return {\n index: params.index,\n score: score\n };\n }\n return params.bestMatch;\n }\n }),\n\n /** return the concat'd longest common prefix and suffix from two strings */\n _naiveStartingAndEndingLCS : function( s1, s2 ){\n var fwdLCS = '',\n revLCS = '',\n i = 0, j = 0;\n while( i < s1.length && i < s2.length ){\n if( s1[ i ] !== s2[ i ] ){\n break;\n }\n fwdLCS += s1[ i ];\n i += 1;\n }\n if( i === s1.length ){ return s1; }\n if( i === s2.length ){ return s2; }\n\n i = ( s1.length - 1 );\n j = ( s2.length - 1 );\n while( i >= 0 && j >= 0 ){\n if( s1[ i ] !== s2[ j ] ){\n break;\n }\n revLCS = [ s1[ i ], revLCS ].join( '' );\n i -= 1;\n j -= 1;\n }\n return fwdLCS + revLCS;\n },\n\n // ------------------------------------------------------------------------ pairing / unpairing\n /** create a pair from fwd and rev, removing them from unpaired, and placing the new pair in paired */\n _pair : function( fwd, rev, options ){\n options = options || {};\n this.debug( '_pair:', fwd, rev );\n var pair = this._createPair( fwd, rev, options.name );\n this.paired.push( pair );\n this.unpaired = _.without( this.unpaired, fwd, rev );\n if( !options.silent ){\n this.trigger( 'pair:new', pair );\n }\n return pair;\n },\n\n /** create a pair Object from fwd and rev, adding the name attribute (will guess if not given) */\n _createPair : function( fwd, rev, name ){\n // ensure existance and don't pair something with itself\n if( !( fwd && rev ) || ( fwd === rev ) ){\n throw new Error( 'Bad pairing: ' + [ JSON.stringify( fwd ), JSON.stringify( rev ) ] );\n }\n name = name || this._guessNameForPair( fwd, rev );\n return { forward : fwd, name : name, reverse : rev };\n },\n\n /** try to find a good pair name for the given fwd and rev datasets */\n _guessNameForPair : function( fwd, rev, removeExtensions ){\n removeExtensions = ( removeExtensions !== undefined )?( removeExtensions ):( this.removeExtensions );\n var fwdName = fwd.name,\n revName = rev.name,\n lcs = this._naiveStartingAndEndingLCS(\n fwdName.replace( new RegExp( this.filters[0] ), '' ),\n revName.replace( new RegExp( this.filters[1] ), '' )\n );\n if( removeExtensions ){\n var lastDotIndex = lcs.lastIndexOf( '.' );\n if( lastDotIndex > 0 ){\n var extension = lcs.slice( lastDotIndex, lcs.length );\n lcs = lcs.replace( extension, '' );\n fwdName = fwdName.replace( extension, '' );\n revName = revName.replace( extension, '' );\n }\n }\n return lcs || ( fwdName + ' & ' + revName );\n },\n\n /** unpair a pair, removing it from paired, and adding the fwd,rev datasets back into unpaired */\n _unpair : function( pair, options ){\n options = options || {};\n if( !pair ){\n throw new Error( 'Bad pair: ' + JSON.stringify( pair ) );\n }\n this.paired = _.without( this.paired, pair );\n this._addToUnpaired( pair.forward );\n this._addToUnpaired( pair.reverse );\n\n if( !options.silent ){\n this.trigger( 'pair:unpair', [ pair ] );\n }\n return pair;\n },\n\n /** unpair all paired datasets */\n unpairAll : function(){\n var pairs = [];\n while( this.paired.length ){\n pairs.push( this._unpair( this.paired[ 0 ], { silent: true }) );\n }\n this.trigger( 'pair:unpair', pairs );\n },\n\n // ------------------------------------------------------------------------ API\n /** convert a pair into JSON compatible with the collections API */\n _pairToJSON : function( pair, src ){\n src = src || 'hda';\n //TODO: consider making this the pair structure when created instead\n return {\n collection_type : 'paired',\n src : 'new_collection',\n name : pair.name,\n element_identifiers : [{\n name : 'forward',\n id : pair.forward.id,\n src : src\n }, {\n name : 'reverse',\n id : pair.reverse.id,\n src : src\n }]\n };\n },\n\n /** create the collection via the API\n * @returns {jQuery.xhr Object} the jquery ajax request\n */\n createList : function( name ){\n var creator = this,\n url = Galaxy.root + 'api/histories/' + this.historyId + '/contents/dataset_collections';\n\n //TODO: use ListPairedCollection.create()\n var ajaxData = {\n type : 'dataset_collection',\n collection_type : 'list:paired',\n name : _.escape( name || creator.$( '.collection-name' ).val() ),\n element_identifiers : creator.paired.map( function( pair ){\n return creator._pairToJSON( pair );\n })\n\n };\n //this.debug( JSON.stringify( ajaxData ) );\n creator.blocking = true;\n return jQuery.ajax( url, {\n type : 'POST',\n contentType : 'application/json',\n dataType : 'json',\n data : JSON.stringify( ajaxData )\n })\n .always( function(){\n creator.blocking = false;\n })\n .fail( function( xhr, status, message ){\n creator._ajaxErrHandler( xhr, status, message );\n })\n .done( function( response, message, xhr ){\n //this.info( 'ok', response, message, xhr );\n creator.trigger( 'collection:created', response, message, xhr );\n creator.metric( 'collection:created', response );\n if( typeof creator.oncreate === 'function' ){\n creator.oncreate.call( this, response, message, xhr );\n }\n });\n },\n\n /** handle ajax errors with feedback and details to the user (if available) */\n _ajaxErrHandler : function( xhr, status, message ){\n this.error( xhr, status, message );\n var content = _l( 'An error occurred while creating this collection' );\n if( xhr ){\n if( xhr.readyState === 0 && xhr.status === 0 ){\n content += ': ' + _l( 'Galaxy could not be reached and may be updating.' )\n + _l( ' Try again in a few minutes.' );\n } else if( xhr.responseJSON ){\n content += '
                                  ' + JSON.stringify( xhr.responseJSON ) + '
                                  ';\n } else {\n content += ': ' + message;\n }\n }\n creator._showAlert( content, 'alert-danger' );\n },\n\n // ------------------------------------------------------------------------ rendering\n /** render the entire interface */\n render : function( speed, callback ){\n //this.debug( '-- _render' );\n //this.$el.empty().html( PairedCollectionCreator.templates.main() );\n this.$el.empty().html( PairedCollectionCreator.templates.main() );\n this._renderHeader( speed );\n this._renderMiddle( speed );\n this._renderFooter( speed );\n this._addPluginComponents();\n this.trigger( 'rendered', this );\n return this;\n },\n\n /** render the header section */\n _renderHeader : function( speed, callback ){\n //this.debug( '-- _renderHeader' );\n var $header = this.$( '.header' ).empty().html( PairedCollectionCreator.templates.header() )\n .find( '.help-content' ).prepend( $( PairedCollectionCreator.templates.helpContent() ) );\n\n this._renderFilters();\n return $header;\n },\n /** fill the filter inputs with the filter values */\n _renderFilters : function(){\n return this.$( '.forward-column .column-header input' ).val( this.filters[0] )\n .add( this.$( '.reverse-column .column-header input' ).val( this.filters[1] ) );\n },\n\n /** render the middle including unpaired and paired sections (which may be hidden) */\n _renderMiddle : function( speed, callback ){\n var $middle = this.$( '.middle' ).empty().html( PairedCollectionCreator.templates.middle() );\n\n // (re-) hide the un/paired panels based on instance vars\n if( this.unpairedPanelHidden ){\n this.$( '.unpaired-columns' ).hide();\n } else if( this.pairedPanelHidden ){\n this.$( '.paired-columns' ).hide();\n }\n\n this._renderUnpaired();\n this._renderPaired();\n return $middle;\n },\n /** render the unpaired section, showing datasets accrd. to filters, update the unpaired counts */\n _renderUnpaired : function( speed, callback ){\n //this.debug( '-- _renderUnpaired' );\n var creator = this,\n $fwd, $rev, $prd = [],\n split = this._splitByFilters();\n // update unpaired counts\n this.$( '.forward-column .title' )\n .text([ split[0].length, _l( 'unpaired forward' ) ].join( ' ' ));\n this.$( '.forward-column .unpaired-info' )\n .text( this._renderUnpairedDisplayStr( this.unpaired.length - split[0].length ) );\n this.$( '.reverse-column .title' )\n .text([ split[1].length, _l( 'unpaired reverse' ) ].join( ' ' ));\n this.$( '.reverse-column .unpaired-info' )\n .text( this._renderUnpairedDisplayStr( this.unpaired.length - split[1].length ) );\n\n this.$( '.unpaired-columns .column-datasets' ).empty();\n\n // show/hide the auto pair button if any unpaired are left\n this.$( '.autopair-link' ).toggle( this.unpaired.length !== 0 );\n if( this.unpaired.length === 0 ){\n this._renderUnpairedEmpty();\n return;\n }\n\n // create the dataset dom arrays\n $rev = split[1].map( function( dataset, i ){\n // if there'll be a fwd dataset across the way, add a button to pair the row\n if( ( split[0][ i ] !== undefined )\n && ( split[0][ i ] !== dataset ) ){\n $prd.push( creator._renderPairButton() );\n }\n return creator._renderUnpairedDataset( dataset );\n });\n $fwd = split[0].map( function( dataset ){\n return creator._renderUnpairedDataset( dataset );\n });\n\n if( !$fwd.length && !$rev.length ){\n this._renderUnpairedNotShown();\n return;\n }\n // add to appropo cols\n //TODO: not the best way to render - consider rendering the entire unpaired-columns section in a fragment\n // and swapping out that\n this.$( '.unpaired-columns .forward-column .column-datasets' ).append( $fwd )\n .add( this.$( '.unpaired-columns .paired-column .column-datasets' ).append( $prd ) )\n .add( this.$( '.unpaired-columns .reverse-column .column-datasets' ).append( $rev ) );\n this._adjUnpairedOnScrollbar();\n },\n /** return a string to display the count of filtered out datasets */\n _renderUnpairedDisplayStr : function( numFiltered ){\n return [ '(', numFiltered, ' ', _l( 'filtered out' ), ')' ].join('');\n },\n /** return an unattached jQuery DOM element to represent an unpaired dataset */\n _renderUnpairedDataset : function( dataset ){\n //TODO: to underscore template\n return $( '
                                • ')\n .attr( 'id', 'dataset-' + dataset.id )\n .addClass( 'dataset unpaired' )\n .attr( 'draggable', true )\n .addClass( dataset.selected? 'selected': '' )\n .append( $( '' ).addClass( 'dataset-name' ).text( dataset.name ) )\n //??\n .data( 'dataset', dataset );\n },\n /** render the button that may go between unpaired datasets, allowing the user to pair a row */\n _renderPairButton : function(){\n //TODO: *not* a dataset - don't pretend like it is\n return $( '
                                • ').addClass( 'dataset unpaired' )\n .append( $( '' ).addClass( 'dataset-name' ).text( _l( 'Pair these datasets' ) ) );\n },\n /** a message to display when no unpaired left */\n _renderUnpairedEmpty : function(){\n //this.debug( '-- renderUnpairedEmpty' );\n var $msg = $( '
                                  ' )\n .text( '(' + _l( 'no remaining unpaired datasets' ) + ')' );\n this.$( '.unpaired-columns .paired-column .column-datasets' ).empty().prepend( $msg );\n return $msg;\n },\n /** a message to display when no unpaired can be shown with the current filters */\n _renderUnpairedNotShown : function(){\n //this.debug( '-- renderUnpairedEmpty' );\n var $msg = $( '
                                  ' )\n .text( '(' + _l( 'no datasets were found matching the current filters' ) + ')' );\n this.$( '.unpaired-columns .paired-column .column-datasets' ).empty().prepend( $msg );\n return $msg;\n },\n /** try to detect if the unpaired section has a scrollbar and adjust left column for better centering of all */\n _adjUnpairedOnScrollbar : function(){\n var $unpairedColumns = this.$( '.unpaired-columns' ).last(),\n $firstDataset = this.$( '.unpaired-columns .reverse-column .dataset' ).first();\n if( !$firstDataset.length ){ return; }\n var ucRight = $unpairedColumns.offset().left + $unpairedColumns.outerWidth(),\n dsRight = $firstDataset.offset().left + $firstDataset.outerWidth(),\n rightDiff = Math.floor( ucRight ) - Math.floor( dsRight );\n //this.debug( 'rightDiff:', ucRight, '-', dsRight, '=', rightDiff );\n this.$( '.unpaired-columns .forward-column' )\n .css( 'margin-left', ( rightDiff > 0 )? rightDiff: 0 );\n },\n\n /** render the paired section and update counts of paired datasets */\n _renderPaired : function( speed, callback ){\n //this.debug( '-- _renderPaired' );\n this.$( '.paired-column-title .title' ).text([ this.paired.length, _l( 'paired' ) ].join( ' ' ) );\n // show/hide the unpair all link\n this.$( '.unpair-all-link' ).toggle( this.paired.length !== 0 );\n if( this.paired.length === 0 ){\n this._renderPairedEmpty();\n return;\n //TODO: would be best to return here (the $columns)\n } else {\n // show/hide 'remove extensions link' when any paired and they seem to have extensions\n this.$( '.remove-extensions-link' ).show();\n }\n\n this.$( '.paired-columns .column-datasets' ).empty();\n var creator = this;\n this.paired.forEach( function( pair, i ){\n //TODO: cache these?\n var pairView = new PairView({ pair: pair });\n creator.$( '.paired-columns .column-datasets' )\n .append( pairView.render().$el )\n .append([\n ''\n ].join( '' ));\n });\n },\n /** a message to display when none paired */\n _renderPairedEmpty : function(){\n var $msg = $( '
                                  ' )\n .text( '(' + _l( 'no paired datasets yet' ) + ')' );\n this.$( '.paired-columns .column-datasets' ).empty().prepend( $msg );\n return $msg;\n },\n\n /** render the footer, completion controls, and cancel controls */\n _renderFooter : function( speed, callback ){\n var $footer = this.$( '.footer' ).empty().html( PairedCollectionCreator.templates.footer() );\n this.$( '.remove-extensions' ).prop( 'checked', this.removeExtensions );\n if( typeof this.oncancel === 'function' ){\n this.$( '.cancel-create.btn' ).show();\n }\n return $footer;\n },\n\n /** add any jQuery/bootstrap/custom plugins to elements rendered */\n _addPluginComponents : function(){\n this._chooseFiltersPopover( '.choose-filters-link' );\n this.$( '.help-content i' ).hoverhighlight( '.collection-creator', 'rgba( 64, 255, 255, 1.0 )' );\n },\n\n /** build a filter selection popover allowing selection of common filter pairs */\n _chooseFiltersPopover : function( selector ){\n function filterChoice( val1, val2 ){\n return [\n ''\n ].join('');\n }\n var $popoverContent = $( _.template([\n '
                                  ',\n '
                                  ',\n _l( 'Choose from the following filters to change which unpaired reads are shown in the display' ),\n ':
                                  ',\n _.values( this.commonFilters ).map( function( filterSet ){\n return filterChoice( filterSet[0], filterSet[1] );\n }).join( '' ),\n '
                                  '\n ].join(''))({}));\n\n return this.$( selector ).popover({\n container : '.collection-creator',\n placement : 'bottom',\n html : true,\n //animation : false,\n content : $popoverContent\n });\n },\n\n /** add (or clear if clear is truthy) a validation warning to what */\n _validationWarning : function( what, clear ){\n var VALIDATION_CLASS = 'validation-warning';\n if( what === 'name' ){\n what = this.$( '.collection-name' ).add( this.$( '.collection-name-prompt' ) );\n this.$( '.collection-name' ).focus().select();\n }\n if( clear ){\n what = what || this.$( '.' + VALIDATION_CLASS );\n what.removeClass( VALIDATION_CLASS );\n } else {\n what.addClass( VALIDATION_CLASS );\n }\n },\n\n // ------------------------------------------------------------------------ events\n /** set up event handlers on self */\n _setUpBehaviors : function(){\n this.once( 'rendered', function(){\n this.trigger( 'rendered:initial', this );\n });\n\n this.on( 'pair:new', function(){\n //TODO: ideally only re-render the columns (or even elements) involved\n this._renderUnpaired();\n this._renderPaired();\n\n // scroll to bottom where new pairs are added\n //TODO: this doesn't seem to work - innerHeight sticks at 133...\n // may have to do with improper flex columns\n //var $pairedView = this.$( '.paired-columns' );\n //$pairedView.scrollTop( $pairedView.innerHeight() );\n //this.debug( $pairedView.height() )\n this.$( '.paired-columns' ).scrollTop( 8000000 );\n });\n this.on( 'pair:unpair', function( pairs ){\n //TODO: ideally only re-render the columns (or even elements) involved\n this._renderUnpaired();\n this._renderPaired();\n this.splitView();\n });\n\n this.on( 'filter-change', function(){\n this.filters = [\n this.$( '.forward-unpaired-filter input' ).val(),\n this.$( '.reverse-unpaired-filter input' ).val()\n ];\n this.metric( 'filter-change', this.filters );\n this._renderFilters();\n this._renderUnpaired();\n });\n\n this.on( 'autopair', function(){\n this._renderUnpaired();\n this._renderPaired();\n\n var message, msgClass = null;\n if( this.paired.length ){\n msgClass = 'alert-success';\n message = this.paired.length + ' ' + _l( 'pairs created' );\n if( !this.unpaired.length ){\n message += ': ' + _l( 'all datasets have been successfully paired' );\n this.hideUnpaired();\n this.$( '.collection-name' ).focus();\n }\n } else {\n message = _l([\n 'Could not automatically create any pairs from the given dataset names.',\n 'You may want to choose or enter different filters and try auto-pairing again.',\n 'Close this message using the X on the right to view more help.'\n ].join( ' ' ));\n }\n this._showAlert( message, msgClass );\n });\n\n //this.on( 'all', function(){\n // this.info( arguments );\n //});\n return this;\n },\n\n events : {\n // header\n 'click .more-help' : '_clickMoreHelp',\n 'click .less-help' : '_clickLessHelp',\n 'click .header .alert button' : '_hideAlert',\n 'click .forward-column .column-title' : '_clickShowOnlyUnpaired',\n 'click .reverse-column .column-title' : '_clickShowOnlyUnpaired',\n 'click .unpair-all-link' : '_clickUnpairAll',\n //TODO: this seems kinda backasswards - re-sending jq event as a backbone event, can we listen directly?\n 'change .forward-unpaired-filter input' : function( ev ){ this.trigger( 'filter-change' ); },\n 'focus .forward-unpaired-filter input' : function( ev ){ $( ev.currentTarget ).select(); },\n 'click .autopair-link' : '_clickAutopair',\n 'click .choose-filters .filter-choice' : '_clickFilterChoice',\n 'click .clear-filters-link' : '_clearFilters',\n 'change .reverse-unpaired-filter input' : function( ev ){ this.trigger( 'filter-change' ); },\n 'focus .reverse-unpaired-filter input' : function( ev ){ $( ev.currentTarget ).select(); },\n // unpaired\n 'click .forward-column .dataset.unpaired' : '_clickUnpairedDataset',\n 'click .reverse-column .dataset.unpaired' : '_clickUnpairedDataset',\n 'click .paired-column .dataset.unpaired' : '_clickPairRow',\n 'click .unpaired-columns' : 'clearSelectedUnpaired',\n 'mousedown .unpaired-columns .dataset' : '_mousedownUnpaired',\n // divider\n 'click .paired-column-title' : '_clickShowOnlyPaired',\n 'mousedown .flexible-partition-drag' : '_startPartitionDrag',\n // paired\n 'click .paired-columns .dataset.paired' : 'selectPair',\n 'click .paired-columns' : 'clearSelectedPaired',\n 'click .paired-columns .pair-name' : '_clickPairName',\n 'click .unpair-btn' : '_clickUnpair',\n // paired - drop target\n //'dragenter .paired-columns' : '_dragenterPairedColumns',\n //'dragleave .paired-columns .column-datasets': '_dragleavePairedColumns',\n 'dragover .paired-columns .column-datasets' : '_dragoverPairedColumns',\n 'drop .paired-columns .column-datasets' : '_dropPairedColumns',\n\n 'pair.dragstart .paired-columns .column-datasets' : '_pairDragstart',\n 'pair.dragend .paired-columns .column-datasets' : '_pairDragend',\n\n // footer\n 'change .remove-extensions' : function( ev ){ this.toggleExtensions(); },\n 'change .collection-name' : '_changeName',\n 'keydown .collection-name' : '_nameCheckForEnter',\n 'click .cancel-create' : function( ev ){\n if( typeof this.oncancel === 'function' ){\n this.oncancel.call( this );\n }\n },\n 'click .create-collection' : '_clickCreate'//,\n },\n\n // ........................................................................ header\n /** expand help */\n _clickMoreHelp : function( ev ){\n this.$( '.main-help' ).addClass( 'expanded' );\n this.$( '.more-help' ).hide();\n },\n /** collapse help */\n _clickLessHelp : function( ev ){\n this.$( '.main-help' ).removeClass( 'expanded' );\n this.$( '.more-help' ).show();\n },\n\n /** show an alert on the top of the interface containing message (alertClass is bootstrap's alert-*)*/\n _showAlert : function( message, alertClass ){\n alertClass = alertClass || 'alert-danger';\n this.$( '.main-help' ).hide();\n this.$( '.header .alert' ).attr( 'class', 'alert alert-dismissable' ).addClass( alertClass ).show()\n .find( '.alert-message' ).html( message );\n },\n /** hide the alerts at the top */\n _hideAlert : function( message ){\n this.$( '.main-help' ).show();\n this.$( '.header .alert' ).hide();\n },\n\n /** toggle between showing only unpaired and split view */\n _clickShowOnlyUnpaired : function( ev ){\n //this.debug( 'click unpaired', ev.currentTarget );\n if( this.$( '.paired-columns' ).is( ':visible' ) ){\n this.hidePaired();\n } else {\n this.splitView();\n }\n },\n /** toggle between showing only paired and split view */\n _clickShowOnlyPaired : function( ev ){\n //this.debug( 'click paired' );\n if( this.$( '.unpaired-columns' ).is( ':visible' ) ){\n this.hideUnpaired();\n } else {\n this.splitView();\n }\n },\n\n /** hide unpaired, show paired */\n hideUnpaired : function( speed, callback ){\n this.unpairedPanelHidden = true;\n this.pairedPanelHidden = false;\n this._renderMiddle( speed, callback );\n },\n /** hide paired, show unpaired */\n hidePaired : function( speed, callback ){\n this.unpairedPanelHidden = false;\n this.pairedPanelHidden = true;\n this._renderMiddle( speed, callback );\n },\n /** show both paired and unpaired (splitting evenly) */\n splitView : function( speed, callback ){\n this.unpairedPanelHidden = this.pairedPanelHidden = false;\n this._renderMiddle( speed, callback );\n return this;\n },\n\n /** unpair all paired and do other super neat stuff which I'm not really sure about yet... */\n _clickUnpairAll : function( ev ){\n this.metric( 'unpairAll' );\n this.unpairAll();\n },\n\n /** attempt to autopair */\n _clickAutopair : function( ev ){\n var paired = this.autoPair();\n this.metric( 'autopair', paired.length, this.unpaired.length );\n this.trigger( 'autopair' );\n },\n\n /** set the filters based on the data attributes of the button click target */\n _clickFilterChoice : function( ev ){\n var $selected = $( ev.currentTarget );\n this.$( '.forward-unpaired-filter input' ).val( $selected.data( 'forward' ) );\n this.$( '.reverse-unpaired-filter input' ).val( $selected.data( 'reverse' ) );\n this._hideChooseFilters();\n this.trigger( 'filter-change' );\n },\n\n /** hide the choose filters popover */\n _hideChooseFilters : function(){\n //TODO: update bootstrap and remove the following hack\n // see also: https://github.com/twbs/bootstrap/issues/10260\n this.$( '.choose-filters-link' ).popover( 'hide' );\n this.$( '.popover' ).css( 'display', 'none' );\n },\n\n /** clear both filters */\n _clearFilters : function( ev ){\n this.$( '.forward-unpaired-filter input' ).val( '' );\n this.$( '.reverse-unpaired-filter input' ).val( '' );\n this.trigger( 'filter-change' );\n },\n\n // ........................................................................ unpaired\n /** select an unpaired dataset */\n _clickUnpairedDataset : function( ev ){\n ev.stopPropagation();\n return this.toggleSelectUnpaired( $( ev.currentTarget ) );\n },\n\n /** Toggle the selection of an unpaired dataset representation.\n * @param [jQuery] $dataset the unpaired dataset dom rep to select\n * @param [Boolean] options.force if defined, force selection based on T/F; otherwise, toggle\n */\n toggleSelectUnpaired : function( $dataset, options ){\n options = options || {};\n var dataset = $dataset.data( 'dataset' ),\n select = options.force !== undefined? options.force: !$dataset.hasClass( 'selected' );\n //this.debug( id, options.force, $dataset, dataset );\n if( !$dataset.length || dataset === undefined ){ return $dataset; }\n\n if( select ){\n $dataset.addClass( 'selected' );\n if( !options.waitToPair ){\n this.pairAllSelected();\n }\n\n } else {\n $dataset.removeClass( 'selected' );\n //delete dataset.selected;\n }\n return $dataset;\n },\n\n /** pair all the currently selected unpaired datasets */\n pairAllSelected : function( options ){\n options = options || {};\n var creator = this,\n fwds = [],\n revs = [],\n pairs = [];\n creator.$( '.unpaired-columns .forward-column .dataset.selected' ).each( function(){\n fwds.push( $( this ).data( 'dataset' ) );\n });\n creator.$( '.unpaired-columns .reverse-column .dataset.selected' ).each( function(){\n revs.push( $( this ).data( 'dataset' ) );\n });\n fwds.length = revs.length = Math.min( fwds.length, revs.length );\n //this.debug( fwds );\n //this.debug( revs );\n fwds.forEach( function( fwd, i ){\n try {\n pairs.push( creator._pair( fwd, revs[i], { silent: true }) );\n\n } catch( err ){\n //TODO: preserve selected state of those that couldn't be paired\n //TODO: warn that some could not be paired\n creator.error( err );\n }\n });\n if( pairs.length && !options.silent ){\n this.trigger( 'pair:new', pairs );\n }\n return pairs;\n },\n\n /** clear the selection on all unpaired datasets */\n clearSelectedUnpaired : function(){\n this.$( '.unpaired-columns .dataset.selected' ).removeClass( 'selected' );\n },\n\n /** when holding down the shift key on a click, 'paint' the moused over datasets as selected */\n _mousedownUnpaired : function( ev ){\n if( ev.shiftKey ){\n var creator = this,\n $startTarget = $( ev.target ).addClass( 'selected' ),\n moveListener = function( ev ){\n creator.$( ev.target ).filter( '.dataset' ).addClass( 'selected' );\n };\n $startTarget.parent().on( 'mousemove', moveListener );\n\n // on any mouseup, stop listening to the move and try to pair any selected\n $( document ).one( 'mouseup', function( ev ){\n $startTarget.parent().off( 'mousemove', moveListener );\n creator.pairAllSelected();\n });\n }\n },\n\n /** attempt to pair two datasets directly across from one another */\n _clickPairRow : function( ev ){\n //if( !ev.currentTarget ){ return true; }\n var rowIndex = $( ev.currentTarget ).index(),\n fwd = $( '.unpaired-columns .forward-column .dataset' ).eq( rowIndex ).data( 'dataset' ),\n rev = $( '.unpaired-columns .reverse-column .dataset' ).eq( rowIndex ).data( 'dataset' );\n //this.debug( 'row:', rowIndex, fwd, rev );\n this._pair( fwd, rev );\n },\n\n // ........................................................................ divider/partition\n /** start dragging the visible divider/partition between unpaired and paired panes */\n _startPartitionDrag : function( ev ){\n var creator = this,\n startingY = ev.pageY;\n //this.debug( 'partition drag START:', ev );\n $( 'body' ).css( 'cursor', 'ns-resize' );\n creator.$( '.flexible-partition-drag' ).css( 'color', 'black' );\n\n function endDrag( ev ){\n //creator.debug( 'partition drag STOP:', ev );\n // doing this by an added class didn't really work well - kept flashing still\n creator.$( '.flexible-partition-drag' ).css( 'color', '' );\n $( 'body' ).css( 'cursor', '' ).unbind( 'mousemove', trackMouse );\n }\n function trackMouse( ev ){\n var offset = ev.pageY - startingY;\n //creator.debug( 'partition:', startingY, offset );\n if( !creator.adjPartition( offset ) ){\n //creator.debug( 'mouseup triggered' );\n $( 'body' ).trigger( 'mouseup' );\n }\n creator._adjUnpairedOnScrollbar();\n startingY += offset;\n }\n $( 'body' ).mousemove( trackMouse );\n $( 'body' ).one( 'mouseup', endDrag );\n },\n\n /** adjust the parition up/down +/-adj pixels */\n adjPartition : function( adj ){\n var $unpaired = this.$( '.unpaired-columns' ),\n $paired = this.$( '.paired-columns' ),\n unpairedHi = parseInt( $unpaired.css( 'height' ), 10 ),\n pairedHi = parseInt( $paired.css( 'height' ), 10 );\n //this.debug( adj, 'hi\\'s:', unpairedHi, pairedHi, unpairedHi + adj, pairedHi - adj );\n\n unpairedHi = Math.max( 10, unpairedHi + adj );\n pairedHi = pairedHi - adj;\n\n var movingUpwards = adj < 0;\n // when the divider gets close to the top - lock into hiding the unpaired section\n if( movingUpwards ){\n if( this.unpairedPanelHidden ){\n return false;\n } else if( unpairedHi <= 10 ){\n this.hideUnpaired();\n return false;\n }\n } else {\n if( this.unpairedPanelHidden ){\n $unpaired.show();\n this.unpairedPanelHidden = false;\n }\n }\n\n // when the divider gets close to the bottom - lock into hiding the paired section\n if( !movingUpwards ){\n if( this.pairedPanelHidden ){\n return false;\n } else if( pairedHi <= 15 ){\n this.hidePaired();\n return false;\n }\n\n } else {\n if( this.pairedPanelHidden ){\n $paired.show();\n this.pairedPanelHidden = false;\n }\n }\n\n $unpaired.css({\n height : unpairedHi + 'px',\n flex : '0 0 auto'\n });\n return true;\n },\n\n // ........................................................................ paired\n /** select a pair when clicked */\n selectPair : function( ev ){\n ev.stopPropagation();\n $( ev.currentTarget ).toggleClass( 'selected' );\n },\n\n /** deselect all pairs */\n clearSelectedPaired : function( ev ){\n this.$( '.paired-columns .dataset.selected' ).removeClass( 'selected' );\n },\n\n /** rename a pair when the pair name is clicked */\n _clickPairName : function( ev ){\n ev.stopPropagation();\n var $name = $( ev.currentTarget ),\n $pair = $name.parent().parent(),\n index = $pair.index( '.dataset.paired' ),\n pair = this.paired[ index ],\n response = prompt( 'Enter a new name for the pair:', pair.name );\n if( response ){\n pair.name = response;\n // set a flag (which won't be passed in json creation) for manual naming so we don't overwrite these\n // when adding/removing extensions\n //hackish\n pair.customizedName = true;\n $name.text( pair.name );\n }\n },\n\n /** unpair this pair */\n _clickUnpair : function( ev ){\n //if( !ev.currentTarget ){ return true; }\n var pairIndex = Math.floor( $( ev.currentTarget ).index( '.unpair-btn' ) );\n //this.debug( 'pair:', pairIndex );\n this._unpair( this.paired[ pairIndex ] );\n },\n\n // ........................................................................ paired - drag and drop re-ordering\n //_dragenterPairedColumns : function( ev ){\n // this.debug( '_dragenterPairedColumns:', ev );\n //},\n //_dragleavePairedColumns : function( ev ){\n // //this.debug( '_dragleavePairedColumns:', ev );\n //},\n /** track the mouse drag over the paired list adding a placeholder to show where the drop would occur */\n _dragoverPairedColumns : function( ev ){\n //this.debug( '_dragoverPairedColumns:', ev );\n ev.preventDefault();\n\n var $list = this.$( '.paired-columns .column-datasets' );\n this._checkForAutoscroll( $list, ev.originalEvent.clientY );\n //this.debug( ev.originalEvent.clientX, ev.originalEvent.clientY );\n var $nearest = this._getNearestPairedDatasetLi( ev.originalEvent.clientY );\n\n $( '.element-drop-placeholder' ).remove();\n var $placeholder = $( '
                                  ' );\n if( !$nearest.length ){\n $list.append( $placeholder );\n } else {\n $nearest.before( $placeholder );\n }\n },\n\n /** If the mouse is near enough to the list's top or bottom, scroll the list */\n _checkForAutoscroll : function( $element, y ){\n var AUTOSCROLL_SPEED = 2;\n var offset = $element.offset(),\n scrollTop = $element.scrollTop(),\n upperDist = y - offset.top,\n lowerDist = ( offset.top + $element.outerHeight() ) - y;\n //this.debug( '_checkForAutoscroll:', scrollTop, upperDist, lowerDist );\n if( upperDist >= 0 && upperDist < this.autoscrollDist ){\n $element.scrollTop( scrollTop - AUTOSCROLL_SPEED );\n } else if( lowerDist >= 0 && lowerDist < this.autoscrollDist ){\n $element.scrollTop( scrollTop + AUTOSCROLL_SPEED );\n }\n },\n\n /** get the nearest *previous* paired dataset PairView based on the mouse's Y coordinate.\n * If the y is at the end of the list, return an empty jQuery object.\n */\n _getNearestPairedDatasetLi : function( y ){\n var WIGGLE = 4,\n lis = this.$( '.paired-columns .column-datasets li' ).toArray();\n for( var i=0; i y && top - halfHeight < y ){\n //this.debug( y, top + halfHeight, top - halfHeight )\n return $li;\n }\n }\n return $();\n },\n /** drop (dragged/selected PairViews) onto the list, re-ordering both the DOM and the internal array of pairs */\n _dropPairedColumns : function( ev ){\n // both required for firefox\n ev.preventDefault();\n ev.dataTransfer.dropEffect = 'move';\n\n var $nearest = this._getNearestPairedDatasetLi( ev.originalEvent.clientY );\n if( $nearest.length ){\n this.$dragging.insertBefore( $nearest );\n\n } else {\n // no nearest before - insert after last element (unpair button)\n this.$dragging.insertAfter( this.$( '.paired-columns .unpair-btn' ).last() );\n }\n // resync the creator's list of paired based on the new DOM order\n this._syncPairsToDom();\n return false;\n },\n /** resync the creator's list of paired based on the DOM order of pairs */\n _syncPairsToDom : function(){\n var newPaired = [];\n //TODO: doesn't seem wise to use the dom to store these - can't we sync another way?\n this.$( '.paired-columns .dataset.paired' ).each( function(){\n newPaired.push( $( this ).data( 'pair' ) );\n });\n //this.debug( newPaired );\n this.paired = newPaired;\n this._renderPaired();\n },\n /** drag communication with pair sub-views: dragstart */\n _pairDragstart : function( ev, pair ){\n //this.debug( '_pairDragstart', ev, pair )\n // auto select the pair causing the event and move all selected\n pair.$el.addClass( 'selected' );\n var $selected = this.$( '.paired-columns .dataset.selected' );\n this.$dragging = $selected;\n },\n /** drag communication with pair sub-views: dragend - remove the placeholder */\n _pairDragend : function( ev, pair ){\n //this.debug( '_pairDragend', ev, pair )\n $( '.element-drop-placeholder' ).remove();\n this.$dragging = null;\n },\n\n // ........................................................................ footer\n toggleExtensions : function( force ){\n var creator = this;\n creator.removeExtensions = ( force !== undefined )?( force ):( !creator.removeExtensions );\n\n _.each( creator.paired, function( pair ){\n // don't overwrite custom names\n if( pair.customizedName ){ return; }\n pair.name = creator._guessNameForPair( pair.forward, pair.reverse );\n });\n\n creator._renderPaired();\n creator._renderFooter();\n },\n\n /** handle a collection name change */\n _changeName : function( ev ){\n this._validationWarning( 'name', !!this._getName() );\n },\n\n /** check for enter key press when in the collection name and submit */\n _nameCheckForEnter : function( ev ){\n if( ev.keyCode === 13 && !this.blocking ){\n this._clickCreate();\n }\n },\n\n /** get the current collection name */\n _getName : function(){\n return _.escape( this.$( '.collection-name' ).val() );\n },\n\n /** attempt to create the current collection */\n _clickCreate : function( ev ){\n var name = this._getName();\n if( !name ){\n this._validationWarning( 'name' );\n } else if( !this.blocking ){\n this.createList();\n }\n },\n\n // ------------------------------------------------------------------------ misc\n /** debug a dataset list */\n _printList : function( list ){\n var creator = this;\n _.each( list, function( e ){\n if( list === creator.paired ){\n creator._printPair( e );\n } else {\n //creator.debug( e );\n }\n });\n },\n\n /** print a pair Object */\n _printPair : function( pair ){\n this.debug( pair.forward.name, pair.reverse.name, ': ->', pair.name );\n },\n\n /** string rep */\n toString : function(){ return 'PairedCollectionCreator'; }\n});\n\n\n//TODO: move to require text plugin and load these as text\n//TODO: underscore currently unnecc. bc no vars are used\n//TODO: better way of localizing text-nodes in long strings\n/** underscore template fns attached to class */\nPairedCollectionCreator.templates = PairedCollectionCreator.templates || {\n\n /** the skeleton */\n main : _.template([\n '
                                  ',\n '
                                  ',\n '
                                  '\n ].join('')),\n\n /** the header (not including help text) */\n header : _.template([\n '
                                  ',\n '', _l( 'More help' ), '',\n '
                                  ',\n '', _l( 'Less' ), '',\n '
                                  ',\n '
                                  ',\n '
                                  ',\n '',\n '',\n '
                                  ',\n\n '
                                  ',\n '
                                  ',\n '
                                  ',\n '
                                  ',\n '', _l( 'Unpaired forward' ), '',\n '',\n '
                                  ',\n '
                                  ',\n '',\n '
                                  ',\n '
                                  ',\n '
                                  ',\n '',\n '
                                  ',\n '
                                  ',\n '
                                  ',\n '', _l( 'Unpaired reverse' ), '',\n '',\n '
                                  ',\n '
                                  ',\n '',\n '
                                  ',\n '
                                  ',\n '
                                  ',\n '
                                  '\n ].join('')),\n\n /** the middle: unpaired, divider, and paired */\n middle : _.template([\n // contains two flex rows (rows that fill available space) and a divider btwn\n '
                                  ',\n '
                                  ',\n '
                                    ',\n '
                                    ',\n '
                                    ',\n '
                                      ',\n '
                                      ',\n '
                                      ',\n '
                                        ',\n '
                                        ',\n '
                                        ',\n '
                                        ',\n '
                                        ',\n '
                                        ',\n '
                                        ',\n '',\n '
                                        ',\n '',\n _l( 'Unpair all' ),\n '',\n '
                                        ',\n '
                                        ',\n '
                                        ',\n '
                                          ',\n '
                                          '\n ].join('')),\n\n /** creation and cancel controls */\n footer : _.template([\n '
                                          ',\n '
                                          ',\n '',\n '
                                          ',\n '
                                          ',\n '',\n '
                                          ', _l( 'Name' ), ':
                                          ',\n '
                                          ',\n '
                                          ',\n\n '
                                          ',\n '
                                          ',\n '',\n '
                                          ',\n '',\n '',\n '
                                          ',\n '
                                          ',\n\n '
                                          ',\n '',\n '
                                          ',\n '
                                          '\n ].join('')),\n\n /** help content */\n helpContent : _.template([\n '

                                          ', _l([\n 'Collections of paired datasets are ordered lists of dataset pairs (often forward and reverse reads). ',\n 'These collections can be passed to tools and workflows in order to have analyses done on each member of ',\n 'the entire group. This interface allows you to create a collection, choose which datasets are paired, ',\n 'and re-order the final collection.'\n ].join( '' )), '

                                          ',\n '

                                          ', _l([\n 'Unpaired datasets are shown in the unpaired section ',\n '(hover over the underlined words to highlight below). ',\n 'Paired datasets are shown in the paired section.',\n '

                                            To pair datasets, you can:',\n '
                                          • Click a dataset in the ',\n 'forward column ',\n 'to select it then click a dataset in the ',\n 'reverse column.',\n '
                                          • ',\n '
                                          • Click one of the \"Pair these datasets\" buttons in the ',\n 'middle column ',\n 'to pair the datasets in a particular row.',\n '
                                          • ',\n '
                                          • Click \"Auto-pair\" ',\n 'to have your datasets automatically paired based on name.',\n '
                                          • ',\n '
                                          '\n ].join( '' )), '

                                          ',\n '

                                          ', _l([\n '

                                            You can filter what is shown in the unpaired sections by:',\n '
                                          • Entering partial dataset names in either the ',\n 'forward filter or ',\n 'reverse filter.',\n '
                                          • ',\n '
                                          • Choosing from a list of preset filters by clicking the ',\n '\"Choose filters\" link.',\n '
                                          • ',\n '
                                          • Entering regular expressions to match dataset names. See: ',\n 'MDN\\'s JavaScript Regular Expression Tutorial. ',\n 'Note: forward slashes (\\\\) are not needed.',\n '
                                          • ',\n '
                                          • Clearing the filters by clicking the ',\n '\"Clear filters\" link.',\n '
                                          • ',\n '
                                          '\n ].join( '' )), '

                                          ',\n '

                                          ', _l([\n 'To unpair individual dataset pairs, click the ',\n 'unpair buttons ( ). ',\n 'Click the \"Unpair all\" link to unpair all pairs.'\n ].join( '' )), '

                                          ',\n '

                                          ', _l([\n 'You can include or remove the file extensions (e.g. \".fastq\") from your pair names by toggling the ',\n '\"Remove file extensions from pair names?\" control.'\n ].join( '' )), '

                                          ',\n '

                                          ', _l([\n 'Once your collection is complete, enter a name and ',\n 'click \"Create list\". ',\n '(Note: you do not have to pair all unpaired datasets to finish.)'\n ].join( '' )), '

                                          '\n ].join(''))\n};\n\n\n//=============================================================================\n/** a modal version of the paired collection creator */\nvar pairedCollectionCreatorModal = function _pairedCollectionCreatorModal( datasets, options ){\n\n var deferred = jQuery.Deferred(),\n creator;\n\n options = _.defaults( options || {}, {\n datasets : datasets,\n oncancel : function(){\n Galaxy.modal.hide();\n deferred.reject( 'cancelled' );\n },\n oncreate : function( creator, response ){\n Galaxy.modal.hide();\n deferred.resolve( response );\n }\n });\n\n if( !window.Galaxy || !Galaxy.modal ){\n throw new Error( 'Galaxy or Galaxy.modal not found' );\n }\n\n creator = new PairedCollectionCreator( options );\n Galaxy.modal.show({\n title : 'Create a collection of paired datasets',\n body : creator.$el,\n width : '80%',\n height : '800px',\n closing_events: true\n });\n creator.render();\n window.creator = creator;\n\n //TODO: remove modal header\n return deferred;\n};\n\n\n//=============================================================================\nfunction createListOfPairsCollection( collection ){\n var elements = collection.toJSON();\n//TODO: validate elements\n return pairedCollectionCreatorModal( elements, {\n historyId : collection.historyId\n });\n}\n\n\n//=============================================================================\n return {\n PairedCollectionCreator : PairedCollectionCreator,\n pairedCollectionCreatorModal : pairedCollectionCreatorModal,\n createListOfPairsCollection : createListOfPairsCollection\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/collection/list-of-pairs-collection-creator.js\n ** module id = 108\n ** module chunks = 3\n **/","define([\n \"mvc/collection/list-collection-creator\",\n \"mvc/history/hdca-model\",\n \"mvc/base-mvc\",\n \"utils/localization\"\n], function( LIST_CREATOR, HDCA, BASE_MVC, _l ){\n\n'use strict';\n\nvar logNamespace = 'collections';\n/*==============================================================================\nTODO:\n the paired creator doesn't really mesh with the list creator as parent\n it may be better to make an abstract super class for both\n composites may inherit from this (or vis-versa)\n PairedDatasetCollectionElementView doesn't make a lot of sense\n\n==============================================================================*/\n/** */\nvar PairedDatasetCollectionElementView = Backbone.View.extend( BASE_MVC.LoggableMixin ).extend({\n _logNamespace : logNamespace,\n\n//TODO: use proper class (DatasetDCE or NestedDCDCE (or the union of both))\n tagName : 'li',\n className : 'collection-element',\n\n initialize : function( attributes ){\n this.element = attributes.element || {};\n this.identifier = attributes.identifier;\n },\n\n render : function(){\n this.$el\n .attr( 'data-element-id', this.element.id )\n .html( this.template({ identifier: this.identifier, element: this.element }) );\n return this;\n },\n\n //TODO: lots of unused space in the element - possibly load details and display them horiz.\n template : _.template([\n '<%- identifier %>',\n '<%- element.name %>',\n ].join('')),\n\n /** remove the DOM and any listeners */\n destroy : function(){\n this.off();\n this.$el.remove();\n },\n\n /** string rep */\n toString : function(){\n return 'DatasetCollectionElementView()';\n }\n});\n\n\n// ============================================================================\nvar _super = LIST_CREATOR.ListCollectionCreator;\n\n/** An interface for building collections.\n */\nvar PairCollectionCreator = _super.extend({\n\n /** the class used to display individual elements */\n elementViewClass : PairedDatasetCollectionElementView,\n /** the class this creator will create and save */\n collectionClass : HDCA.HistoryPairDatasetCollection,\n className : 'pair-collection-creator collection-creator flex-row-container',\n\n /** override to no-op */\n _mangleDuplicateNames : function(){},\n\n // TODO: this whole pattern sucks. There needs to be two classes of problem area:\n // bad inital choices and\n // when the user has painted his/her self into a corner during creation/use-of-the-creator\n /** render the entire interface */\n render : function( speed, callback ){\n if( this.workingElements.length === 2 ){\n return _super.prototype.render.call( this, speed, callback );\n }\n return this._renderInvalid( speed, callback );\n },\n\n // ------------------------------------------------------------------------ rendering elements\n /** render forward/reverse */\n _renderList : function( speed, callback ){\n //this.debug( '-- _renderList' );\n //precondition: there are two valid elements in workingElements\n var creator = this,\n $tmp = jQuery( '
                                          ' ),\n $list = creator.$list();\n\n // lose the original views, create the new, append all at once, then call their renders\n _.each( this.elementViews, function( view ){\n view.destroy();\n creator.removeElementView( view );\n });\n $tmp.append( creator._createForwardElementView().$el );\n $tmp.append( creator._createReverseElementView().$el );\n $list.empty().append( $tmp.children() );\n _.invoke( creator.elementViews, 'render' );\n },\n\n /** create the forward element view */\n _createForwardElementView : function(){\n return this._createElementView( this.workingElements[0], { identifier: 'forward' } );\n },\n\n /** create the forward element view */\n _createReverseElementView : function(){\n return this._createElementView( this.workingElements[1], { identifier: 'reverse' } );\n },\n\n /** create an element view, cache in elementViews, and return */\n _createElementView : function( element, options ){\n var elementView = new this.elementViewClass( _.extend( options, {\n element : element,\n }));\n this.elementViews.push( elementView );\n return elementView;\n },\n\n /** swap the forward, reverse elements and re-render */\n swap : function(){\n this.workingElements = [\n this.workingElements[1],\n this.workingElements[0],\n ];\n this._renderList();\n },\n\n events : _.extend( _.clone( _super.prototype.events ), {\n 'click .swap' : 'swap',\n }),\n\n // ------------------------------------------------------------------------ templates\n //TODO: move to require text plugin and load these as text\n //TODO: underscore currently unnecc. bc no vars are used\n //TODO: better way of localizing text-nodes in long strings\n /** underscore template fns attached to class */\n templates : _.extend( _.clone( _super.prototype.templates ), {\n /** the middle: element list */\n middle : _.template([\n '',\n '
                                          ',\n '
                                          '\n ].join('')),\n\n /** help content */\n helpContent : _.template([\n '

                                          ', _l([\n 'Pair collections are permanent collections containing two datasets: one forward and one reverse. ',\n 'Often these are forward and reverse reads. The pair collections can be passed to tools and ',\n 'workflows in order to have analyses done on both datasets. This interface allows ',\n 'you to create a pair, name it, and swap which is forward and which reverse.'\n ].join( '' )), '

                                          ',\n '
                                            ',\n '
                                          • ', _l([\n 'Click the \"Swap\" link to make your forward dataset the reverse ',\n 'and the reverse dataset forward.'\n ].join( '' )), '
                                          • ',\n '
                                          • ', _l([\n 'Click the \"Cancel\" button to exit the interface.'\n ].join( '' )), '
                                          • ',\n '

                                          ',\n '

                                          ', _l([\n 'Once your collection is complete, enter a name and ',\n 'click \"Create list\".'\n ].join( '' )), '

                                          '\n ].join('')),\n\n /** a simplified page communicating what went wrong and why the user needs to reselect something else */\n invalidInitial : _.template([\n '
                                          ',\n '
                                          ',\n '',\n '<% if( _.size( problems ) ){ %>',\n _l( 'The following selections could not be included due to problems' ),\n '
                                            <% _.each( problems, function( problem ){ %>',\n '
                                          • <%- problem.element.name %>: <%- problem.text %>
                                          • ',\n '<% }); %>
                                          ',\n '<% } else if( _.size( elements ) === 0 ){ %>',\n _l( 'No datasets were selected' ), '.',\n '<% } else if( _.size( elements ) === 1 ){ %>',\n _l( 'Only one dataset was selected' ), ': <%- elements[0].name %>',\n '<% } else if( _.size( elements ) > 2 ){ %>',\n _l( 'Too many datasets were selected' ),\n ': <%- _.pluck( elements, \"name\" ).join( \", \") %>',\n '<% } %>',\n '
                                          ',\n _l( 'Two (and only two) elements are needed for the pair' ), '. ',\n _l( 'You may need to ' ),\n '', _l( 'cancel' ), ' ',\n _l( 'and reselect new elements' ), '.',\n '
                                          ',\n '
                                          ',\n '
                                          ',\n '
                                          ',\n '
                                          ',\n '
                                          ',\n '',\n // _l( 'Create a different kind of collection' ),\n '
                                          ',\n '
                                          ',\n '
                                          '\n ].join('')),\n }),\n\n // ------------------------------------------------------------------------ misc\n /** string rep */\n toString : function(){ return 'PairCollectionCreator'; }\n});\n\n\n//==============================================================================\n/** List collection flavor of collectionCreatorModal. */\nvar pairCollectionCreatorModal = function _pairCollectionCreatorModal( elements, options ){\n options = options || {};\n options.title = _l( 'Create a collection from a pair of datasets' );\n return LIST_CREATOR.collectionCreatorModal( elements, options, PairCollectionCreator );\n};\n\n\n//==============================================================================\n/** Use a modal to create a pair collection, then add it to the given history contents.\n * @returns {Deferred} resolved when the collection is added to the history.\n */\nfunction createPairCollection( contents ){\n var elements = contents.toJSON(),\n promise = pairCollectionCreatorModal( elements, {\n creationFn : function( elements, name ){\n elements = [\n { name: \"forward\", src: \"hda\", id: elements[0].id },\n { name: \"reverse\", src: \"hda\", id: elements[1].id }\n ];\n return contents.createHDCA( elements, 'paired', name );\n }\n });\n return promise;\n}\n\n//==============================================================================\n return {\n PairCollectionCreator : PairCollectionCreator,\n pairCollectionCreatorModal : pairCollectionCreatorModal,\n createPairCollection : createPairCollection,\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/collection/pair-collection-creator.js\n ** module id = 109\n ** module chunks = 3\n **/","define([\n \"mvc/ui/ui-modal\",\n \"mvc/ui/error-modal\",\n \"utils/localization\"\n], function( MODAL, ERROR_MODAL, _l ){\n\n'use strict';\n\n//==============================================================================\n/**\n * A dialog/modal that allows copying a user history or 'importing' from user\n * another. Generally called via historyCopyDialog below.\n * @type {Object}\n */\nvar CopyDialog = {\n\n // language related strings/fns\n defaultName : _.template( \"Copy of '<%- name %>'\" ),\n title : _.template( _l( 'Copying history' ) + ' \"<%- name %>\"' ),\n submitLabel : _l( 'Copy' ),\n errorMessage : _l( 'History could not be copied.' ),\n progressive : _l( 'Copying history' ),\n activeLabel : _l( 'Copy only the active, non-deleted datasets' ),\n allLabel : _l( 'Copy all datasets including deleted ones' ),\n anonWarning : _l( 'As an anonymous user, unless you login or register, you will lose your current history ' ) +\n _l( 'after copying this history. ' ),\n\n // template for modal body\n _template : _.template([\n //TODO: remove inline styles\n // show a warning message for losing current to anon users\n '<% if( isAnon ){ %>',\n '
                                          ',\n '<%- anonWarning %>',\n _l( 'You can' ),\n ' ', _l( 'login here' ), ' ', _l( 'or' ), ' ',\n ' ', _l( 'register here' ), '.',\n '
                                          ',\n '<% } %>',\n '
                                          ',\n '
                                          ',\n // TODO: could use required here and the form validators\n // NOTE: use unescaped here if escaped in the modal function below\n '\" />',\n '

                                          ',\n _l( 'Please enter a valid history title' ),\n '

                                          ',\n // if allowAll, add the option to copy deleted datasets, too\n '<% if( allowAll ){ %>',\n '
                                          ',\n '

                                          ', _l( 'Choose which datasets from the original history to include:' ), '

                                          ',\n // copy non-deleted is the default\n '/>',\n '',\n '
                                          ',\n '/>',\n '',\n '<% } %>',\n '
                                          '\n ].join( '' )),\n\n // empty modal body and let the user know the copy is happening\n _showAjaxIndicator : function _showAjaxIndicator(){\n var indicator = '

                                          ' + this.progressive + '...

                                          ';\n this.modal.$( '.modal-body' ).empty().append( indicator ).css({ 'margin-top': '8px' });\n },\n\n // (sorta) public interface - display the modal, render the form, and potentially copy the history\n // returns a jQuery.Deferred done->history copied, fail->user cancelled\n dialog : function _dialog( modal, history, options ){\n options = options || {};\n\n var dialog = this,\n deferred = jQuery.Deferred(),\n // TODO: getting a little byzantine here\n defaultCopyNameFn = options.nameFn || this.defaultName,\n defaultCopyName = defaultCopyNameFn({ name: history.get( 'name' ) }),\n // TODO: these two might be simpler as one 3 state option (all,active,no-choice)\n defaultCopyWhat = options.allDatasets? 'copy-all' : 'copy-non-deleted',\n allowAll = !_.isUndefined( options.allowAll )? options.allowAll : true,\n autoClose = !_.isUndefined( options.autoClose )? options.autoClose : true;\n\n this.modal = modal;\n\n\n // validate the name and copy if good\n function checkNameAndCopy(){\n var name = modal.$( '#copy-modal-title' ).val();\n if( !name ){\n modal.$( '.invalid-title' ).show();\n return;\n }\n // get further settings, shut down and indicate the ajax call, then hide and resolve/reject\n var copyAllDatasets = modal.$( 'input[name=\"copy-what\"]:checked' ).val() === 'copy-all';\n modal.$( 'button' ).prop( 'disabled', true );\n dialog._showAjaxIndicator();\n history.copy( true, name, copyAllDatasets )\n .done( function( response ){\n deferred.resolve( response );\n })\n .fail( function( xhr, status, message ){\n var options = { name: name, copyAllDatasets: copyAllDatasets };\n ERROR_MODAL.ajaxErrorModal( history, xhr, options, dialog.errorMessage );\n deferred.rejectWith( deferred, arguments );\n })\n .done( function(){\n if( autoClose ){ modal.hide(); }\n });\n }\n\n var originalClosingCallback = options.closing_callback;\n modal.show( _.extend( options, {\n title : this.title({ name: history.get( 'name' ) }),\n body : $( dialog._template({\n name : defaultCopyName,\n isAnon : Galaxy.user.isAnonymous(),\n allowAll : allowAll,\n copyWhat : defaultCopyWhat,\n activeLabel : this.activeLabel,\n allLabel : this.allLabel,\n anonWarning : this.anonWarning,\n })),\n buttons : _.object([\n [ _l( 'Cancel' ), function(){ modal.hide(); } ],\n [ this.submitLabel, checkNameAndCopy ]\n ]),\n height : 'auto',\n closing_events : true,\n closing_callback: function _historyCopyClose( cancelled ){\n if( cancelled ){\n deferred.reject({ cancelled : true });\n }\n if( originalClosingCallback ){\n originalClosingCallback( cancelled );\n }\n }\n }));\n\n // set the default dataset copy, autofocus the title, and set up for a simple return\n modal.$( '#copy-modal-title' ).focus().select();\n modal.$( '#copy-modal-title' ).on( 'keydown', function( ev ){\n if( ev.keyCode === 13 ){\n ev.preventDefault();\n checkNameAndCopy();\n }\n });\n\n return deferred;\n },\n};\n\n//==============================================================================\n// maintain the (slight) distinction between copy and import\n/**\n * Subclass CopyDialog to use the import language.\n */\nvar ImportDialog = _.extend( {}, CopyDialog, {\n defaultName : _.template( \"imported: <%- name %>\" ),\n title : _.template( _l( 'Importing history' ) + ' \"<%- name %>\"' ),\n submitLabel : _l( 'Import' ),\n errorMessage : _l( 'History could not be imported.' ),\n progressive : _l( 'Importing history' ),\n activeLabel : _l( 'Import only the active, non-deleted datasets' ),\n allLabel : _l( 'Import all datasets including deleted ones' ),\n anonWarning : _l( 'As an anonymous user, unless you login or register, you will lose your current history ' ) +\n _l( 'after importing this history. ' ),\n\n});\n\n//==============================================================================\n/**\n * Main interface for both history import and history copy dialogs.\n * @param {Backbone.Model} history the history to copy\n * @param {Object} options a hash\n * @return {jQuery.Deferred} promise that fails on close and succeeds on copy\n *\n * options:\n * (this object is also passed to the modal used to display the dialog and accepts modal options)\n * {Function} nameFn if defined, use this to build the default name shown to the user\n * (the fn is passed: {name: })\n * {bool} useImport if true, use the 'import' language (instead of Copy)\n * {bool} allowAll if true, allow the user to choose between copying all datasets and\n * only non-deleted datasets\n * {String} allDatasets default initial checked radio button: 'copy-all' or 'copy-non-deleted',\n */\nvar historyCopyDialog = function( history, options ){\n options = options || {};\n // create our own modal if Galaxy doesn't have one (mako tab without use_panels)\n var modal = window.parent.Galaxy.modal || new MODAL.View({});\n return options.useImport?\n ImportDialog.dialog( modal, history, options ):\n CopyDialog.dialog( modal, history, options );\n};\n\n\n//==============================================================================\n return historyCopyDialog;\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/history/copy-dialog.js\n ** module id = 110\n ** module chunks = 3\n **/","define([\n \"mvc/dataset/dataset-li-edit\",\n \"mvc/history/hda-li\",\n \"mvc/base-mvc\",\n \"utils/localization\"\n], function( DATASET_LI_EDIT, HDA_LI, BASE_MVC, _l ){\n\n'use strict';\n\n//==============================================================================\nvar _super = DATASET_LI_EDIT.DatasetListItemEdit;\n/** @class Editing view for HistoryDatasetAssociation.\n */\nvar HDAListItemEdit = _super.extend(\n/** @lends HDAListItemEdit.prototype */{\n\n className : _super.prototype.className + \" history-content\",\n\n /** In this override, only get details if in the ready state, get rerunnable if in other states.\n * Note: fetch with no 'change' event triggering to prevent automatic rendering.\n */\n _fetchModelDetails : function(){\n var view = this;\n if( view.model.inReadyState() && !view.model.hasDetails() ){\n return view.model.fetch({ silent: true });\n\n // special case the need for the rerunnable and creating_job attributes\n // needed for rendering re-run button on queued, running datasets\n } else if( !view.model.has( 'rerunnable' ) ){\n return view.model.fetch({ silent: true, data: {\n // only fetch rerunnable and creating_job to keep overhead down\n keys: [ 'rerunnable', 'creating_job' ].join(',')\n }});\n }\n return jQuery.when();\n },\n\n /** event map */\n events : _.extend( _.clone( _super.prototype.events ), {\n 'click .unhide-link' : function( ev ){ this.model.unhide(); return false; }\n }),\n\n /** string rep */\n toString : function(){\n var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n return 'HDAListItemEdit(' + modelString + ')';\n }\n});\n\n\n// ............................................................................ TEMPLATES\n/** underscore templates */\nHDAListItemEdit.prototype.templates = (function(){\n\n var warnings = _.extend( {}, _super.prototype.templates.warnings, {\n hidden : BASE_MVC.wrapTemplate([\n '<% if( !dataset.visible ){ %>',\n // add a link to unhide a dataset\n '
                                          ',\n _l( 'This dataset has been hidden' ),\n '
                                          ', _l( 'Unhide it' ), '',\n '
                                          ',\n '<% } %>'\n ], 'dataset' )\n });\n\n return _.extend( {}, _super.prototype.templates, {\n //NOTE: *steal* the HDAListItemView titleBar\n titleBar : HDA_LI.HDAListItemView.prototype.templates.titleBar,\n warnings : warnings\n });\n}());\n\n\n//==============================================================================\n return {\n HDAListItemEdit : HDAListItemEdit\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/history/hda-li-edit.js\n ** module id = 111\n ** module chunks = 3\n **/","define([\n \"mvc/history/hdca-li\",\n \"mvc/collection/collection-view-edit\",\n \"ui/fa-icon-button\",\n \"utils/localization\"\n], function( HDCA_LI, DC_VIEW_EDIT, faIconButton, _l ){\n\n'use strict';\n\n//==============================================================================\nvar _super = HDCA_LI.HDCAListItemView;\n/** @class Editing view for HistoryDatasetCollectionAssociation.\n */\nvar HDCAListItemEdit = _super.extend(\n/** @lends HDCAListItemEdit.prototype */{\n\n /** logger used to record this.log messages, commonly set to console */\n //logger : console,\n\n /** Override to return editable versions of the collection panels */\n _getFoldoutPanelClass : function(){\n switch( this.model.get( 'collection_type' ) ){\n case 'list':\n return DC_VIEW_EDIT.ListCollectionViewEdit;\n case 'paired':\n return DC_VIEW_EDIT.PairCollectionViewEdit;\n case 'list:paired':\n return DC_VIEW_EDIT.ListOfPairsCollectionViewEdit;\n case 'list:list':\n return DC_VIEW_EDIT.ListOfListsCollectionViewEdit;\n }\n throw new TypeError( 'Uknown collection_type: ' + this.model.get( 'collection_type' ) );\n },\n\n // ......................................................................... delete\n /** In this override, add the delete button. */\n _renderPrimaryActions : function(){\n this.log( this + '._renderPrimaryActions' );\n // render the display, edit attr and delete icon-buttons\n return _super.prototype._renderPrimaryActions.call( this )\n .concat([\n this._renderDeleteButton()\n ]);\n },\n\n /** Render icon-button to delete this collection. */\n _renderDeleteButton : function(){\n var self = this,\n deleted = this.model.get( 'deleted' );\n return faIconButton({\n title : deleted? _l( 'Dataset collection is already deleted' ): _l( 'Delete' ),\n classes : 'delete-btn',\n faIcon : 'fa-times',\n disabled : deleted,\n onclick : function() {\n // ...bler... tooltips being left behind in DOM (hover out never called on deletion)\n self.$el.find( '.icon-btn.delete-btn' ).trigger( 'mouseout' );\n self.model[ 'delete' ]();\n }\n });\n },\n\n // ......................................................................... misc\n /** string rep */\n toString : function(){\n var modelString = ( this.model )?( this.model + '' ):( '(no model)' );\n return 'HDCAListItemEdit(' + modelString + ')';\n }\n});\n\n//==============================================================================\n return {\n HDCAListItemEdit : HDCAListItemEdit\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/history/hdca-li-edit.js\n ** module id = 112\n ** module chunks = 3\n **/","define([\n \"mvc/history/history-model\",\n \"mvc/history/history-view-edit\",\n \"mvc/base-mvc\",\n \"utils/localization\"\n], function( HISTORY_MODEL, HISTORY_VIEW_EDIT, BASE_MVC, _l ){\n\n'use strict';\n\n// ============================================================================\n/** session storage for history panel preferences (and to maintain state)\n */\nvar HistoryViewPrefs = BASE_MVC.SessionStorageModel.extend(\n/** @lends HistoryViewPrefs.prototype */{\n defaults : {\n /** should the tags editor be shown or hidden initially? */\n tagsEditorShown : false,\n /** should the annotation editor be shown or hidden initially? */\n annotationEditorShown : false,\n ///** what is the currently focused content (dataset or collection) in the current history?\n // * (the history panel will highlight and scroll to the focused content view)\n // */\n //focusedContentId : null\n /** Current scroll position */\n scrollPosition : 0\n },\n toString : function(){\n return 'HistoryViewPrefs(' + JSON.stringify( this.toJSON() ) + ')';\n }\n});\n\n/** key string to store panel prefs (made accessible on class so you can access sessionStorage directly) */\nHistoryViewPrefs.storageKey = function storageKey(){\n return ( 'history-panel' );\n};\n\n/* =============================================================================\nTODO:\n\n============================================================================= */\nvar _super = HISTORY_VIEW_EDIT.HistoryViewEdit;\n// used in root/index.mako\n/** @class View/Controller for the user's current history model as used in the history\n * panel (current right hand panel) of the analysis page.\n *\n * The only history panel that:\n * will poll for updates.\n * displays datasets in reverse hid order.\n */\nvar CurrentHistoryView = _super.extend(/** @lends CurrentHistoryView.prototype */{\n\n className : _super.prototype.className + ' current-history-panel',\n\n /** override to use drilldown (and not foldout) for how collections are displayed */\n HDCAViewClass : _super.prototype.HDCAViewClass.extend({\n foldoutStyle : 'drilldown'\n }),\n\n emptyMsg : [\n _l( 'This history is empty' ), '. ',\n _l( 'You can ' ),\n '',\n _l( 'load your own data' ),\n '',\n _l( ' or ' ),\n '',\n _l( 'get data from an external source' ),\n ''\n ].join(''),\n\n // ......................................................................... SET UP\n /** Set up the view, set up storage, bind listeners to HistoryContents events */\n initialize : function( attributes ){\n attributes = attributes || {};\n\n // ---- persistent preferences\n /** maintain state / preferences over page loads */\n this.preferences = new HistoryViewPrefs( _.extend({\n id : HistoryViewPrefs.storageKey()\n }, _.pick( attributes, _.keys( HistoryViewPrefs.prototype.defaults ) )));\n\n _super.prototype.initialize.call( this, attributes );\n\n /** sub-views that will overlay this panel (collections) */\n this.panelStack = [];\n\n /** id of currently focused content */\n this.currentContentId = attributes.currentContentId || null;\n //NOTE: purposely not sent to localstorage since panel recreation roughly lines up with a reset of this value\n },\n\n /** Override to cache the current scroll position with a listener */\n _setUpListeners : function(){\n _super.prototype._setUpListeners.call( this );\n\n var panel = this;\n // reset scroll position when there's a new history\n this.on( 'new-model', function(){\n panel.preferences.set( 'scrollPosition', 0 );\n });\n },\n\n // ------------------------------------------------------------------------ loading history/item models\n // TODO: next three more appropriate moved to the app level\n /** (re-)loads the user's current history & contents w/ details */\n loadCurrentHistory : function(){\n return this.loadHistory( null, { url : Galaxy.root + 'history/current_history_json' });\n },\n\n /** loads a history & contents w/ details and makes them the current history */\n switchToHistory : function( historyId, attributes ){\n if( Galaxy.user.isAnonymous() ){\n this.trigger( 'error', _l( 'You must be logged in to switch histories' ), _l( 'Anonymous user' ) );\n return $.when();\n }\n return this.loadHistory( historyId, { url : Galaxy.root + 'history/set_as_current?id=' + historyId });\n },\n\n /** creates a new history on the server and sets it as the user's current history */\n createNewHistory : function( attributes ){\n if( Galaxy.user.isAnonymous() ){\n this.trigger( 'error', _l( 'You must be logged in to create histories' ), _l( 'Anonymous user' ) );\n return $.when();\n }\n return this.loadHistory( null, { url : Galaxy.root + 'history/create_new_current' });\n },\n\n /** release/free/shutdown old models and set up panel for new models */\n setModel : function( model, attributes, render ){\n _super.prototype.setModel.call( this, model, attributes, render );\n if( this.model && this.model.id ){\n this.log( 'checking for updates' );\n this.model.checkForUpdates();\n }\n return this;\n },\n\n // ------------------------------------------------------------------------ history/content event listening\n /** listening for history events */\n _setUpModelListeners : function(){\n _super.prototype._setUpModelListeners.call( this );\n // re-broadcast any model change events so that listeners don't have to re-bind to each history\n return this.listenTo( this.model, {\n 'change:nice_size change:size' : function(){\n this.trigger( 'history-size-change', this, this.model, arguments );\n },\n 'change:id' : function(){\n this.once( 'loading-done', function(){ this.model.checkForUpdates(); });\n }\n });\n },\n\n /** listening for collection events */\n _setUpCollectionListeners : function(){\n _super.prototype._setUpCollectionListeners.call( this );\n // if a hidden item is created (gen. by a workflow), moves thru the updater to the ready state,\n // then: remove it from the collection if the panel is set to NOT show hidden datasets\n this.listenTo( this.collection, 'state:ready', function( model, newState, oldState ){\n if( ( !model.get( 'visible' ) )\n && ( !this.collection.storage.includeHidden() ) ){\n this.removeItemView( model );\n }\n });\n },\n\n // ------------------------------------------------------------------------ panel rendering\n /** override to add a handler to capture the scroll position when the parent scrolls */\n _setUpBehaviors : function( $where ){\n $where = $where || this.$el;\n // console.log( '_setUpBehaviors', this.$scrollContainer( $where ).get(0), this.$list( $where ) );\n // we need to call this in _setUpBehaviors which is called after render since the $el\n // may not be attached to $el.parent and $scrollContainer() may not work\n var panel = this;\n _super.prototype._setUpBehaviors.call( panel, $where );\n\n // cache the handler to remove and re-add so we don't pile up the handlers\n if( !this._debouncedScrollCaptureHandler ){\n this._debouncedScrollCaptureHandler = _.debounce( function scrollCapture(){\n // cache the scroll position (only if visible)\n if( panel.$el.is( ':visible' ) ){\n panel.preferences.set( 'scrollPosition', $( this ).scrollTop() );\n }\n }, 40 );\n }\n\n panel.$scrollContainer( $where )\n .off( 'scroll', this._debouncedScrollCaptureHandler )\n .on( 'scroll', this._debouncedScrollCaptureHandler );\n return panel;\n },\n\n /** In this override, handle null models and move the search input to the top */\n _buildNewRender : function(){\n if( !this.model ){ return $(); }\n var $newRender = _super.prototype._buildNewRender.call( this );\n $newRender.find( '.search' ).prependTo( $newRender.find( '> .controls' ) );\n this._renderQuotaMessage( $newRender );\n return $newRender;\n },\n\n /** render the message displayed when a user is over quota and can't run jobs */\n _renderQuotaMessage : function( $whereTo ){\n $whereTo = $whereTo || this.$el;\n return $( this.templates.quotaMsg( {}, this ) ).prependTo( $whereTo.find( '.messages' ) );\n },\n\n /** In this override, get and set current panel preferences when editor is used */\n _renderTags : function( $where ){\n var panel = this;\n // render tags and show/hide based on preferences\n _super.prototype._renderTags.call( panel, $where );\n if( panel.preferences.get( 'tagsEditorShown' ) ){\n panel.tagsEditor.toggle( true );\n }\n // store preference when shown or hidden\n panel.listenTo( panel.tagsEditor, 'hiddenUntilActivated:shown hiddenUntilActivated:hidden',\n function( tagsEditor ){\n panel.preferences.set( 'tagsEditorShown', tagsEditor.hidden );\n }\n );\n },\n\n /** In this override, get and set current panel preferences when editor is used */\n _renderAnnotation : function( $where ){\n var panel = this;\n // render annotation and show/hide based on preferences\n _super.prototype._renderAnnotation.call( panel, $where );\n if( panel.preferences.get( 'annotationEditorShown' ) ){\n panel.annotationEditor.toggle( true );\n }\n // store preference when shown or hidden\n panel.listenTo( panel.annotationEditor, 'hiddenUntilActivated:shown hiddenUntilActivated:hidden',\n function( annotationEditor ){\n panel.preferences.set( 'annotationEditorShown', annotationEditor.hidden );\n }\n );\n },\n\n /** Override to scroll to cached position (in prefs) after swapping */\n _swapNewRender : function( $newRender ){\n _super.prototype._swapNewRender.call( this, $newRender );\n var panel = this;\n _.delay( function(){\n var pos = panel.preferences.get( 'scrollPosition' );\n if( pos ){\n panel.scrollTo( pos, 0 );\n }\n }, 10 );\n //TODO: is this enough of a delay on larger histories?\n\n return this;\n },\n\n // ------------------------------------------------------------------------ sub-views\n /** Override to add the current-content highlight class to currentContentId's view */\n _attachItems : function( $whereTo ){\n _super.prototype._attachItems.call( this, $whereTo );\n var panel = this;\n if( panel.currentContentId ){\n panel._setCurrentContentById( panel.currentContentId );\n }\n return this;\n },\n\n /** Override to remove any drill down panels */\n addItemView : function( model, collection, options ){\n var view = _super.prototype.addItemView.call( this, model, collection, options );\n if( !view ){ return view; }\n if( this.panelStack.length ){ return this._collapseDrilldownPanel(); }\n return view;\n },\n\n // ------------------------------------------------------------------------ collection sub-views\n /** In this override, add/remove expanded/collapsed model ids to/from web storage */\n _setUpItemViewListeners : function( view ){\n var panel = this;\n _super.prototype._setUpItemViewListeners.call( panel, view );\n // use pub-sub to: handle drilldown expansion and collapse\n return panel.listenTo( view, {\n 'expanded:drilldown' : function( v, drilldown ){\n this._expandDrilldownPanel( drilldown );\n },\n 'collapsed:drilldown' : function( v, drilldown ){\n this._collapseDrilldownPanel( drilldown );\n },\n });\n },\n\n /** display 'current content': add a visible highlight and store the id of a content item */\n setCurrentContent : function( view ){\n this.$( '.history-content.current-content' ).removeClass( 'current-content' );\n if( view ){\n view.$el.addClass( 'current-content' );\n this.currentContentId = view.model.id;\n } else {\n this.currentContentId = null;\n }\n },\n\n /** find the view with the id and then call setCurrentContent on it */\n _setCurrentContentById : function( id ){\n var view = this.viewFromModelId( id ) || null;\n this.setCurrentContent( view );\n },\n\n /** Handle drill down by hiding this panels list and controls and showing the sub-panel */\n _expandDrilldownPanel : function( drilldown ){\n this.panelStack.push( drilldown );\n // hide this panel's controls and list, set the name for back navigation, and attach to the $el\n this.$controls().add( this.$list() ).hide();\n drilldown.parentName = this.model.get( 'name' );\n drilldown.delegateEvents().render().$el.appendTo( this.$el );\n },\n\n /** Handle drilldown close by freeing the panel and re-rendering this panel */\n _collapseDrilldownPanel : function( drilldown ){\n this.panelStack.pop();\n //TODO: MEM: free the panel\n this.$controls().add( this.$list() ).show();\n },\n\n // ........................................................................ panel events\n /** event map */\n events : _.extend( _.clone( _super.prototype.events ), {\n // the two links in the empty message\n 'click .uploader-link' : function( ev ){ Galaxy.upload.show( ev ); },\n 'click .get-data-link' : function( ev ){\n var $toolMenu = $( '.toolMenuContainer' );\n $toolMenu.parent().scrollTop( 0 );\n $toolMenu.find( 'span:contains(\"Get Data\")' ).click();\n }\n }),\n\n // ........................................................................ external objects/MVC\n listenToGalaxy : function( galaxy ){\n this.listenTo( galaxy, {\n // when the galaxy_main iframe is loaded with a new page,\n // compare the url to the following list and if there's a match\n // pull the id from url and indicate in the history view that\n // the dataset with that id is the 'current'ly active dataset\n 'galaxy_main:load': function( data ){\n var pathToMatch = data.fullpath;\n var hdaId = null;\n var useToURLRegexMap = {\n 'display' : /datasets\\/([a-f0-9]+)\\/display/,\n 'edit' : /datasets\\/([a-f0-9]+)\\/edit/,\n 'report_error' : /dataset\\/errors\\?id=([a-f0-9]+)/,\n 'rerun' : /tool_runner\\/rerun\\?id=([a-f0-9]+)/,\n 'show_params' : /datasets\\/([a-f0-9]+)\\/show_params/,\n // no great way to do this here? (leave it in the dataset event handlers above?)\n // 'visualization' : 'visualization',\n };\n _.find( useToURLRegexMap, function( regex, use ){\n // grab the more specific match result (1), save, and use it as the find flag\n hdaId = _.result( pathToMatch.match( regex ), 1 );\n return hdaId;\n });\n // need to type mangle to go from web route to history contents\n this._setCurrentContentById( hdaId? ( 'dataset-' + hdaId ) : null );\n },\n // when the center panel is given a new view, clear the current indicator\n 'center-panel:load': function( view ){\n this._setCurrentContentById();\n }\n });\n },\n\n //TODO: remove quota meter from panel and remove this\n /** add listeners to an external quota meter (mvc/user/user-quotameter.js) */\n connectToQuotaMeter : function( quotaMeter ){\n if( !quotaMeter ){\n return this;\n }\n // show/hide the 'over quota message' in the history when the meter tells it to\n this.listenTo( quotaMeter, 'quota:over', this.showQuotaMessage );\n this.listenTo( quotaMeter, 'quota:under', this.hideQuotaMessage );\n\n // having to add this to handle re-render of hview while overquota (the above do not fire)\n this.on( 'rendered rendered:initial', function(){\n if( quotaMeter && quotaMeter.isOverQuota() ){\n this.showQuotaMessage();\n }\n });\n return this;\n },\n\n /** Override to preserve the quota message */\n clearMessages : function( ev ){\n var $target = !_.isUndefined( ev )?\n $( ev.currentTarget )\n :this.$messages().children( '[class$=\"message\"]' );\n $target = $target.not( '.quota-message' );\n $target.fadeOut( this.fxSpeed, function(){\n $( this ).remove();\n });\n return this;\n },\n\n /** Show the over quota message (which happens to be in the history panel).\n */\n showQuotaMessage : function(){\n var $msg = this.$( '.quota-message' );\n if( $msg.is( ':hidden' ) ){ $msg.slideDown( this.fxSpeed ); }\n },\n\n /** Hide the over quota message (which happens to be in the history panel).\n */\n hideQuotaMessage : function(){\n var $msg = this.$( '.quota-message' );\n if( !$msg.is( ':hidden' ) ){ $msg.slideUp( this.fxSpeed ); }\n },\n\n // ........................................................................ options menu\n //TODO: remove to batch\n /** unhide any hidden datasets */\n unhideHidden : function() {\n var self = this;\n if( confirm( _l( 'Really unhide all hidden datasets?' ) ) ){\n // get all hidden, regardless of deleted/purged\n return self.model.contents._filterAndUpdate(\n { visible: false, deleted: '', purged: '' },\n { visible : true }\n ).done( function(){\n // TODO: would be better to render these as they're unhidden instead of all at once\n if( !self.model.contents.includeHidden ){\n self.renderItems();\n }\n });\n }\n return jQuery.when();\n },\n\n /** delete any hidden datasets */\n deleteHidden : function() {\n var self = this;\n if( confirm( _l( 'Really delete all hidden datasets?' ) ) ){\n return self.model.contents._filterAndUpdate(\n // get all hidden, regardless of deleted/purged\n { visible: false, deleted: '', purged: '' },\n // both delete *and* unhide them\n { deleted : true, visible: true }\n );\n }\n return jQuery.when();\n },\n\n /** Return a string rep of the history */\n toString : function(){\n return 'CurrentHistoryView(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';\n }\n});\n\n\n//------------------------------------------------------------------------------ TEMPLATES\nCurrentHistoryView.prototype.templates = (function(){\n\n var quotaMsgTemplate = BASE_MVC.wrapTemplate([\n '
                                          ',\n _l( 'You are over your disk quota' ), '. ',\n _l( 'Tool execution is on hold until your disk usage drops below your allocated quota' ), '.',\n '
                                          '\n ], 'history' );\n return _.extend( _.clone( _super.prototype.templates ), {\n quotaMsg : quotaMsgTemplate\n });\n\n}());\n\n\n//==============================================================================\n return {\n CurrentHistoryView : CurrentHistoryView\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/history/history-view-edit-current.js\n ** module id = 113\n ** module chunks = 3\n **/","define([\n \"mvc/history/history-view\",\n \"mvc/history/history-contents\",\n \"mvc/dataset/states\",\n \"mvc/history/hda-model\",\n \"mvc/history/hda-li-edit\",\n \"mvc/history/hdca-li-edit\",\n \"mvc/tag\",\n \"mvc/annotation\",\n \"mvc/collection/list-collection-creator\",\n \"mvc/collection/pair-collection-creator\",\n \"mvc/collection/list-of-pairs-collection-creator\",\n \"ui/fa-icon-button\",\n \"mvc/ui/popup-menu\",\n \"mvc/base-mvc\",\n \"utils/localization\",\n \"ui/editable-text\",\n], function(\n HISTORY_VIEW,\n HISTORY_CONTENTS,\n STATES,\n HDA_MODEL,\n HDA_LI_EDIT,\n HDCA_LI_EDIT,\n TAGS,\n ANNOTATIONS,\n LIST_COLLECTION_CREATOR,\n PAIR_COLLECTION_CREATOR,\n LIST_OF_PAIRS_COLLECTION_CREATOR,\n faIconButton,\n PopupMenu,\n BASE_MVC,\n _l\n){\n\n'use strict';\n\n/* =============================================================================\nTODO:\n\n============================================================================= */\nvar _super = HISTORY_VIEW.HistoryView;\n// base class for history-view-edit-current and used as-is in history/view.mako\n/** @class Editable View/Controller for the history model.\n *\n * Allows:\n * (everything HistoryView allows)\n * changing the name\n * displaying and editing tags and annotations\n * multi-selection and operations on mulitple content items\n */\nvar HistoryViewEdit = _super.extend(\n/** @lends HistoryViewEdit.prototype */{\n\n /** class to use for constructing the HistoryDatasetAssociation views */\n HDAViewClass : HDA_LI_EDIT.HDAListItemEdit,\n /** class to use for constructing the HistoryDatasetCollectionAssociation views */\n HDCAViewClass : HDCA_LI_EDIT.HDCAListItemEdit,\n\n // ......................................................................... SET UP\n /** Set up the view, set up storage, bind listeners to HistoryContents events\n * @param {Object} attributes\n */\n initialize : function( attributes ){\n attributes = attributes || {};\n _super.prototype.initialize.call( this, attributes );\n\n // ---- set up instance vars\n /** editor for tags - sub-view */\n this.tagsEditor = null;\n /** editor for annotations - sub-view */\n this.annotationEditor = null;\n\n /** allow user purge of dataset files? */\n this.purgeAllowed = attributes.purgeAllowed || false;\n\n // states/modes the panel can be in\n /** is the panel currently showing the dataset selection controls? */\n this.annotationEditorShown = attributes.annotationEditorShown || false;\n this.tagsEditorShown = attributes.tagsEditorShown || false;\n },\n\n /** Override to handle history as drag-drop target */\n _setUpListeners : function(){\n _super.prototype._setUpListeners.call( this );\n return this.on({\n 'droptarget:drop': function( ev, data ){\n // process whatever was dropped and re-hide the drop target\n this.dataDropped( data );\n this.dropTargetOff();\n },\n 'view:attached view:removed': function(){\n this._renderCounts();\n },\n 'search:loading-progress': this._renderSearchProgress,\n 'search:searching': this._renderSearchFindings,\n });\n },\n\n // ------------------------------------------------------------------------ listeners\n /** listening for history and HDA events */\n _setUpModelListeners : function(){\n _super.prototype._setUpModelListeners.call( this );\n this.listenTo( this.model, 'change:size', this.updateHistoryDiskSize );\n return this;\n },\n\n /** listening for collection events */\n _setUpCollectionListeners : function(){\n _super.prototype._setUpCollectionListeners.call( this );\n this.listenTo( this.collection, {\n 'change:deleted': this._handleItemDeletedChange,\n 'change:visible': this._handleItemVisibleChange,\n 'change:purged' : function( model ){\n // hafta get the new nice-size w/o the purged model\n this.model.fetch();\n },\n // loading indicators for deleted/hidden\n 'fetching-deleted' : function( collection ){\n this.$( '> .controls .deleted-count' )\n .html( '' + _l( 'loading...' ) + '' );\n },\n 'fetching-hidden' : function( collection ){\n this.$( '> .controls .hidden-count' )\n .html( '' + _l( 'loading...' ) + '' );\n },\n 'fetching-deleted-done fetching-hidden-done' : this._renderCounts,\n });\n return this;\n },\n\n // ------------------------------------------------------------------------ panel rendering\n /** In this override, add tag and annotation editors and a btn to toggle the selectors */\n _buildNewRender : function(){\n // create a new render using a skeleton template, render title buttons, render body, and set up events, etc.\n var $newRender = _super.prototype._buildNewRender.call( this );\n if( !this.model ){ return $newRender; }\n\n if( Galaxy && Galaxy.user && Galaxy.user.id && Galaxy.user.id === this.model.get( 'user_id' ) ){\n this._renderTags( $newRender );\n this._renderAnnotation( $newRender );\n }\n return $newRender;\n },\n\n /** Update the history size display (curr. upper right of panel). */\n updateHistoryDiskSize : function(){\n this.$( '.history-size' ).text( this.model.get( 'nice_size' ) );\n },\n\n /** override to render counts when the items are rendered */\n renderItems : function( $whereTo ){\n var views = _super.prototype.renderItems.call( this, $whereTo );\n if( !this.searchFor ){ this._renderCounts( $whereTo ); }\n return views;\n },\n\n /** override to show counts, what's deleted/hidden, and links to toggle those */\n _renderCounts : function( $whereTo ){\n $whereTo = $whereTo instanceof jQuery? $whereTo : this.$el;\n var html = this.templates.counts( this.model.toJSON(), this );\n return $whereTo.find( '> .controls .subtitle' ).html( html );\n },\n\n /** render the tags sub-view controller */\n _renderTags : function( $where ){\n var panel = this;\n this.tagsEditor = new TAGS.TagsEditor({\n model : this.model,\n el : $where.find( '.controls .tags-display' ),\n onshowFirstTime : function(){ this.render(); },\n // show hide sub-view tag editors when this is shown/hidden\n onshow : function(){\n panel.toggleHDATagEditors( true, panel.fxSpeed );\n },\n onhide : function(){\n panel.toggleHDATagEditors( false, panel.fxSpeed );\n },\n $activator : faIconButton({\n title : _l( 'Edit history tags' ),\n classes : 'history-tag-btn',\n faIcon : 'fa-tags'\n }).appendTo( $where.find( '.controls .actions' ) )\n });\n },\n /** render the annotation sub-view controller */\n _renderAnnotation : function( $where ){\n var panel = this;\n this.annotationEditor = new ANNOTATIONS.AnnotationEditor({\n model : this.model,\n el : $where.find( '.controls .annotation-display' ),\n onshowFirstTime : function(){ this.render(); },\n // show hide sub-view view annotation editors when this is shown/hidden\n onshow : function(){\n panel.toggleHDAAnnotationEditors( true, panel.fxSpeed );\n },\n onhide : function(){\n panel.toggleHDAAnnotationEditors( false, panel.fxSpeed );\n },\n $activator : faIconButton({\n title : _l( 'Edit history annotation' ),\n classes : 'history-annotate-btn',\n faIcon : 'fa-comment'\n }).appendTo( $where.find( '.controls .actions' ) )\n });\n },\n\n /** Set up HistoryViewEdit js/widget behaviours\n * In this override, make the name editable\n */\n _setUpBehaviors : function( $where ){\n $where = $where || this.$el;\n _super.prototype._setUpBehaviors.call( this, $where );\n if( !this.model ){ return; }\n\n // anon users shouldn't have access to any of the following\n if( ( !Galaxy.user || Galaxy.user.isAnonymous() )\n || ( Galaxy.user.id !== this.model.get( 'user_id' ) ) ){\n return;\n }\n\n var panel = this,\n nameSelector = '> .controls .name';\n $where.find( nameSelector )\n .attr( 'title', _l( 'Click to rename history' ) )\n .tooltip({ placement: 'bottom' })\n .make_text_editable({\n on_finish: function( newName ){\n var previousName = panel.model.get( 'name' );\n if( newName && newName !== previousName ){\n panel.$el.find( nameSelector ).text( newName );\n panel.model.save({ name: newName })\n .fail( function(){\n panel.$el.find( nameSelector ).text( panel.model.previous( 'name' ) );\n });\n } else {\n panel.$el.find( nameSelector ).text( previousName );\n }\n }\n });\n },\n\n /** return a new popup menu for choosing a multi selection action\n * ajax calls made for multiple datasets are queued\n */\n multiselectActions : function(){\n var panel = this,\n actions = [\n { html: _l( 'Hide datasets' ), func: function(){\n var action = HDA_MODEL.HistoryDatasetAssociation.prototype.hide;\n panel.getSelectedModels().ajaxQueue( action );\n }\n },\n { html: _l( 'Unhide datasets' ), func: function(){\n var action = HDA_MODEL.HistoryDatasetAssociation.prototype.unhide;\n panel.getSelectedModels().ajaxQueue( action );\n }\n },\n { html: _l( 'Delete datasets' ), func: function(){\n var action = HDA_MODEL.HistoryDatasetAssociation.prototype['delete'];\n panel.getSelectedModels().ajaxQueue( action );\n }\n },\n { html: _l( 'Undelete datasets' ), func: function(){\n var action = HDA_MODEL.HistoryDatasetAssociation.prototype.undelete;\n panel.getSelectedModels().ajaxQueue( action );\n }\n }\n ];\n if( panel.purgeAllowed ){\n actions.push({\n html: _l( 'Permanently delete datasets' ), func: function(){\n if( confirm( _l( 'This will permanently remove the data in your datasets. Are you sure?' ) ) ){\n var action = HDA_MODEL.HistoryDatasetAssociation.prototype.purge;\n panel.getSelectedModels().ajaxQueue( action );\n }\n }\n });\n }\n actions = actions.concat( panel._collectionActions() );\n return actions;\n },\n\n /** */\n _collectionActions : function(){\n var panel = this;\n return [\n { html: _l( 'Build Dataset List' ), func: function() {\n LIST_COLLECTION_CREATOR.createListCollection( panel.getSelectedModels() )\n .done( function(){ panel.model.refresh(); });\n }\n },\n // TODO: Only show quick pair if two things selected.\n { html: _l( 'Build Dataset Pair' ), func: function() {\n PAIR_COLLECTION_CREATOR.createPairCollection( panel.getSelectedModels() )\n .done( function(){ panel.model.refresh(); });\n }\n },\n { html: _l( 'Build List of Dataset Pairs' ), func: function() {\n LIST_OF_PAIRS_COLLECTION_CREATOR.createListOfPairsCollection( panel.getSelectedModels() )\n .done( function(){ panel.model.refresh(); });\n }\n },\n ];\n },\n\n // ------------------------------------------------------------------------ sub-views\n /** In this override, add purgeAllowed and whether tags/annotation editors should be shown */\n _getItemViewOptions : function( model ){\n var options = _super.prototype._getItemViewOptions.call( this, model );\n _.extend( options, {\n purgeAllowed : this.purgeAllowed,\n tagsEditorShown : ( this.tagsEditor && !this.tagsEditor.hidden ),\n annotationEditorShown : ( this.annotationEditor && !this.annotationEditor.hidden )\n });\n return options;\n },\n\n /** If this item is deleted and we're not showing deleted items, remove the view\n * @param {Model} the item model to check\n */\n _handleItemDeletedChange : function( itemModel ){\n if( itemModel.get( 'deleted' ) ){\n this._handleItemDeletion( itemModel );\n } else {\n this._handleItemUndeletion( itemModel );\n }\n this._renderCounts();\n },\n\n _handleItemDeletion : function( itemModel ){\n var contentsShown = this.model.get( 'contents_active' );\n contentsShown.deleted += 1;\n contentsShown.active -= 1;\n if( !this.model.contents.includeDeleted ){\n this.removeItemView( itemModel );\n }\n this.model.set( 'contents_active', contentsShown );\n },\n\n _handleItemUndeletion : function( itemModel ){\n var contentsShown = this.model.get( 'contents_active' );\n contentsShown.deleted -= 1;\n if( !this.model.contents.includeDeleted ){\n contentsShown.active -= 1;\n }\n this.model.set( 'contents_active', contentsShown );\n },\n\n /** If this item is hidden and we're not showing hidden items, remove the view\n * @param {Model} the item model to check\n */\n _handleItemVisibleChange : function( itemModel ){\n if( itemModel.hidden() ){\n this._handleItemHidden( itemModel );\n } else {\n this._handleItemUnhidden( itemModel );\n }\n this._renderCounts();\n },\n\n _handleItemHidden : function( itemModel ){\n var contentsShown = this.model.get( 'contents_active' );\n contentsShown.hidden += 1;\n contentsShown.active -= 1;\n if( !this.model.contents.includeHidden ){\n this.removeItemView( itemModel );\n }\n this.model.set( 'contents_active', contentsShown );\n },\n\n _handleItemUnhidden : function( itemModel ){\n var contentsShown = this.model.get( 'contents_active' );\n contentsShown.hidden -= 1;\n if( !this.model.contents.includeHidden ){\n contentsShown.active -= 1;\n }\n this.model.set( 'contents_active', contentsShown );\n },\n\n /** toggle the visibility of each content's tagsEditor applying all the args sent to this function */\n toggleHDATagEditors : function( showOrHide, speed ){\n _.each( this.views, function( view ){\n if( view.tagsEditor ){\n view.tagsEditor.toggle( showOrHide, speed );\n }\n });\n },\n\n /** toggle the visibility of each content's annotationEditor applying all the args sent to this function */\n toggleHDAAnnotationEditors : function( showOrHide, speed ){\n _.each( this.views, function( view ){\n if( view.annotationEditor ){\n view.annotationEditor.toggle( showOrHide, speed );\n }\n });\n },\n\n // ------------------------------------------------------------------------ panel events\n /** event map */\n events : _.extend( _.clone( _super.prototype.events ), {\n 'click .show-selectors-btn' : 'toggleSelectors',\n 'click .toggle-deleted-link' : function( ev ){ this.toggleShowDeleted(); },\n 'click .toggle-hidden-link' : function( ev ){ this.toggleShowHidden(); }\n }),\n\n // ------------------------------------------------------------------------ search\n _renderSearchProgress : function( limit, offset ){\n var stop = limit + offset;\n return this.$( '> .controls .subtitle' ).html([\n '',\n _l( 'Searching ' ), stop, '/', this.model.contentsShown(),\n ''\n ].join(''));\n },\n\n /** override to display number found in subtitle */\n _renderSearchFindings : function(){\n this.$( '> .controls .subtitle' ).html([\n _l( 'Found' ), this.views.length\n ].join(' '));\n return this;\n },\n\n // ------------------------------------------------------------------------ as drop target\n /** turn all the drag and drop handlers on and add some help text above the drop area */\n dropTargetOn : function(){\n if( this.dropTarget ){ return this; }\n this.dropTarget = true;\n\n //TODO: to init\n var dropHandlers = {\n 'dragenter' : _.bind( this.dragenter, this ),\n 'dragover' : _.bind( this.dragover, this ),\n 'dragleave' : _.bind( this.dragleave, this ),\n 'drop' : _.bind( this.drop, this )\n };\n\n var $dropTarget = this._renderDropTarget();\n this.$list().before([ this._renderDropTargetHelp(), $dropTarget ]);\n for( var evName in dropHandlers ){\n if( dropHandlers.hasOwnProperty( evName ) ){\n //console.debug( evName, dropHandlers[ evName ] );\n $dropTarget.on( evName, dropHandlers[ evName ] );\n }\n }\n return this;\n },\n\n /** render a box to serve as a 'drop here' area on the history */\n _renderDropTarget : function(){\n this.$( '.history-drop-target' ).remove();\n return $( '
                                          ' ).addClass( 'history-drop-target' );\n },\n\n /** tell the user how it works */\n _renderDropTargetHelp : function(){\n this.$( '.history-drop-target-help' ).remove();\n return $( '
                                          ' ).addClass( 'history-drop-target-help' )\n .text( _l( 'Drag datasets here to copy them to the current history' ) );\n },\n\n /** shut down drag and drop event handlers and remove drop target */\n dropTargetOff : function(){\n if( !this.dropTarget ){ return this; }\n //this.log( 'dropTargetOff' );\n this.dropTarget = false;\n var dropTarget = this.$( '.history-drop-target' ).get(0);\n for( var evName in this._dropHandlers ){\n if( this._dropHandlers.hasOwnProperty( evName ) ){\n dropTarget.off( evName, this._dropHandlers[ evName ] );\n }\n }\n this.$( '.history-drop-target' ).remove();\n this.$( '.history-drop-target-help' ).remove();\n return this;\n },\n /** toggle the target on/off */\n dropTargetToggle : function(){\n if( this.dropTarget ){\n this.dropTargetOff();\n } else {\n this.dropTargetOn();\n }\n return this;\n },\n\n dragenter : function( ev ){\n //console.debug( 'dragenter:', this, ev );\n ev.preventDefault();\n ev.stopPropagation();\n this.$( '.history-drop-target' ).css( 'border', '2px solid black' );\n },\n dragover : function( ev ){\n ev.preventDefault();\n ev.stopPropagation();\n },\n dragleave : function( ev ){\n //console.debug( 'dragleave:', this, ev );\n ev.preventDefault();\n ev.stopPropagation();\n this.$( '.history-drop-target' ).css( 'border', '1px dashed black' );\n },\n /** when (text) is dropped try to parse as json and trigger an event */\n drop : function( ev ){\n ev.preventDefault();\n //ev.stopPropagation();\n\n var self = this;\n var dataTransfer = ev.originalEvent.dataTransfer;\n var data = dataTransfer.getData( \"text\" );\n\n dataTransfer.dropEffect = 'move';\n try {\n data = JSON.parse( data );\n } catch( err ){\n self.warn( 'error parsing JSON from drop:', data );\n }\n\n self.trigger( 'droptarget:drop', ev, data, self );\n return false;\n },\n\n /** handler that copies data into the contents */\n dataDropped : function( data ){\n var self = this;\n // HDA: dropping will copy it to the history\n if( _.isObject( data ) && data.model_class === 'HistoryDatasetAssociation' && data.id ){\n if( self.contents.currentPage !== 0 ){\n return self.contents.fetchPage( 0 )\n .then( function(){\n return self.model.contents.copy( data.id );\n });\n }\n return self.model.contents.copy( data.id );\n }\n return jQuery.when();\n },\n\n // ........................................................................ misc\n /** Return a string rep of the history */\n toString : function(){\n return 'HistoryViewEdit(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';\n }\n});\n\n//------------------------------------------------------------------------------ TEMPLATES\nHistoryViewEdit.prototype.templates = (function(){\n\n var countsTemplate = BASE_MVC.wrapTemplate([\n '<% var shown = Math.max( view.views.length, history.contents_active.active ) %>',\n '<% if( shown ){ %>',\n '',\n '<%- shown %> ', _l( 'shown' ),\n '',\n '<% } %>',\n\n '<% if( history.contents_active.deleted ){ %>',\n '',\n '<% if( view.model.contents.includeDeleted ){ %>',\n '',\n _l( 'hide deleted' ),\n '',\n '<% } else { %>',\n '<%- history.contents_active.deleted %> ',\n '',\n _l( 'deleted' ),\n '',\n '<% } %>',\n '',\n '<% } %>',\n\n '<% if( history.contents_active.hidden ){ %>',\n '',\n '<% if( view.model.contents.includeHidden ){ %>',\n '',\n _l( 'hide hidden' ),\n '',\n '<% } else { %>',\n '<%- history.contents_active.hidden %> ',\n '',\n _l( 'hidden' ),\n '',\n '<% } %>',\n '',\n '<% } %>',\n ], 'history' );\n\n return _.extend( _.clone( _super.prototype.templates ), {\n counts : countsTemplate\n });\n}());\n\n\n//==============================================================================\n return {\n HistoryViewEdit : HistoryViewEdit\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/history/history-view-edit.js\n ** module id = 114\n ** module chunks = 3\n **/","define([\n \"mvc/list/list-view\",\n \"mvc/history/history-model\",\n \"mvc/history/history-contents\",\n \"mvc/history/history-preferences\",\n \"mvc/history/hda-li\",\n \"mvc/history/hdca-li\",\n \"mvc/user/user-model\",\n \"mvc/ui/error-modal\",\n \"ui/fa-icon-button\",\n \"mvc/base-mvc\",\n \"utils/localization\",\n \"ui/search-input\"\n], function(\n LIST_VIEW,\n HISTORY_MODEL,\n HISTORY_CONTENTS,\n HISTORY_PREFS,\n HDA_LI,\n HDCA_LI,\n USER,\n ERROR_MODAL,\n faIconButton,\n BASE_MVC,\n _l\n){\n'use strict';\n\n/* =============================================================================\nTODO:\n\n============================================================================= */\n/** @class non-editable, read-only View/Controller for a history model.\n * Allows:\n * changing the loaded history\n * displaying data, info, and download\n * tracking history attrs: size, tags, annotations, name, etc.\n * Does not allow:\n * changing the name\n */\nvar _super = LIST_VIEW.ModelListPanel;\nvar HistoryView = _super.extend(\n/** @lends HistoryView.prototype */{\n _logNamespace : 'history',\n\n /** class to use for constructing the HDA views */\n HDAViewClass : HDA_LI.HDAListItemView,\n /** class to use for constructing the HDCA views */\n HDCAViewClass : HDCA_LI.HDCAListItemView,\n /** class to used for constructing collection of sub-view models */\n collectionClass : HISTORY_CONTENTS.HistoryContents,\n /** key of attribute in model to assign to this.collection */\n modelCollectionKey : 'contents',\n\n tagName : 'div',\n className : _super.prototype.className + ' history-panel',\n\n /** string to display when the collection is empty */\n emptyMsg : _l( 'This history is empty' ),\n /** displayed when no items match the search terms */\n noneFoundMsg : _l( 'No matching datasets found' ),\n /** string used for search placeholder */\n searchPlaceholder : _l( 'search datasets' ),\n\n /** @type {Number} ms to wait after history load to fetch/decorate hdcas with element_count */\n FETCH_COLLECTION_COUNTS_DELAY : 2000,\n\n // ......................................................................... SET UP\n /** Set up the view, bind listeners.\n * @param {Object} attributes optional settings for the panel\n */\n initialize : function( attributes ){\n _super.prototype.initialize.call( this, attributes );\n // ---- instance vars\n // control contents/behavior based on where (and in what context) the panel is being used\n /** where should pages from links be displayed? (default to new tab/window) */\n this.linkTarget = attributes.linkTarget || '_blank';\n },\n\n /** create and return a collection for when none is initially passed */\n _createDefaultCollection : function(){\n // override\n return new this.collectionClass([], { history: this.model });\n },\n\n /** In this override, clear the update timer on the model */\n freeModel : function(){\n _super.prototype.freeModel.call( this );\n if( this.model ){\n this.model.clearUpdateTimeout();\n }\n return this;\n },\n\n /** create any event listeners for the panel\n * @fires: rendered:initial on the first render\n * @fires: empty-history when switching to a history with no contents or creating a new history\n */\n _setUpListeners : function(){\n _super.prototype._setUpListeners.call( this );\n this.on({\n error : function( model, xhr, options, msg, details ){\n this.errorHandler( model, xhr, options, msg, details );\n },\n 'loading-done' : function(){\n var self = this;\n // after the initial load, decorate with more time consuming fields (like HDCA element_counts)\n _.delay( function(){\n self.model.contents.fetchCollectionCounts();\n }, self.FETCH_COLLECTION_COUNTS_DELAY );\n },\n 'views:ready view:attached view:removed' : function( view ){\n this._renderSelectButton();\n },\n 'view:attached' : function( view ){\n this.scrollTo(0);\n },\n });\n // this.on( 'all', function(){ console.debug( arguments ); });\n },\n\n // ------------------------------------------------------------------------ loading history/hda models\n /** load the history with the given id then it's contents, sending ajax options to both */\n loadHistory : function( historyId, options, contentsOptions ){\n contentsOptions = _.extend( contentsOptions || { silent: true });\n this.info( 'loadHistory:', historyId, options, contentsOptions );\n var self = this;\n self.setModel( new HISTORY_MODEL.History({ id : historyId }) );\n\n contentsOptions.silent = true;\n self.trigger( 'loading' );\n return self.model\n .fetchWithContents( options, contentsOptions )\n .always( function(){\n self.render();\n self.trigger( 'loading-done' );\n });\n },\n\n /** convenience alias to the model. Updates the item list only (not the history) */\n refreshContents : function( options ){\n if( this.model ){\n return this.model.refresh( options );\n }\n // may have callbacks - so return an empty promise\n return $.when();\n },\n\n /** Override to reset web storage when the id changes (since it needs the id) */\n _setUpCollectionListeners : function(){\n _super.prototype._setUpCollectionListeners.call( this );\n return this.listenTo( this.collection, {\n // 'all' : function(){ console.log( this.collection + ':', arguments ); },\n 'fetching-more' : function(){\n this._toggleContentsLoadingIndicator( true );\n this.$emptyMessage().hide();\n },\n 'fetching-more-done': function(){ this._toggleContentsLoadingIndicator( false ); },\n });\n },\n\n // ------------------------------------------------------------------------ panel rendering\n /** hide the $el and display a loading indicator (in the $el's parent) when loading new data */\n _showLoadingIndicator : function( msg, speed, callback ){\n var $indicator = $( '
                                          ' );\n this.$el.html( $indicator.text( msg ).slideDown( !_.isUndefined( speed )? speed : this.fxSpeed ) );\n },\n\n /** hide the loading indicator */\n _hideLoadingIndicator : function( speed ){\n // make speed a bit slower to compensate for slow rendering of up to 500 contents\n this.$( '.loading-indicator' ).slideUp( !_.isUndefined( speed )? speed : ( this.fxSpeed + 200 ), function(){\n $( this ).remove();\n });\n },\n\n /** In this override, add a btn to toggle the selectors */\n _buildNewRender : function(){\n var $newRender = _super.prototype._buildNewRender.call( this );\n this._renderSelectButton( $newRender );\n return $newRender;\n },\n\n /** button for starting select mode */\n _renderSelectButton : function( $where ){\n $where = $where || this.$el;\n // do not render selector option if no actions\n if( !this.multiselectActions().length ){\n return null;\n }\n // do not render (and remove even) if nothing to select\n if( !this.views.length ){\n this.hideSelectors();\n $where.find( '.controls .actions .show-selectors-btn' ).remove();\n return null;\n }\n // don't bother rendering if there's one already\n var $existing = $where.find( '.controls .actions .show-selectors-btn' );\n if( $existing.length ){\n return $existing;\n }\n\n return faIconButton({\n title : _l( 'Operations on multiple datasets' ),\n classes : 'show-selectors-btn',\n faIcon : 'fa-check-square-o'\n }).prependTo( $where.find( '.controls .actions' ) );\n },\n\n /** override to avoid showing intial empty message using contents_active */\n _renderEmptyMessage : function( $whereTo ){\n var self = this;\n var $emptyMsg = self.$emptyMessage( $whereTo );\n\n var empty = self.model.get( 'contents_active' ).active <= 0;\n if( empty ){\n return $emptyMsg.empty().append( self.emptyMsg ).show();\n\n } else if( self.searchFor && self.model.contents.haveSearchDetails() && !self.views.length ){\n return $emptyMsg.empty().append( self.noneFoundMsg ).show();\n }\n $emptyMsg.hide();\n return $();\n },\n\n /** the scroll container for this panel - can be $el, $el.parent(), or grandparent depending on context */\n $scrollContainer : function( $where ){\n // override or set via attributes.$scrollContainer\n return this.$list( $where );\n },\n\n // ------------------------------------------------------------------------ subviews\n _toggleContentsLoadingIndicator : function( show ){\n if( !show ){\n this.$list().find( '.contents-loading-indicator' ).remove();\n } else {\n this.$list().html( '
                                          '\n + '
                                          ' );\n }\n },\n\n /** override to render pagination also */\n renderItems: function( $whereTo ){\n // console.log( this + '.renderItems-----------------', new Date() );\n $whereTo = $whereTo || this.$el;\n var self = this;\n var $list = self.$list( $whereTo );\n\n // TODO: bootstrap hack to remove orphaned tooltips\n $( '.tooltip' ).remove();\n\n $list.empty();\n self.views = [];\n\n var models = self._filterCollection();\n if( models.length ){\n self._renderPagination( $whereTo );\n self.views = self._renderSomeItems( models, $list );\n } else {\n // TODO: consolidate with _renderPagination above by (???) passing in models/length?\n $whereTo.find( '> .controls .list-pagination' ).empty();\n }\n self._renderEmptyMessage( $whereTo ).toggle( !models.length );\n\n self.trigger( 'views:ready', self.views );\n return self.views;\n },\n\n /** render pagination controls if not searching and contents says we're paginating */\n _renderPagination: function( $whereTo ){\n var $paginationControls = $whereTo.find( '> .controls .list-pagination' );\n if( this.searchFor || !this.model.contents.shouldPaginate() ) return $paginationControls.empty();\n\n $paginationControls.html( this.templates.pagination({\n // pagination is 1-based for the user\n current : this.model.contents.currentPage + 1,\n last : this.model.contents.getLastPage() + 1,\n }, this ));\n $paginationControls.find( 'select.pages' ).tooltip();\n return $paginationControls;\n },\n\n /** render a subset of the entire collection (client-side pagination) */\n _renderSomeItems: function( models, $list ){\n var self = this;\n var views = [];\n $list.append( models.map( function( m ){\n var view = self._createItemView( m );\n views.push( view );\n return self._renderItemView$el( view );\n }));\n return views;\n },\n\n // ------------------------------------------------------------------------ sub-views\n /** in this override, check if the contents would also display based on includeDeleted/hidden */\n _filterItem : function( model ){\n var self = this;\n var contents = self.model.contents;\n return ( contents.includeHidden || !model.hidden() )\n && ( contents.includeDeleted || !model.isDeletedOrPurged() )\n && ( _super.prototype._filterItem.call( self, model ) );\n },\n\n /** In this override, since history contents are mixed,\n * get the appropo view class based on history_content_type\n */\n _getItemViewClass : function( model ){\n var contentType = model.get( \"history_content_type\" );\n switch( contentType ){\n case 'dataset':\n return this.HDAViewClass;\n case 'dataset_collection':\n return this.HDCAViewClass;\n }\n throw new TypeError( 'Unknown history_content_type: ' + contentType );\n },\n\n /** in this override, add a linktarget, and expand if id is in web storage */\n _getItemViewOptions : function( model ){\n var options = _super.prototype._getItemViewOptions.call( this, model );\n return _.extend( options, {\n linkTarget : this.linkTarget,\n expanded : this.model.contents.storage.isExpanded( model.id ),\n hasUser : this.model.ownedByCurrUser()\n });\n },\n\n /** In this override, add/remove expanded/collapsed model ids to/from web storage */\n _setUpItemViewListeners : function( view ){\n var panel = this;\n _super.prototype._setUpItemViewListeners.call( panel, view );\n //TODO: send from content view: this.model.collection.storage.addExpanded\n // maintain a list of items whose bodies are expanded\n return panel.listenTo( view, {\n 'expanded': function( v ){\n panel.model.contents.storage.addExpanded( v.model );\n },\n 'collapsed': function( v ){\n panel.model.contents.storage.removeExpanded( v.model );\n }\n });\n },\n\n /** override to remove expandedIds from webstorage */\n collapseAll : function(){\n this.model.contents.storage.clearExpanded();\n _super.prototype.collapseAll.call( this );\n },\n\n // ------------------------------------------------------------------------ selection\n /** Override to correctly set the historyId of the new collection */\n getSelectedModels : function(){\n var collection = _super.prototype.getSelectedModels.call( this );\n collection.historyId = this.collection.historyId;\n return collection;\n },\n\n\n // ------------------------------------------------------------------------ panel events\n /** event map */\n events : _.extend( _.clone( _super.prototype.events ), {\n 'click .show-selectors-btn' : 'toggleSelectors',\n 'click > .controls .prev' : '_clickPrevPage',\n 'click > .controls .next' : '_clickNextPage',\n 'change > .controls .pages' : '_changePageSelect',\n // allow (error) messages to be clicked away\n 'click .messages [class$=message]' : 'clearMessages',\n }),\n\n _clickPrevPage : function( ev ){\n this.model.contents.fetchPrevPage();\n },\n\n _clickNextPage : function( ev ){\n this.model.contents.fetchNextPage();\n },\n\n _changePageSelect : function( ev ){\n var page = $( ev.currentTarget ).val();\n this.model.contents.fetchPage( page );\n },\n\n /** Toggle and store the deleted visibility and re-render items\n * @returns {Boolean} new setting\n */\n toggleShowDeleted : function( show, options ){\n show = ( show !== undefined )?( show ):( !this.model.contents.includeDeleted );\n var self = this;\n var contents = self.model.contents;\n contents.setIncludeDeleted( show, options );\n self.trigger( 'show-deleted', show );\n\n contents.fetchCurrentPage({ renderAll: true });\n return show;\n },\n\n /** Toggle and store whether to render explicity hidden contents\n * @returns {Boolean} new setting\n */\n toggleShowHidden : function( show, store, options ){\n // console.log( 'toggleShowHidden', show, store );\n show = ( show !== undefined )?( show ):( !this.model.contents.includeHidden );\n var self = this;\n var contents = self.model.contents;\n contents.setIncludeHidden( show, options );\n self.trigger( 'show-hidden', show );\n\n contents.fetchCurrentPage({ renderAll: true });\n return show;\n },\n\n /** On the first search, if there are no details - load them, then search */\n _firstSearch : function( searchFor ){\n var self = this;\n var inputSelector = '> .controls .search-input';\n this.log( 'onFirstSearch', searchFor );\n\n // if the contents already have enough details to search, search and return now\n if( self.model.contents.haveSearchDetails() ){\n self.searchItems( searchFor );\n return;\n }\n\n // otherwise, load the details progressively here\n self.$( inputSelector ).searchInput( 'toggle-loading' );\n // set this now so that only results will show during progress\n self.searchFor = searchFor;\n var xhr = self.model.contents.progressivelyFetchDetails({ silent: true })\n .progress( function( response, limit, offset ){\n self.renderItems();\n self.trigger( 'search:loading-progress', limit, offset );\n })\n .always( function(){\n self.$el.find( inputSelector ).searchInput( 'toggle-loading' );\n })\n .done( function(){\n self.searchItems( searchFor, 'force' );\n });\n },\n\n /** clear the search filters and show all views that are normally shown */\n clearSearch : function( searchFor ){\n var self = this;\n if( !self.searchFor ) return self;\n //self.log( 'onSearchClear', self );\n self.searchFor = '';\n self.trigger( 'search:clear', self );\n self.$( '> .controls .search-query' ).val( '' );\n // NOTE: silent + render prevents collection update event with merge only\n // - which causes an empty page due to event handler above\n self.model.contents.fetchCurrentPage({ silent: true })\n .done( function(){\n self.renderItems();\n });\n return self;\n },\n\n // ........................................................................ error handling\n /** Event handler for errors (from the panel, the history, or the history's contents)\n * Alternately use two strings for model and xhr to use custom message and title (respectively)\n * @param {Model or View} model the (Backbone) source of the error\n * @param {XMLHTTPRequest} xhr any ajax obj. assoc. with the error\n * @param {Object} options the options map commonly used with bbone ajax\n */\n errorHandler : function( model, xhr, options ){\n //TODO: to mixin or base model\n // interrupted ajax or no connection\n if( xhr && xhr.status === 0 && xhr.readyState === 0 ){\n // return ERROR_MODAL.offlineErrorModal();\n // fail silently\n return;\n }\n // otherwise, leave something to report in the console\n this.error( model, xhr, options );\n // and feedback to a modal\n // if sent two strings (and possibly details as 'options'), use those as message and title\n if( _.isString( model ) && _.isString( xhr ) ){\n var message = model;\n var title = xhr;\n return ERROR_MODAL.errorModal( message, title, options );\n }\n // bad gateway\n // TODO: possibly to global handler\n if( xhr && xhr.status === 502 ){\n return ERROR_MODAL.badGatewayErrorModal();\n }\n return ERROR_MODAL.ajaxErrorModal( model, xhr, options );\n },\n\n /** Remove all messages from the panel. */\n clearMessages : function( ev ){\n var $target = !_.isUndefined( ev )?\n $( ev.currentTarget )\n :this.$messages().children( '[class$=\"message\"]' );\n $target.fadeOut( this.fxSpeed, function(){\n $( this ).remove();\n });\n return this;\n },\n\n // ........................................................................ scrolling\n /** Scrolls the panel to show the content sub-view with the given hid.\n * @param {Integer} hid the hid of item to scroll into view\n * @returns {HistoryView} the panel\n */\n scrollToHid : function( hid ){\n return this.scrollToItem( _.first( this.viewsWhereModel({ hid: hid }) ) );\n },\n\n // ........................................................................ misc\n /** utility for adding -st, -nd, -rd, -th to numbers */\n ordinalIndicator : function( number ){\n var numStr = number + '';\n switch( numStr.charAt( numStr.length - 1 )){\n case '1': return numStr + 'st';\n case '2': return numStr + 'nd';\n case '3': return numStr + 'rd';\n default : return numStr + 'th';\n }\n },\n\n /** Return a string rep of the history */\n toString : function(){\n return 'HistoryView(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';\n }\n});\n\n\n//------------------------------------------------------------------------------ TEMPLATES\nHistoryView.prototype.templates = (function(){\n\n var mainTemplate = BASE_MVC.wrapTemplate([\n // temp container\n '
                                          ',\n '
                                          ',\n '
                                            ',\n '
                                            ',\n '
                                            '\n ]);\n\n var controlsTemplate = BASE_MVC.wrapTemplate([\n '
                                            ',\n '
                                            ',\n '
                                            <%- history.name %>
                                            ',\n '
                                            ',\n '
                                            ',\n '
                                            <%- history.nice_size %>
                                            ',\n\n '
                                            ',\n\n '
                                            ',\n '<% if( history.deleted && history.purged ){ %>',\n '
                                            ',\n _l( 'This history has been purged and deleted' ),\n '
                                            ',\n '<% } else if( history.deleted ){ %>',\n '
                                            ',\n _l( 'This history has been deleted' ),\n '
                                            ',\n '<% } else if( history.purged ){ %>',\n '
                                            ',\n _l( 'This history has been purged' ),\n '
                                            ',\n '<% } %>',\n\n '<% if( history.message ){ %>',\n // should already be localized\n '
                                            messagesmall\">',\n '<%= history.message.text %>',\n '
                                            ',\n '<% } %>',\n '
                                            ',\n\n // add tags and annotations\n '
                                            ',\n '
                                            ',\n\n '
                                            ',\n '
                                            ',\n '
                                            ',\n\n '
                                            ',\n '
                                            ',\n '',\n '',\n '
                                            ',\n '
                                            ',\n '
                                            ',\n '
                                            ',\n '
                                            ',\n '
                                            '\n ], 'history' );\n\n var paginationTemplate = BASE_MVC.wrapTemplate([\n '',\n '',\n '',\n ], 'pages' );\n\n return _.extend( _.clone( _super.prototype.templates ), {\n el : mainTemplate,\n controls : controlsTemplate,\n pagination : paginationTemplate,\n });\n}());\n\n\n//==============================================================================\n return {\n HistoryView: HistoryView\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/history/history-view.js\n ** module id = 115\n ** module chunks = 3\n **/","define([\n \"mvc/ui/popup-menu\",\n \"mvc/history/copy-dialog\",\n \"mvc/base-mvc\",\n \"utils/localization\"\n], function( PopupMenu, historyCopyDialog, BASE_MVC, _l ){\n\n'use strict';\n\n// ============================================================================\nvar menu = [\n {\n html : _l( 'History Lists' ),\n header : true\n },\n {\n html : _l( 'Saved Histories' ),\n href : 'history/list',\n },\n {\n html : _l( 'Histories Shared with Me' ),\n href : 'history/list_shared'\n },\n\n {\n html : _l( 'History Actions' ),\n header : true,\n anon : true\n },\n {\n html : _l( 'Create New' ),\n func : function(){ Galaxy.currHistoryPanel.createNewHistory(); }\n },\n {\n html : _l( 'Copy History' ),\n func : function(){\n historyCopyDialog( Galaxy.currHistoryPanel.model )\n .done( function(){\n Galaxy.currHistoryPanel.loadCurrentHistory();\n });\n },\n },\n {\n html : _l( 'Share or Publish' ),\n href : 'history/sharing',\n },\n {\n html : _l( 'Show Structure' ),\n href : 'history/display_structured',\n anon : true,\n },\n {\n html : _l( 'Extract Workflow' ),\n href : 'workflow/build_from_current_history',\n },\n {\n html : _l( 'Delete' ),\n anon : true,\n func : function() {\n if( Galaxy && Galaxy.currHistoryPanel && confirm( _l( 'Really delete the current history?' ) ) ){\n galaxy_main.window.location.href = 'history/delete?id=' + Galaxy.currHistoryPanel.model.id;\n }\n },\n },\n {\n html : _l( 'Delete Permanently' ),\n purge : true,\n anon : true,\n func : function() {\n if( Galaxy && Galaxy.currHistoryPanel\n && confirm( _l( 'Really delete the current history permanently? This cannot be undone.' ) ) ){\n galaxy_main.window.location.href = 'history/delete?purge=True&id=' + Galaxy.currHistoryPanel.model.id;\n }\n },\n },\n\n\n {\n html : _l( 'Dataset Actions' ),\n header : true,\n anon : true\n },\n {\n html : _l( 'Copy Datasets' ),\n href : 'dataset/copy_datasets',\n },\n {\n html : _l( 'Dataset Security' ),\n href : 'root/history_set_default_permissions',\n },\n {\n html : _l( 'Resume Paused Jobs' ),\n href : 'history/resume_paused_jobs?current=True',\n anon : true,\n },\n {\n html : _l( 'Collapse Expanded Datasets' ),\n func : function(){ Galaxy.currHistoryPanel.collapseAll(); }\n },\n {\n html : _l( 'Unhide Hidden Datasets' ),\n anon : true,\n func : function(){ Galaxy.currHistoryPanel.unhideHidden(); }\n },\n {\n html : _l( 'Delete Hidden Datasets' ),\n anon : true,\n func : function(){ Galaxy.currHistoryPanel.deleteHidden(); }\n },\n {\n html : _l( 'Purge Deleted Datasets' ),\n confirm : _l( 'Really delete all deleted datasets permanently? This cannot be undone.' ),\n href : 'history/purge_deleted_datasets',\n purge : true,\n anon : true,\n },\n\n {\n html : _l( 'Downloads' ),\n header : true\n },\n {\n html : _l( 'Export Tool Citations' ),\n href : 'history/citations',\n anon : true,\n },\n {\n html : _l( 'Export History to File' ),\n href : 'history/export_archive?preview=True',\n anon : true,\n },\n\n {\n html : _l( 'Other Actions' ),\n header : true\n },\n {\n html : _l( 'Import from File' ),\n href : 'history/import_archive',\n }\n];\n\nfunction buildMenu( isAnon, purgeAllowed, urlRoot ){\n return _.clone( menu ).filter( function( menuOption ){\n if( isAnon && !menuOption.anon ){\n return false;\n }\n if( !purgeAllowed && menuOption.purge ){\n return false;\n }\n\n //TODO:?? hard-coded galaxy_main\n if( menuOption.href ){\n menuOption.href = urlRoot + menuOption.href;\n menuOption.target = 'galaxy_main';\n }\n\n if( menuOption.confirm ){\n menuOption.func = function(){\n if( confirm( menuOption.confirm ) ){\n galaxy_main.location = menuOption.href;\n }\n };\n }\n return true;\n });\n}\n\nvar create = function( $button, options ){\n options = options || {};\n var isAnon = options.anonymous === undefined? true : options.anonymous,\n purgeAllowed = options.purgeAllowed || false,\n menu = buildMenu( isAnon, purgeAllowed, Galaxy.root );\n //console.debug( 'menu:', menu );\n return new PopupMenu( $button, menu );\n};\n\n\n// ============================================================================\n return create;\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/mvc/history/options-menu.js\n ** module id = 116\n ** module chunks = 3\n **/","/**\n * Renders tabs e.g. used in the Charts editor, behaves similar to repeat and section rendering\n */\ndefine( [ 'utils/utils' ], function( Utils ) {\nvar View = Backbone.View.extend({\n initialize : function( options ) {\n var self = this;\n this.visible = false;\n this.$nav = null;\n this.$content = null;\n this.first_tab = null;\n this.current_id = null;\n this.list = {};\n this.options = Utils.merge( options, {\n title_new : '',\n operations : null,\n onnew : null,\n max : null,\n onchange : null\n });\n this.setElement( $( this._template( this.options ) ) );\n this.$nav = this.$( '.tab-navigation' );\n this.$content = this.$( '.tab-content' );\n this.$operations = this.$nav.find( '.tab-operations' );\n\n // Renders tab operations\n if ( this.options.operations ) {\n $.each( this.options.operations, function( name, item ) {\n item.$el.prop( 'id', name );\n self.$operations.append( item.$el );\n });\n }\n\n // Allows user to add new tabs\n this.options.onnew && this.$nav.append( $( this._template_tab_new( this.options ) )\n .tooltip( { title: 'Add a new tab', placement: 'bottom', container: self.$el } )\n .on( 'click', function( e ) { self.options.onnew() } )\n );\n this.$tabnew = this.$nav.find( '.tab-new' );\n\n // Remove all tooltips on click\n this.$el.on( 'click', function() { $( '.tooltip' ).hide() } );\n },\n\n /** Returns current number of tabs */\n size: function() {\n return _.size( this.list );\n },\n\n /** Returns tab id for currently shown tab */\n current: function() {\n return this.$el.find( '.tab-pane.active' ).attr( 'id' );\n },\n\n /** Adds a new tab */\n add: function( options ) {\n var self = this;\n var id = options.id;\n var $tab_title = $( this._template_tab( options ) );\n var $tab_content = $( '
                                            ' ).attr( 'id', options.id ).addClass( 'tab-pane' );\n\n // hide new tab if maximum number of tabs has been reached\n this.list[ id ] = true;\n if ( this.options.max && this.size() >= this.options.max ) {\n this.$tabnew.hide();\n }\n\n // insert tab before new tab or as last tab\n if ( this.options.onnew ) {\n this.$tabnew.before( $tab_title );\n } else {\n this.$nav.append( $tab_title );\n }\n\n // assing delete callback if provided\n if ( options.ondel ) {\n $tab_title.find( '.tab-delete' ).tooltip( { title: 'Delete this tab', placement: 'bottom', container: self.$el } )\n .on( 'click', function() { options.ondel() } );\n } else {\n $tab_title.tooltip( { title: options.tooltip, placement: 'bottom', container: self.$el } );\n }\n $tab_title.on( 'click', function( e ) {\n e.preventDefault();\n options.onclick ? options.onclick() : self.show( id );\n });\n this.$content.append( $tab_content.append( options.$el ) );\n\n // assign current/first tab\n if ( this.size() == 1 ) {\n $tab_title.addClass( 'active' );\n $tab_content.addClass( 'active' );\n this.first_tab = id;\n }\n if ( !this.current_id ) {\n this.current_id = id;\n }\n },\n\n /** Delete tab */\n del: function( id ) {\n this.$( '#tab-' + id ).remove();\n this.$( '#' + id ).remove();\n this.first_tab = this.first_tab == id ? null : this.first_tab;\n this.first_tab != null && this.show( this.first_tab );\n this.list[ id ] && delete this.list[ id ];\n if ( this.size() < this.options.max ) {\n this.$el.find( '.ui-tabs-new' ).show();\n }\n },\n\n /** Delete all tabs */\n delAll: function() {\n for ( var id in this.list ) {\n this.del( id );\n }\n },\n\n /** Show tab view and highlight a tab by id */\n show: function( id ){\n this.$el.fadeIn( 'fast' );\n this.visible = true;\n if ( id ) {\n this.$( '#tab-' + this.current_id ).removeClass('active' );\n this.$( '#' + this.current_id ).removeClass('active' );\n this.$( '#tab-' + id ).addClass( 'active' );\n this.$( '#' + id ).addClass( 'active' );\n this.current_id = id;\n }\n this.options.onchange && this.options.onchange( id );\n },\n \n /** Hide tab view */\n hide: function(){\n this.$el.fadeOut( 'fast' );\n this.visible = false;\n },\n\n /** Show tab */\n showTab: function( id ) {\n this.$( '#tab-' + id ).show();\n },\n\n /** hide tab */\n hideTab: function( id ) {\n this.$( '#tab-' + id ).hide();\n },\n\n /** Hide operation by id */\n hideOperation: function( id ) {\n this.$nav.find( '#' + id ).hide();\n },\n\n /** Show operation by id */\n showOperation: function( id ) {\n this.$nav.find( '#' + id ).show();\n },\n\n /** Reassign an operation to a new callback */\n setOperation: function( id, callback ) {\n this.$nav.find( '#' + id ).off('click').on( 'click', callback );\n },\n\n /** Set/Get title */\n title: function( id, new_title ) {\n var $el = this.$( '#tab-title-text-' + id );\n new_title && $el.html( new_title );\n return $el.html();\n },\n\n /** Enumerate titles */\n retitle: function( new_title ) {\n var index = 0;\n for ( var id in this.list ) {\n this.title( id, ++index + ': ' + new_title );\n }\n },\n\n /** Main template */\n _template: function( options ) {\n return $( '
                                            ' ).addClass( 'ui-tabs tabbable tabs-left' )\n .append( $( '
                                          • ' +\n '
                                            ' +\n '
                                            ' +\n '
                                            ' +\n '
                                            You can tell Galaxy to download data from web by entering URL in this box (one per line). You can also directly paste the contents of a file.
                                            ' +\n '",gt.noCloneChecked=!!t.cloneNode(!0).lastChild.defaultValue,e.appendChild(t),i=at.createElement("input"),i.setAttribute("type","radio"),i.setAttribute("checked","checked"),i.setAttribute("name","t"),t.appendChild(i),gt.checkClone=t.cloneNode(!0).cloneNode(!0).lastChild.checked,gt.noCloneEvent=!!t.addEventListener,t[vt.expando]=1,gt.attributes=!t.getAttribute(vt.expando)}();var Yt={option:[1,""],legend:[1,"
                                            ","
                                            "],area:[1,"",""],param:[1,"",""],thead:[1,"","
                                            "],tr:[2,"","
                                            "],col:[2,"","
                                            "],td:[3,"","
                                            "],_default:gt.htmlSerialize?[0,"",""]:[1,"X
                                            ","
                                            "]};Yt.optgroup=Yt.option,Yt.tbody=Yt.tfoot=Yt.colgroup=Yt.caption=Yt.thead,Yt.th=Yt.td;var Jt=/<|&#?\w+;/,Qt=/
                                            t
                                            ",u.childNodes[0].style.borderCollapse="separate",t=u.getElementsByTagName("td"),t[0].style.cssText="margin:0;border:0;padding:0;display:none",s=0===t[0].offsetHeight,s&&(t[0].style.display="",t[1].style.display="none",s=0===t[0].offsetHeight)),c.removeChild(l)}var e,n,r,s,o,a,l=at.createElement("div"),u=at.createElement("div");u.style&&(u.style.cssText="float:left;opacity:.5",gt.opacity="0.5"===u.style.opacity,gt.cssFloat=!!u.style.cssFloat,u.style.backgroundClip="content-box",u.cloneNode(!0).style.backgroundClip="",gt.clearCloneStyle="content-box"===u.style.backgroundClip,l=at.createElement("div"),l.style.cssText="border:0;width:8px;height:0;top:0;left:-9999px;padding:0;margin-top:1px;position:absolute",u.innerHTML="",l.appendChild(u),gt.boxSizing=""===u.style.boxSizing||""===u.style.MozBoxSizing||""===u.style.WebkitBoxSizing,vt.extend(gt,{reliableHiddenOffsets:function(){return null==e&&t(),s},boxSizingReliable:function(){return null==e&&t(),r},pixelMarginRight:function(){return null==e&&t(),n},pixelPosition:function(){return null==e&&t(),e},reliableMarginRight:function(){return null==e&&t(),o},reliableMarginLeft:function(){return null==e&&t(),a}}))}();var ye,be,we=/^(top|right|bottom|left)$/;i.getComputedStyle?(ye=function(t){var e=t.ownerDocument.defaultView;return e&&e.opener||(e=i),e.getComputedStyle(t)},be=function(t,e,i){var n,r,s,o,a=t.style;return i=i||ye(t),o=i?i.getPropertyValue(e)||i[e]:void 0,""!==o&&void 0!==o||vt.contains(t.ownerDocument,t)||(o=vt.style(t,e)),i&&!gt.pixelMarginRight()&&ge.test(o)&&fe.test(e)&&(n=a.width,r=a.minWidth,s=a.maxWidth,a.minWidth=a.maxWidth=a.width=o,o=i.width,a.width=n,a.minWidth=r,a.maxWidth=s),void 0===o?o:o+""}):ve.currentStyle&&(ye=function(t){return t.currentStyle},be=function(t,e,i){var n,r,s,o,a=t.style;return i=i||ye(t),o=i?i[e]:void 0,null==o&&a&&a[e]&&(o=a[e]),ge.test(o)&&!we.test(e)&&(n=a.left,r=t.runtimeStyle,s=r&&r.left,s&&(r.left=t.currentStyle.left),a.left="fontSize"===e?"1em":o,o=a.pixelLeft+"px",a.left=n,s&&(r.left=s)),void 0===o?o:o+""||"auto"});var _e=/alpha\([^)]*\)/i,xe=/opacity\s*=\s*([^)]*)/i,Ce=/^(none|table(?!-c[ea]).+)/,Se=new RegExp("^("+$t+")(.*)$","i"),ke={position:"absolute",visibility:"hidden",display:"block"},Te={letterSpacing:"0",fontWeight:"400"},Ee=["Webkit","O","Moz","ms"],Ae=at.createElement("div").style;vt.extend({cssHooks:{opacity:{get:function(t,e){if(e){var i=be(t,"opacity");return""===i?"1":i}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":gt.cssFloat?"cssFloat":"styleFloat"},style:function(t,e,i,n){if(t&&3!==t.nodeType&&8!==t.nodeType&&t.style){var r,s,o,a=vt.camelCase(e),l=t.style;if(e=vt.cssProps[a]||(vt.cssProps[a]=L(a)||a),o=vt.cssHooks[e]||vt.cssHooks[a],void 0===i)return o&&"get"in o&&void 0!==(r=o.get(t,!1,n))?r:l[e];if(s=typeof i,"string"===s&&(r=Ft.exec(i))&&r[1]&&(i=m(t,e,r),s="number"),null!=i&&i===i&&("number"===s&&(i+=r&&r[3]||(vt.cssNumber[a]?"":"px")),gt.clearCloneStyle||""!==i||0!==e.indexOf("background")||(l[e]="inherit"),!(o&&"set"in o&&void 0===(i=o.set(t,i,n)))))try{l[e]=i}catch(u){}}},css:function(t,e,i,n){var r,s,o,a=vt.camelCase(e);return e=vt.cssProps[a]||(vt.cssProps[a]=L(a)||a),o=vt.cssHooks[e]||vt.cssHooks[a],o&&"get"in o&&(s=o.get(t,!0,i)),void 0===s&&(s=be(t,e,n)),"normal"===s&&e in Te&&(s=Te[e]),""===i||i?(r=parseFloat(s),i===!0||isFinite(r)?r||0:s):s}}),vt.each(["height","width"],function(t,e){vt.cssHooks[e]={get:function(t,i,n){return i?Ce.test(vt.css(t,"display"))&&0===t.offsetWidth?me(t,ke,function(){return F(t,e,n)}):F(t,e,n):void 0},set:function(t,i,n){var r=n&&ye(t);return I(t,i,n?$(t,e,n,gt.boxSizing&&"border-box"===vt.css(t,"boxSizing",!1,r),r):0)}}}),gt.opacity||(vt.cssHooks.opacity={get:function(t,e){return xe.test((e&&t.currentStyle?t.currentStyle.filter:t.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":e?"1":""},set:function(t,e){var i=t.style,n=t.currentStyle,r=vt.isNumeric(e)?"alpha(opacity="+100*e+")":"",s=n&&n.filter||i.filter||"";i.zoom=1,(e>=1||""===e)&&""===vt.trim(s.replace(_e,""))&&i.removeAttribute&&(i.removeAttribute("filter"),""===e||n&&!n.filter)||(i.filter=_e.test(s)?s.replace(_e,r):s+" "+r)}}),vt.cssHooks.marginRight=j(gt.reliableMarginRight,function(t,e){return e?me(t,{display:"inline-block"},be,[t,"marginRight"]):void 0}),vt.cssHooks.marginLeft=j(gt.reliableMarginLeft,function(t,e){return e?(parseFloat(be(t,"marginLeft"))||(vt.contains(t.ownerDocument,t)?t.getBoundingClientRect().left-me(t,{marginLeft:0},function(){return t.getBoundingClientRect().left}):0))+"px":void 0}),vt.each({margin:"",padding:"",border:"Width"},function(t,e){vt.cssHooks[t+e]={expand:function(i){for(var n=0,r={},s="string"==typeof i?i.split(" "):[i];4>n;n++)r[t+qt[n]+e]=s[n]||s[n-2]||s[0];return r}},fe.test(t)||(vt.cssHooks[t+e].set=I)}),vt.fn.extend({css:function(t,e){return zt(this,function(t,e,i){var n,r,s={},o=0;if(vt.isArray(e)){for(n=ye(t),r=e.length;r>o;o++)s[e[o]]=vt.css(t,e[o],!1,n);return s}return void 0!==i?vt.style(t,e,i):vt.css(t,e)},t,e,arguments.length>1)},show:function(){return H(this,!0)},hide:function(){return H(this)},toggle:function(t){return"boolean"==typeof t?t?this.show():this.hide():this.each(function(){Wt(this)?vt(this).show():vt(this).hide()})}}),vt.Tween=q,q.prototype={constructor:q,init:function(t,e,i,n,r,s){this.elem=t,this.prop=i,this.easing=r||vt.easing._default,this.options=e,this.start=this.now=this.cur(),this.end=n,this.unit=s||(vt.cssNumber[i]?"":"px")},cur:function(){var t=q.propHooks[this.prop];return t&&t.get?t.get(this):q.propHooks._default.get(this)},run:function(t){var e,i=q.propHooks[this.prop];return this.options.duration?this.pos=e=vt.easing[this.easing](t,this.options.duration*t,0,1,this.options.duration):this.pos=e=t,this.now=(this.end-this.start)*e+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),i&&i.set?i.set(this):q.propHooks._default.set(this),this}},q.prototype.init.prototype=q.prototype,q.propHooks={_default:{get:function(t){var e;return 1!==t.elem.nodeType||null!=t.elem[t.prop]&&null==t.elem.style[t.prop]?t.elem[t.prop]:(e=vt.css(t.elem,t.prop,""),e&&"auto"!==e?e:0)},set:function(t){vt.fx.step[t.prop]?vt.fx.step[t.prop](t):1!==t.elem.nodeType||null==t.elem.style[vt.cssProps[t.prop]]&&!vt.cssHooks[t.prop]?t.elem[t.prop]=t.now:vt.style(t.elem,t.prop,t.now+t.unit)}}},q.propHooks.scrollTop=q.propHooks.scrollLeft={set:function(t){t.elem.nodeType&&t.elem.parentNode&&(t.elem[t.prop]=t.now)}},vt.easing={linear:function(t){return t},swing:function(t){return.5-Math.cos(t*Math.PI)/2},_default:"swing"},vt.fx=q.prototype.init,vt.fx.step={};var Ne,Oe,Me=/^(?:toggle|show|hide)$/,De=/queueHooks$/;vt.Animation=vt.extend(G,{tweeners:{"*":[function(t,e){var i=this.createTween(t,e);return m(i.elem,t,Ft.exec(e),i),i}]},tweener:function(t,e){vt.isFunction(t)?(e=t,t=["*"]):t=t.match(Pt);for(var i,n=0,r=t.length;r>n;n++)i=t[n],G.tweeners[i]=G.tweeners[i]||[],G.tweeners[i].unshift(e)},prefilters:[U],prefilter:function(t,e){e?G.prefilters.unshift(t):G.prefilters.push(t)}}),vt.speed=function(t,e,i){var n=t&&"object"==typeof t?vt.extend({},t):{complete:i||!i&&e||vt.isFunction(t)&&t,duration:t,easing:i&&e||e&&!vt.isFunction(e)&&e};return n.duration=vt.fx.off?0:"number"==typeof n.duration?n.duration:n.duration in vt.fx.speeds?vt.fx.speeds[n.duration]:vt.fx.speeds._default,(null==n.queue||n.queue===!0)&&(n.queue="fx"),n.old=n.complete,n.complete=function(){vt.isFunction(n.old)&&n.old.call(this),n.queue&&vt.dequeue(this,n.queue)},n},vt.fn.extend({fadeTo:function(t,e,i,n){return this.filter(Wt).css("opacity",0).show().end().animate({opacity:e},t,i,n)},animate:function(t,e,i,n){var r=vt.isEmptyObject(t),s=vt.speed(e,i,n),o=function(){var e=G(this,vt.extend({},t),s);(r||vt._data(this,"finish"))&&e.stop(!0)};return o.finish=o,r||s.queue===!1?this.each(o):this.queue(s.queue,o)},stop:function(t,e,i){var n=function(t){var e=t.stop;delete t.stop,e(i)};return"string"!=typeof t&&(i=e,e=t,t=void 0),e&&t!==!1&&this.queue(t||"fx",[]),this.each(function(){var e=!0,r=null!=t&&t+"queueHooks",s=vt.timers,o=vt._data(this);if(r)o[r]&&o[r].stop&&n(o[r]);else for(r in o)o[r]&&o[r].stop&&De.test(r)&&n(o[r]);for(r=s.length;r--;)s[r].elem!==this||null!=t&&s[r].queue!==t||(s[r].anim.stop(i),e=!1,s.splice(r,1));(e||!i)&&vt.dequeue(this,t)})},finish:function(t){return t!==!1&&(t=t||"fx"),this.each(function(){var e,i=vt._data(this),n=i[t+"queue"],r=i[t+"queueHooks"],s=vt.timers,o=n?n.length:0;for(i.finish=!0,vt.queue(this,t,[]),r&&r.stop&&r.stop.call(this,!0),e=s.length;e--;)s[e].elem===this&&s[e].queue===t&&(s[e].anim.stop(!0),s.splice(e,1));for(e=0;o>e;e++)n[e]&&n[e].finish&&n[e].finish.call(this);delete i.finish})}}),vt.each(["toggle","show","hide"],function(t,e){var i=vt.fn[e];vt.fn[e]=function(t,n,r){return null==t||"boolean"==typeof t?i.apply(this,arguments):this.animate(z(e,!0),t,n,r)}}),vt.each({slideDown:z("show"),slideUp:z("hide"),slideToggle:z("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(t,e){vt.fn[t]=function(t,i,n){return this.animate(e,t,i,n)}}),vt.timers=[],vt.fx.tick=function(){var t,e=vt.timers,i=0;for(Ne=vt.now();i
                                            a",t=i.getElementsByTagName("a")[0],e.setAttribute("type","checkbox"),i.appendChild(e),t=i.getElementsByTagName("a")[0],t.style.cssText="top:1px",gt.getSetAttribute="t"!==i.className,gt.style=/top/.test(t.getAttribute("style")),gt.hrefNormalized="/a"===t.getAttribute("href"),gt.checkOn=!!e.value,gt.optSelected=r.selected,gt.enctype=!!at.createElement("form").enctype,n.disabled=!0,gt.optDisabled=!r.disabled,e=at.createElement("input"),e.setAttribute("value",""),gt.input=""===e.getAttribute("value"),e.value="t",e.setAttribute("type","radio"),gt.radioValue="t"===e.value}();var Pe=/\r/g,Re=/[\x20\t\r\n\f]+/g;vt.fn.extend({val:function(t){var e,i,n,r=this[0];{if(arguments.length)return n=vt.isFunction(t),this.each(function(i){var r;1===this.nodeType&&(r=n?t.call(this,i,vt(this).val()):t,null==r?r="":"number"==typeof r?r+="":vt.isArray(r)&&(r=vt.map(r,function(t){return null==t?"":t+""})),e=vt.valHooks[this.type]||vt.valHooks[this.nodeName.toLowerCase()],e&&"set"in e&&void 0!==e.set(this,r,"value")||(this.value=r))});if(r)return e=vt.valHooks[r.type]||vt.valHooks[r.nodeName.toLowerCase()],e&&"get"in e&&void 0!==(i=e.get(r,"value"))?i:(i=r.value,"string"==typeof i?i.replace(Pe,""):null==i?"":i)}}}),vt.extend({valHooks:{option:{get:function(t){var e=vt.find.attr(t,"value");return null!=e?e:vt.trim(vt.text(t)).replace(Re," ")}},select:{get:function(t){for(var e,i,n=t.options,r=t.selectedIndex,s="select-one"===t.type||0>r,o=s?null:[],a=s?r+1:n.length,l=0>r?a:s?r:0;a>l;l++)if(i=n[l],(i.selected||l===r)&&(gt.optDisabled?!i.disabled:null===i.getAttribute("disabled"))&&(!i.parentNode.disabled||!vt.nodeName(i.parentNode,"optgroup"))){if(e=vt(i).val(),s)return e;o.push(e)}return o},set:function(t,e){for(var i,n,r=t.options,s=vt.makeArray(e),o=r.length;o--;)if(n=r[o],vt.inArray(vt.valHooks.option.get(n),s)>-1)try{n.selected=i=!0}catch(a){n.scrollHeight}else n.selected=!1;return i||(t.selectedIndex=-1),r}}}}),vt.each(["radio","checkbox"],function(){vt.valHooks[this]={set:function(t,e){return vt.isArray(e)?t.checked=vt.inArray(vt(t).val(),e)>-1:void 0}},gt.checkOn||(vt.valHooks[this].get=function(t){return null===t.getAttribute("value")?"on":t.value})});var je,Le,He=vt.expr.attrHandle,Ie=/^(?:checked|selected)$/i,$e=gt.getSetAttribute,Fe=gt.input;vt.fn.extend({attr:function(t,e){return zt(this,vt.attr,t,e,arguments.length>1)},removeAttr:function(t){return this.each(function(){vt.removeAttr(this,t)})}}),vt.extend({attr:function(t,e,i){var n,r,s=t.nodeType;if(3!==s&&8!==s&&2!==s)return"undefined"==typeof t.getAttribute?vt.prop(t,e,i):(1===s&&vt.isXMLDoc(t)||(e=e.toLowerCase(),r=vt.attrHooks[e]||(vt.expr.match.bool.test(e)?Le:je)),void 0!==i?null===i?void vt.removeAttr(t,e):r&&"set"in r&&void 0!==(n=r.set(t,i,e))?n:(t.setAttribute(e,i+""),i):r&&"get"in r&&null!==(n=r.get(t,e))?n:(n=vt.find.attr(t,e),null==n?void 0:n))},attrHooks:{type:{set:function(t,e){if(!gt.radioValue&&"radio"===e&&vt.nodeName(t,"input")){var i=t.value;return t.setAttribute("type",e),i&&(t.value=i),e}}}},removeAttr:function(t,e){var i,n,r=0,s=e&&e.match(Pt);if(s&&1===t.nodeType)for(;i=s[r++];)n=vt.propFix[i]||i,vt.expr.match.bool.test(i)?Fe&&$e||!Ie.test(i)?t[n]=!1:t[vt.camelCase("default-"+i)]=t[n]=!1:vt.attr(t,i,""),t.removeAttribute($e?i:n)}}),Le={set:function(t,e,i){return e===!1?vt.removeAttr(t,i):Fe&&$e||!Ie.test(i)?t.setAttribute(!$e&&vt.propFix[i]||i,i):t[vt.camelCase("default-"+i)]=t[i]=!0,i}},vt.each(vt.expr.match.bool.source.match(/\w+/g),function(t,e){var i=He[e]||vt.find.attr;Fe&&$e||!Ie.test(e)?He[e]=function(t,e,n){var r,s;return n||(s=He[e],He[e]=r,r=null!=i(t,e,n)?e.toLowerCase():null,He[e]=s),r}:He[e]=function(t,e,i){return i?void 0:t[vt.camelCase("default-"+e)]?e.toLowerCase():null}}),Fe&&$e||(vt.attrHooks.value={set:function(t,e,i){return vt.nodeName(t,"input")?void(t.defaultValue=e):je&&je.set(t,e,i)}}),$e||(je={set:function(t,e,i){var n=t.getAttributeNode(i);return n||t.setAttributeNode(n=t.ownerDocument.createAttribute(i)),n.value=e+="","value"===i||e===t.getAttribute(i)?e:void 0}},He.id=He.name=He.coords=function(t,e,i){var n;return i?void 0:(n=t.getAttributeNode(e))&&""!==n.value?n.value:null},vt.valHooks.button={get:function(t,e){var i=t.getAttributeNode(e);return i&&i.specified?i.value:void 0},set:je.set},vt.attrHooks.contenteditable={set:function(t,e,i){je.set(t,""===e?!1:e,i)}},vt.each(["width","height"],function(t,e){vt.attrHooks[e]={set:function(t,i){return""===i?(t.setAttribute(e,"auto"),i):void 0}}})),gt.style||(vt.attrHooks.style={get:function(t){return t.style.cssText||void 0},set:function(t,e){return t.style.cssText=e+""}});var qe=/^(?:input|select|textarea|button|object)$/i,We=/^(?:a|area)$/i;vt.fn.extend({prop:function(t,e){return zt(this,vt.prop,t,e,arguments.length>1)},removeProp:function(t){return t=vt.propFix[t]||t,this.each(function(){try{this[t]=void 0,delete this[t]}catch(e){}})}}),vt.extend({prop:function(t,e,i){var n,r,s=t.nodeType;if(3!==s&&8!==s&&2!==s)return 1===s&&vt.isXMLDoc(t)||(e=vt.propFix[e]||e,r=vt.propHooks[e]),void 0!==i?r&&"set"in r&&void 0!==(n=r.set(t,i,e))?n:t[e]=i:r&&"get"in r&&null!==(n=r.get(t,e))?n:t[e]},propHooks:{tabIndex:{get:function(t){var e=vt.find.attr(t,"tabindex");return e?parseInt(e,10):qe.test(t.nodeName)||We.test(t.nodeName)&&t.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),gt.hrefNormalized||vt.each(["href","src"],function(t,e){vt.propHooks[e]={get:function(t){return t.getAttribute(e,4)}}}),gt.optSelected||(vt.propHooks.selected={get:function(t){var e=t.parentNode;return e&&(e.selectedIndex,e.parentNode&&e.parentNode.selectedIndex),null},set:function(t){var e=t.parentNode;e&&(e.selectedIndex,e.parentNode&&e.parentNode.selectedIndex)}}),vt.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){vt.propFix[this.toLowerCase()]=this}),gt.enctype||(vt.propFix.enctype="encoding");var ze=/[\t\r\n\f]/g;vt.fn.extend({addClass:function(t){var e,i,n,r,s,o,a,l=0;if(vt.isFunction(t))return this.each(function(e){vt(this).addClass(t.call(this,e,X(this)))});if("string"==typeof t&&t)for(e=t.match(Pt)||[];i=this[l++];)if(r=X(i),n=1===i.nodeType&&(" "+r+" ").replace(ze," ")){for(o=0;s=e[o++];)n.indexOf(" "+s+" ")<0&&(n+=s+" ");a=vt.trim(n),r!==a&&vt.attr(i,"class",a)}return this},removeClass:function(t){var e,i,n,r,s,o,a,l=0;if(vt.isFunction(t))return this.each(function(e){vt(this).removeClass(t.call(this,e,X(this)))});if(!arguments.length)return this.attr("class","");if("string"==typeof t&&t)for(e=t.match(Pt)||[];i=this[l++];)if(r=X(i),n=1===i.nodeType&&(" "+r+" ").replace(ze," ")){for(o=0;s=e[o++];)for(;n.indexOf(" "+s+" ")>-1;)n=n.replace(" "+s+" "," ");a=vt.trim(n),r!==a&&vt.attr(i,"class",a)}return this},toggleClass:function(t,e){var i=typeof t;return"boolean"==typeof e&&"string"===i?e?this.addClass(t):this.removeClass(t):vt.isFunction(t)?this.each(function(i){vt(this).toggleClass(t.call(this,i,X(this),e),e)}):this.each(function(){ -var e,n,r,s;if("string"===i)for(n=0,r=vt(this),s=t.match(Pt)||[];e=s[n++];)r.hasClass(e)?r.removeClass(e):r.addClass(e);else(void 0===t||"boolean"===i)&&(e=X(this),e&&vt._data(this,"__className__",e),vt.attr(this,"class",e||t===!1?"":vt._data(this,"__className__")||""))})},hasClass:function(t){var e,i,n=0;for(e=" "+t+" ";i=this[n++];)if(1===i.nodeType&&(" "+X(i)+" ").replace(ze," ").indexOf(e)>-1)return!0;return!1}}),vt.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(t,e){vt.fn[e]=function(t,i){return arguments.length>0?this.on(e,null,t,i):this.trigger(e)}}),vt.fn.extend({hover:function(t,e){return this.mouseenter(t).mouseleave(e||t)}});var Be=i.location,Ue=vt.now(),Ve=/\?/,Ge=/(,)|(\[|{)|(}|])|"(?:[^"\\\r\n]|\\["\\\/bfnrt]|\\u[\da-fA-F]{4})*"\s*:?|true|false|null|-?(?!0\d)\d+(?:\.\d+|)(?:[eE][+-]?\d+|)/g;vt.parseJSON=function(t){if(i.JSON&&i.JSON.parse)return i.JSON.parse(t+"");var e,n=null,r=vt.trim(t+"");return r&&!vt.trim(r.replace(Ge,function(t,i,r,s){return e&&i&&(n=0),0===n?t:(e=r||i,n+=!s-!r,"")}))?Function("return "+r)():vt.error("Invalid JSON: "+t)},vt.parseXML=function(t){var e,n;if(!t||"string"!=typeof t)return null;try{i.DOMParser?(n=new i.DOMParser,e=n.parseFromString(t,"text/xml")):(e=new i.ActiveXObject("Microsoft.XMLDOM"),e.async="false",e.loadXML(t))}catch(r){e=void 0}return e&&e.documentElement&&!e.getElementsByTagName("parsererror").length||vt.error("Invalid XML: "+t),e};var Xe=/#.*$/,Ye=/([?&])_=[^&]*/,Je=/^(.*?):[ \t]*([^\r\n]*)\r?$/gm,Qe=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Ke=/^(?:GET|HEAD)$/,Ze=/^\/\//,ti=/^([\w.+-]+:)(?:\/\/(?:[^\/?#]*@|)([^\/?#:]*)(?::(\d+)|)|)/,ei={},ii={},ni="*/".concat("*"),ri=Be.href,si=ti.exec(ri.toLowerCase())||[];vt.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:ri,type:"GET",isLocal:Qe.test(si[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":ni,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":vt.parseJSON,"text xml":vt.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(t,e){return e?Q(Q(t,vt.ajaxSettings),e):Q(vt.ajaxSettings,t)},ajaxPrefilter:Y(ei),ajaxTransport:Y(ii),ajax:function(t,e){function n(t,e,n,r){var s,c,y,b,_,C=e;2!==w&&(w=2,l&&i.clearTimeout(l),h=void 0,a=r||"",x.readyState=t>0?4:0,s=t>=200&&300>t||304===t,n&&(b=K(d,x,n)),b=Z(d,b,x,s),s?(d.ifModified&&(_=x.getResponseHeader("Last-Modified"),_&&(vt.lastModified[o]=_),_=x.getResponseHeader("etag"),_&&(vt.etag[o]=_)),204===t||"HEAD"===d.type?C="nocontent":304===t?C="notmodified":(C=b.state,c=b.data,y=b.error,s=!y)):(y=C,(t||!C)&&(C="error",0>t&&(t=0))),x.status=t,x.statusText=(e||C)+"",s?g.resolveWith(p,[c,C,x]):g.rejectWith(p,[x,C,y]),x.statusCode(v),v=void 0,u&&f.trigger(s?"ajaxSuccess":"ajaxError",[x,d,s?c:y]),m.fireWith(p,[x,C]),u&&(f.trigger("ajaxComplete",[x,d]),--vt.active||vt.event.trigger("ajaxStop")))}"object"==typeof t&&(e=t,t=void 0),e=e||{};var r,s,o,a,l,u,h,c,d=vt.ajaxSetup({},e),p=d.context||d,f=d.context&&(p.nodeType||p.jquery)?vt(p):vt.event,g=vt.Deferred(),m=vt.Callbacks("once memory"),v=d.statusCode||{},y={},b={},w=0,_="canceled",x={readyState:0,getResponseHeader:function(t){var e;if(2===w){if(!c)for(c={};e=Je.exec(a);)c[e[1].toLowerCase()]=e[2];e=c[t.toLowerCase()]}return null==e?null:e},getAllResponseHeaders:function(){return 2===w?a:null},setRequestHeader:function(t,e){var i=t.toLowerCase();return w||(t=b[i]=b[i]||t,y[t]=e),this},overrideMimeType:function(t){return w||(d.mimeType=t),this},statusCode:function(t){var e;if(t)if(2>w)for(e in t)v[e]=[v[e],t[e]];else x.always(t[x.status]);return this},abort:function(t){var e=t||_;return h&&h.abort(e),n(0,e),this}};if(g.promise(x).complete=m.add,x.success=x.done,x.error=x.fail,d.url=((t||d.url||ri)+"").replace(Xe,"").replace(Ze,si[1]+"//"),d.type=e.method||e.type||d.method||d.type,d.dataTypes=vt.trim(d.dataType||"*").toLowerCase().match(Pt)||[""],null==d.crossDomain&&(r=ti.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]===si[1]&&r[2]===si[2]&&(r[3]||("http:"===r[1]?"80":"443"))===(si[3]||("http:"===si[1]?"80":"443")))),d.data&&d.processData&&"string"!=typeof d.data&&(d.data=vt.param(d.data,d.traditional)),J(ei,d,e,x),2===w)return x;u=vt.event&&d.global,u&&0===vt.active++&&vt.event.trigger("ajaxStart"),d.type=d.type.toUpperCase(),d.hasContent=!Ke.test(d.type),o=d.url,d.hasContent||(d.data&&(o=d.url+=(Ve.test(o)?"&":"?")+d.data,delete d.data),d.cache===!1&&(d.url=Ye.test(o)?o.replace(Ye,"$1_="+Ue++):o+(Ve.test(o)?"&":"?")+"_="+Ue++)),d.ifModified&&(vt.lastModified[o]&&x.setRequestHeader("If-Modified-Since",vt.lastModified[o]),vt.etag[o]&&x.setRequestHeader("If-None-Match",vt.etag[o])),(d.data&&d.hasContent&&d.contentType!==!1||e.contentType)&&x.setRequestHeader("Content-Type",d.contentType),x.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+("*"!==d.dataTypes[0]?", "+ni+"; q=0.01":""):d.accepts["*"]);for(s in d.headers)x.setRequestHeader(s,d.headers[s]);if(d.beforeSend&&(d.beforeSend.call(p,x,d)===!1||2===w))return x.abort();_="abort";for(s in{success:1,error:1,complete:1})x[s](d[s]);if(h=J(ii,d,e,x)){if(x.readyState=1,u&&f.trigger("ajaxSend",[x,d]),2===w)return x;d.async&&d.timeout>0&&(l=i.setTimeout(function(){x.abort("timeout")},d.timeout));try{w=1,h.send(y,n)}catch(C){if(!(2>w))throw C;n(-1,C)}}else n(-1,"No Transport");return x},getJSON:function(t,e,i){return vt.get(t,e,i,"json")},getScript:function(t,e){return vt.get(t,void 0,e,"script")}}),vt.each(["get","post"],function(t,e){vt[e]=function(t,i,n,r){return vt.isFunction(i)&&(r=r||n,n=i,i=void 0),vt.ajax(vt.extend({url:t,type:e,dataType:r,data:i,success:n},vt.isPlainObject(t)&&t))}}),vt._evalUrl=function(t){return vt.ajax({url:t,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,"throws":!0})},vt.fn.extend({wrapAll:function(t){if(vt.isFunction(t))return this.each(function(e){vt(this).wrapAll(t.call(this,e))});if(this[0]){var e=vt(t,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&e.insertBefore(this[0]),e.map(function(){for(var t=this;t.firstChild&&1===t.firstChild.nodeType;)t=t.firstChild;return t}).append(this)}return this},wrapInner:function(t){return vt.isFunction(t)?this.each(function(e){vt(this).wrapInner(t.call(this,e))}):this.each(function(){var e=vt(this),i=e.contents();i.length?i.wrapAll(t):e.append(t)})},wrap:function(t){var e=vt.isFunction(t);return this.each(function(i){vt(this).wrapAll(e?t.call(this,i):t)})},unwrap:function(){return this.parent().each(function(){vt.nodeName(this,"body")||vt(this).replaceWith(this.childNodes)}).end()}}),vt.expr.filters.hidden=function(t){return gt.reliableHiddenOffsets()?t.offsetWidth<=0&&t.offsetHeight<=0&&!t.getClientRects().length:et(t)},vt.expr.filters.visible=function(t){return!vt.expr.filters.hidden(t)};var oi=/%20/g,ai=/\[\]$/,li=/\r?\n/g,ui=/^(?:submit|button|image|reset|file)$/i,hi=/^(?:input|select|textarea|keygen)/i;vt.param=function(t,e){var i,n=[],r=function(t,e){e=vt.isFunction(e)?e():null==e?"":e,n[n.length]=encodeURIComponent(t)+"="+encodeURIComponent(e)};if(void 0===e&&(e=vt.ajaxSettings&&vt.ajaxSettings.traditional),vt.isArray(t)||t.jquery&&!vt.isPlainObject(t))vt.each(t,function(){r(this.name,this.value)});else for(i in t)it(i,t[i],e,r);return n.join("&").replace(oi,"+")},vt.fn.extend({serialize:function(){return vt.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var t=vt.prop(this,"elements");return t?vt.makeArray(t):this}).filter(function(){var t=this.type;return this.name&&!vt(this).is(":disabled")&&hi.test(this.nodeName)&&!ui.test(t)&&(this.checked||!Bt.test(t))}).map(function(t,e){var i=vt(this).val();return null==i?null:vt.isArray(i)?vt.map(i,function(t){return{name:e.name,value:t.replace(li,"\r\n")}}):{name:e.name,value:i.replace(li,"\r\n")}}).get()}}),vt.ajaxSettings.xhr=void 0!==i.ActiveXObject?function(){return this.isLocal?rt():at.documentMode>8?nt():/^(get|post|head|put|delete|options)$/i.test(this.type)&&nt()||rt()}:nt;var ci=0,di={},pi=vt.ajaxSettings.xhr();i.attachEvent&&i.attachEvent("onunload",function(){for(var t in di)di[t](void 0,!0)}),gt.cors=!!pi&&"withCredentials"in pi,pi=gt.ajax=!!pi,pi&&vt.ajaxTransport(function(t){if(!t.crossDomain||gt.cors){var e;return{send:function(n,r){var s,o=t.xhr(),a=++ci;if(o.open(t.type,t.url,t.async,t.username,t.password),t.xhrFields)for(s in t.xhrFields)o[s]=t.xhrFields[s];t.mimeType&&o.overrideMimeType&&o.overrideMimeType(t.mimeType),t.crossDomain||n["X-Requested-With"]||(n["X-Requested-With"]="XMLHttpRequest");for(s in n)void 0!==n[s]&&o.setRequestHeader(s,n[s]+"");o.send(t.hasContent&&t.data||null),e=function(i,n){var s,l,u;if(e&&(n||4===o.readyState))if(delete di[a],e=void 0,o.onreadystatechange=vt.noop,n)4!==o.readyState&&o.abort();else{u={},s=o.status,"string"==typeof o.responseText&&(u.text=o.responseText);try{l=o.statusText}catch(h){l=""}s||!t.isLocal||t.crossDomain?1223===s&&(s=204):s=u.text?200:404}u&&r(s,l,u,o.getAllResponseHeaders())},t.async?4===o.readyState?i.setTimeout(e):o.onreadystatechange=di[a]=e:e()},abort:function(){e&&e(void 0,!0)}}}}),vt.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(t){return vt.globalEval(t),t}}}),vt.ajaxPrefilter("script",function(t){void 0===t.cache&&(t.cache=!1),t.crossDomain&&(t.type="GET",t.global=!1)}),vt.ajaxTransport("script",function(t){if(t.crossDomain){var e,i=at.head||vt("head")[0]||at.documentElement;return{send:function(n,r){e=at.createElement("script"),e.async=!0,t.scriptCharset&&(e.charset=t.scriptCharset),e.src=t.url,e.onload=e.onreadystatechange=function(t,i){(i||!e.readyState||/loaded|complete/.test(e.readyState))&&(e.onload=e.onreadystatechange=null,e.parentNode&&e.parentNode.removeChild(e),e=null,i||r(200,"success"))},i.insertBefore(e,i.firstChild)},abort:function(){e&&e.onload(void 0,!0)}}}});var fi=[],gi=/(=)\?(?=&|$)|\?\?/;vt.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var t=fi.pop()||vt.expando+"_"+Ue++;return this[t]=!0,t}}),vt.ajaxPrefilter("json jsonp",function(t,e,n){var r,s,o,a=t.jsonp!==!1&&(gi.test(t.url)?"url":"string"==typeof t.data&&0===(t.contentType||"").indexOf("application/x-www-form-urlencoded")&&gi.test(t.data)&&"data");return a||"jsonp"===t.dataTypes[0]?(r=t.jsonpCallback=vt.isFunction(t.jsonpCallback)?t.jsonpCallback():t.jsonpCallback,a?t[a]=t[a].replace(gi,"$1"+r):t.jsonp!==!1&&(t.url+=(Ve.test(t.url)?"&":"?")+t.jsonp+"="+r),t.converters["script json"]=function(){return o||vt.error(r+" was not called"),o[0]},t.dataTypes[0]="json",s=i[r],i[r]=function(){o=arguments},n.always(function(){void 0===s?vt(i).removeProp(r):i[r]=s,t[r]&&(t.jsonpCallback=e.jsonpCallback,fi.push(r)),o&&vt.isFunction(s)&&s(o[0]),o=s=void 0}),"script"):void 0}),vt.parseHTML=function(t,e,i){if(!t||"string"!=typeof t)return null;"boolean"==typeof e&&(i=e,e=!1),e=e||at;var n=Tt.exec(t),r=!i&&[];return n?[e.createElement(n[1])]:(n=_([t],e,r),r&&r.length&&vt(r).remove(),vt.merge([],n.childNodes))};var mi=vt.fn.load;vt.fn.load=function(t,e,i){if("string"!=typeof t&&mi)return mi.apply(this,arguments);var n,r,s,o=this,a=t.indexOf(" ");return a>-1&&(n=vt.trim(t.slice(a,t.length)),t=t.slice(0,a)),vt.isFunction(e)?(i=e,e=void 0):e&&"object"==typeof e&&(r="POST"),o.length>0&&vt.ajax({url:t,type:r||"GET",dataType:"html",data:e}).done(function(t){s=arguments,o.html(n?vt("
                                            ").append(vt.parseHTML(t)).find(n):t)}).always(i&&function(t,e){o.each(function(){i.apply(this,s||[t.responseText,e,t])})}),this},vt.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(t,e){vt.fn[e]=function(t){return this.on(e,t)}}),vt.expr.filters.animated=function(t){return vt.grep(vt.timers,function(e){return t===e.elem}).length},vt.offset={setOffset:function(t,e,i){var n,r,s,o,a,l,u,h=vt.css(t,"position"),c=vt(t),d={};"static"===h&&(t.style.position="relative"),a=c.offset(),s=vt.css(t,"top"),l=vt.css(t,"left"),u=("absolute"===h||"fixed"===h)&&vt.inArray("auto",[s,l])>-1,u?(n=c.position(),o=n.top,r=n.left):(o=parseFloat(s)||0,r=parseFloat(l)||0),vt.isFunction(e)&&(e=e.call(t,i,vt.extend({},a))),null!=e.top&&(d.top=e.top-a.top+o),null!=e.left&&(d.left=e.left-a.left+r),"using"in e?e.using.call(t,d):c.css(d)}},vt.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){vt.offset.setOffset(this,t,e)});var e,i,n={top:0,left:0},r=this[0],s=r&&r.ownerDocument;if(s)return e=s.documentElement,vt.contains(e,r)?("undefined"!=typeof r.getBoundingClientRect&&(n=r.getBoundingClientRect()),i=st(s),{top:n.top+(i.pageYOffset||e.scrollTop)-(e.clientTop||0),left:n.left+(i.pageXOffset||e.scrollLeft)-(e.clientLeft||0)}):n},position:function(){if(this[0]){var t,e,i={top:0,left:0},n=this[0];return"fixed"===vt.css(n,"position")?e=n.getBoundingClientRect():(t=this.offsetParent(),e=this.offset(),vt.nodeName(t[0],"html")||(i=t.offset()),i.top+=vt.css(t[0],"borderTopWidth",!0),i.left+=vt.css(t[0],"borderLeftWidth",!0)),{top:e.top-i.top-vt.css(n,"marginTop",!0),left:e.left-i.left-vt.css(n,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){for(var t=this.offsetParent;t&&!vt.nodeName(t,"html")&&"static"===vt.css(t,"position");)t=t.offsetParent;return t||ve})}}),vt.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,e){var i=/Y/.test(e);vt.fn[t]=function(n){return zt(this,function(t,n,r){var s=st(t);return void 0===r?s?e in s?s[e]:s.document.documentElement[n]:t[n]:void(s?s.scrollTo(i?vt(s).scrollLeft():r,i?r:vt(s).scrollTop()):t[n]=r)},t,n,arguments.length,null)}}),vt.each(["top","left"],function(t,e){vt.cssHooks[e]=j(gt.pixelPosition,function(t,i){return i?(i=be(t,e),ge.test(i)?vt(t).position()[e]+"px":i):void 0})}),vt.each({Height:"height",Width:"width"},function(t,e){vt.each({padding:"inner"+t,content:e,"":"outer"+t},function(i,n){vt.fn[n]=function(n,r){var s=arguments.length&&(i||"boolean"!=typeof n),o=i||(n===!0||r===!0?"margin":"border");return zt(this,function(e,i,n){var r;return vt.isWindow(e)?e.document.documentElement["client"+t]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+t],r["scroll"+t],e.body["offset"+t],r["offset"+t],r["client"+t])):void 0===n?vt.css(e,i,o):vt.style(e,i,n,o)},e,s?n:void 0,s,null)}})}),vt.fn.extend({bind:function(t,e,i){return this.on(t,null,e,i)},unbind:function(t,e){return this.off(t,null,e)},delegate:function(t,e,i,n){return this.on(e,t,i,n)},undelegate:function(t,e,i){return 1===arguments.length?this.off(t,"**"):this.off(e,t||"**",i)}}),vt.fn.size=function(){return this.length},vt.fn.andSelf=vt.fn.addBack,n=[],r=function(){return vt}.apply(e,n),!(void 0!==r&&(t.exports=r));var vi=i.jQuery,yi=i.$;return vt.noConflict=function(t){return i.$===vt&&(i.$=yi),t&&i.jQuery===vt&&(i.jQuery=vi),vt},s||(i.jQuery=i.$=vt),vt})},function(t,e,i){var n,r;(function(){function i(t){function e(e,i,n,r,s,o){for(;s>=0&&o>s;s+=t){var a=r?r[s]:s;n=i(n,e[a],a,e)}return n}return function(i,n,r,s){n=C(n,s,4);var o=!O(i)&&x.keys(i),a=(o||i).length,l=t>0?0:a-1;return arguments.length<3&&(r=i[o?o[l]:l],l+=t),e(i,n,r,o,l,a)}}function s(t){return function(e,i,n){i=S(i,n);for(var r=N(e),s=t>0?0:r-1;s>=0&&r>s;s+=t)if(i(e[s],s,e))return s;return-1}}function o(t,e,i){return function(n,r,s){var o=0,a=N(n);if("number"==typeof s)t>0?o=s>=0?s:Math.max(s+a,o):a=s>=0?Math.min(s+1,a):s+a+1;else if(i&&s&&a)return s=i(n,r),n[s]===r?s:-1;if(r!==r)return s=e(f.call(n,o,a),x.isNaN),s>=0?s+o:-1;for(s=t>0?o:a-1;s>=0&&a>s;s+=t)if(n[s]===r)return s;return-1}}function a(t,e){var i=j.length,n=t.constructor,r=x.isFunction(n)&&n.prototype||c,s="constructor";for(x.has(t,s)&&!x.contains(e,s)&&e.push(s);i--;)s=j[i],s in t&&t[s]!==r[s]&&!x.contains(e,s)&&e.push(s)}var l=this,u=l._,h=Array.prototype,c=Object.prototype,d=Function.prototype,p=h.push,f=h.slice,g=c.toString,m=c.hasOwnProperty,v=Array.isArray,y=Object.keys,b=d.bind,w=Object.create,_=function(){},x=function(t){return t instanceof x?t:this instanceof x?void(this._wrapped=t):new x(t)};"undefined"!=typeof t&&t.exports&&(e=t.exports=x),e._=x,x.VERSION="1.8.3";var C=function(t,e,i){if(void 0===e)return t;switch(null==i?3:i){case 1:return function(i){return t.call(e,i)};case 2:return function(i,n){return t.call(e,i,n)};case 3:return function(i,n,r){return t.call(e,i,n,r)};case 4:return function(i,n,r,s){return t.call(e,i,n,r,s)}}return function(){return t.apply(e,arguments)}},S=function(t,e,i){return null==t?x.identity:x.isFunction(t)?C(t,e,i):x.isObject(t)?x.matcher(t):x.property(t)};x.iteratee=function(t,e){return S(t,e,1/0)};var k=function(t,e){return function(i){var n=arguments.length;if(2>n||null==i)return i;for(var r=1;n>r;r++)for(var s=arguments[r],o=t(s),a=o.length,l=0;a>l;l++){var u=o[l];e&&void 0!==i[u]||(i[u]=s[u])}return i}},T=function(t){if(!x.isObject(t))return{};if(w)return w(t);_.prototype=t;var e=new _;return _.prototype=null,e},E=function(t){return function(e){return null==e?void 0:e[t]}},A=Math.pow(2,53)-1,N=E("length"),O=function(t){var e=N(t);return"number"==typeof e&&e>=0&&A>=e};x.each=x.forEach=function(t,e,i){e=C(e,i);var n,r;if(O(t))for(n=0,r=t.length;r>n;n++)e(t[n],n,t);else{var s=x.keys(t);for(n=0,r=s.length;r>n;n++)e(t[s[n]],s[n],t)}return t},x.map=x.collect=function(t,e,i){e=S(e,i);for(var n=!O(t)&&x.keys(t),r=(n||t).length,s=Array(r),o=0;r>o;o++){var a=n?n[o]:o;s[o]=e(t[a],a,t)}return s},x.reduce=x.foldl=x.inject=i(1),x.reduceRight=x.foldr=i(-1),x.find=x.detect=function(t,e,i){var n;return n=O(t)?x.findIndex(t,e,i):x.findKey(t,e,i),void 0!==n&&-1!==n?t[n]:void 0},x.filter=x.select=function(t,e,i){var n=[];return e=S(e,i),x.each(t,function(t,i,r){e(t,i,r)&&n.push(t)}),n},x.reject=function(t,e,i){return x.filter(t,x.negate(S(e)),i)},x.every=x.all=function(t,e,i){e=S(e,i);for(var n=!O(t)&&x.keys(t),r=(n||t).length,s=0;r>s;s++){var o=n?n[s]:s;if(!e(t[o],o,t))return!1}return!0},x.some=x.any=function(t,e,i){e=S(e,i);for(var n=!O(t)&&x.keys(t),r=(n||t).length,s=0;r>s;s++){var o=n?n[s]:s;if(e(t[o],o,t))return!0}return!1},x.contains=x.includes=x.include=function(t,e,i,n){return O(t)||(t=x.values(t)),("number"!=typeof i||n)&&(i=0),x.indexOf(t,e,i)>=0},x.invoke=function(t,e){var i=f.call(arguments,2),n=x.isFunction(e);return x.map(t,function(t){var r=n?e:t[e];return null==r?r:r.apply(t,i)})},x.pluck=function(t,e){return x.map(t,x.property(e))},x.where=function(t,e){return x.filter(t,x.matcher(e))},x.findWhere=function(t,e){return x.find(t,x.matcher(e))},x.max=function(t,e,i){var n,r,s=-(1/0),o=-(1/0);if(null==e&&null!=t){t=O(t)?t:x.values(t);for(var a=0,l=t.length;l>a;a++)n=t[a],n>s&&(s=n)}else e=S(e,i),x.each(t,function(t,i,n){r=e(t,i,n),(r>o||r===-(1/0)&&s===-(1/0))&&(s=t,o=r)});return s},x.min=function(t,e,i){var n,r,s=1/0,o=1/0;if(null==e&&null!=t){t=O(t)?t:x.values(t);for(var a=0,l=t.length;l>a;a++)n=t[a],s>n&&(s=n)}else e=S(e,i),x.each(t,function(t,i,n){r=e(t,i,n),(o>r||r===1/0&&s===1/0)&&(s=t,o=r)});return s},x.shuffle=function(t){for(var e,i=O(t)?t:x.values(t),n=i.length,r=Array(n),s=0;n>s;s++)e=x.random(0,s),e!==s&&(r[s]=r[e]),r[e]=i[s];return r},x.sample=function(t,e,i){return null==e||i?(O(t)||(t=x.values(t)),t[x.random(t.length-1)]):x.shuffle(t).slice(0,Math.max(0,e))},x.sortBy=function(t,e,i){return e=S(e,i),x.pluck(x.map(t,function(t,i,n){return{value:t,index:i,criteria:e(t,i,n)}}).sort(function(t,e){var i=t.criteria,n=e.criteria;if(i!==n){if(i>n||void 0===i)return 1;if(n>i||void 0===n)return-1}return t.index-e.index}),"value")};var M=function(t){return function(e,i,n){var r={};return i=S(i,n),x.each(e,function(n,s){var o=i(n,s,e);t(r,n,o)}),r}};x.groupBy=M(function(t,e,i){x.has(t,i)?t[i].push(e):t[i]=[e]}),x.indexBy=M(function(t,e,i){t[i]=e}),x.countBy=M(function(t,e,i){x.has(t,i)?t[i]++:t[i]=1}),x.toArray=function(t){return t?x.isArray(t)?f.call(t):O(t)?x.map(t,x.identity):x.values(t):[]},x.size=function(t){return null==t?0:O(t)?t.length:x.keys(t).length},x.partition=function(t,e,i){e=S(e,i);var n=[],r=[];return x.each(t,function(t,i,s){(e(t,i,s)?n:r).push(t)}),[n,r]},x.first=x.head=x.take=function(t,e,i){return null!=t?null==e||i?t[0]:x.initial(t,t.length-e):void 0},x.initial=function(t,e,i){return f.call(t,0,Math.max(0,t.length-(null==e||i?1:e)))},x.last=function(t,e,i){return null!=t?null==e||i?t[t.length-1]:x.rest(t,Math.max(0,t.length-e)):void 0},x.rest=x.tail=x.drop=function(t,e,i){return f.call(t,null==e||i?1:e)},x.compact=function(t){return x.filter(t,x.identity)};var D=function(t,e,i,n){for(var r=[],s=0,o=n||0,a=N(t);a>o;o++){var l=t[o];if(O(l)&&(x.isArray(l)||x.isArguments(l))){e||(l=D(l,e,i));var u=0,h=l.length;for(r.length+=h;h>u;)r[s++]=l[u++]}else i||(r[s++]=l)}return r};x.flatten=function(t,e){return D(t,e,!1)},x.without=function(t){return x.difference(t,f.call(arguments,1))},x.uniq=x.unique=function(t,e,i,n){x.isBoolean(e)||(n=i,i=e,e=!1),null!=i&&(i=S(i,n));for(var r=[],s=[],o=0,a=N(t);a>o;o++){var l=t[o],u=i?i(l,o,t):l;e?(o&&s===u||r.push(l),s=u):i?x.contains(s,u)||(s.push(u),r.push(l)):x.contains(r,l)||r.push(l)}return r},x.union=function(){return x.uniq(D(arguments,!0,!0))},x.intersection=function(t){for(var e=[],i=arguments.length,n=0,r=N(t);r>n;n++){var s=t[n];if(!x.contains(e,s)){for(var o=1;i>o&&x.contains(arguments[o],s);o++);o===i&&e.push(s)}}return e},x.difference=function(t){var e=D(arguments,!0,!0,1);return x.filter(t,function(t){return!x.contains(e,t)})},x.zip=function(){return x.unzip(arguments)},x.unzip=function(t){for(var e=t&&x.max(t,N).length||0,i=Array(e),n=0;e>n;n++)i[n]=x.pluck(t,n);return i},x.object=function(t,e){for(var i={},n=0,r=N(t);r>n;n++)e?i[t[n]]=e[n]:i[t[n][0]]=t[n][1];return i},x.findIndex=s(1),x.findLastIndex=s(-1),x.sortedIndex=function(t,e,i,n){i=S(i,n,1);for(var r=i(e),s=0,o=N(t);o>s;){var a=Math.floor((s+o)/2);i(t[a])s;s++,t+=i)r[s]=t;return r};var P=function(t,e,i,n,r){if(!(n instanceof e))return t.apply(i,r);var s=T(t.prototype),o=t.apply(s,r);return x.isObject(o)?o:s};x.bind=function(t,e){if(b&&t.bind===b)return b.apply(t,f.call(arguments,1));if(!x.isFunction(t))throw new TypeError("Bind must be called on a function");var i=f.call(arguments,2),n=function(){return P(t,n,e,this,i.concat(f.call(arguments)))};return n},x.partial=function(t){var e=f.call(arguments,1),i=function(){for(var n=0,r=e.length,s=Array(r),o=0;r>o;o++)s[o]=e[o]===x?arguments[n++]:e[o];for(;n=n)throw new Error("bindAll must be passed function names");for(e=1;n>e;e++)i=arguments[e],t[i]=x.bind(t[i],t);return t},x.memoize=function(t,e){var i=function(n){var r=i.cache,s=""+(e?e.apply(this,arguments):n);return x.has(r,s)||(r[s]=t.apply(this,arguments)),r[s]};return i.cache={},i},x.delay=function(t,e){var i=f.call(arguments,2);return setTimeout(function(){return t.apply(null,i)},e)},x.defer=x.partial(x.delay,x,1),x.throttle=function(t,e,i){var n,r,s,o=null,a=0;i||(i={});var l=function(){a=i.leading===!1?0:x.now(),o=null,s=t.apply(n,r),o||(n=r=null)};return function(){var u=x.now();a||i.leading!==!1||(a=u);var h=e-(u-a);return n=this,r=arguments,0>=h||h>e?(o&&(clearTimeout(o),o=null),a=u,s=t.apply(n,r),o||(n=r=null)):o||i.trailing===!1||(o=setTimeout(l,h)),s}},x.debounce=function(t,e,i){var n,r,s,o,a,l=function(){var u=x.now()-o;e>u&&u>=0?n=setTimeout(l,e-u):(n=null,i||(a=t.apply(s,r),n||(s=r=null)))};return function(){s=this,r=arguments,o=x.now();var u=i&&!n;return n||(n=setTimeout(l,e)),u&&(a=t.apply(s,r),s=r=null),a}},x.wrap=function(t,e){return x.partial(e,t)},x.negate=function(t){return function(){return!t.apply(this,arguments)}},x.compose=function(){var t=arguments,e=t.length-1;return function(){for(var i=e,n=t[e].apply(this,arguments);i--;)n=t[i].call(this,n);return n}},x.after=function(t,e){return function(){return--t<1?e.apply(this,arguments):void 0}},x.before=function(t,e){var i;return function(){return--t>0&&(i=e.apply(this,arguments)),1>=t&&(e=null),i}},x.once=x.partial(x.before,2);var R=!{toString:null}.propertyIsEnumerable("toString"),j=["valueOf","isPrototypeOf","toString","propertyIsEnumerable","hasOwnProperty","toLocaleString"];x.keys=function(t){if(!x.isObject(t))return[];if(y)return y(t);var e=[];for(var i in t)x.has(t,i)&&e.push(i);return R&&a(t,e),e},x.allKeys=function(t){if(!x.isObject(t))return[];var e=[];for(var i in t)e.push(i);return R&&a(t,e),e},x.values=function(t){for(var e=x.keys(t),i=e.length,n=Array(i),r=0;i>r;r++)n[r]=t[e[r]];return n},x.mapObject=function(t,e,i){e=S(e,i);for(var n,r=x.keys(t),s=r.length,o={},a=0;s>a;a++)n=r[a],o[n]=e(t[n],n,t);return o},x.pairs=function(t){for(var e=x.keys(t),i=e.length,n=Array(i),r=0;i>r;r++)n[r]=[e[r],t[e[r]]];return n},x.invert=function(t){for(var e={},i=x.keys(t),n=0,r=i.length;r>n;n++)e[t[i[n]]]=i[n];return e},x.functions=x.methods=function(t){var e=[];for(var i in t)x.isFunction(t[i])&&e.push(i);return e.sort()},x.extend=k(x.allKeys),x.extendOwn=x.assign=k(x.keys),x.findKey=function(t,e,i){e=S(e,i);for(var n,r=x.keys(t),s=0,o=r.length;o>s;s++)if(n=r[s],e(t[n],n,t))return n},x.pick=function(t,e,i){var n,r,s={},o=t;if(null==o)return s;x.isFunction(e)?(r=x.allKeys(o),n=C(e,i)):(r=D(arguments,!1,!1,1),n=function(t,e,i){return e in i},o=Object(o));for(var a=0,l=r.length;l>a;a++){var u=r[a],h=o[u];n(h,u,o)&&(s[u]=h)}return s},x.omit=function(t,e,i){if(x.isFunction(e))e=x.negate(e);else{var n=x.map(D(arguments,!1,!1,1),String);e=function(t,e){return!x.contains(n,e)}}return x.pick(t,e,i)},x.defaults=k(x.allKeys,!0),x.create=function(t,e){var i=T(t);return e&&x.extendOwn(i,e),i},x.clone=function(t){return x.isObject(t)?x.isArray(t)?t.slice():x.extend({},t):t},x.tap=function(t,e){return e(t),t},x.isMatch=function(t,e){var i=x.keys(e),n=i.length;if(null==t)return!n;for(var r=Object(t),s=0;n>s;s++){var o=i[s];if(e[o]!==r[o]||!(o in r))return!1}return!0};var L=function(t,e,i,n){if(t===e)return 0!==t||1/t===1/e;if(null==t||null==e)return t===e;t instanceof x&&(t=t._wrapped),e instanceof x&&(e=e._wrapped);var r=g.call(t);if(r!==g.call(e))return!1;switch(r){case"[object RegExp]":case"[object String]":return""+t==""+e;case"[object Number]":return+t!==+t?+e!==+e:0===+t?1/+t===1/e:+t===+e;case"[object Date]":case"[object Boolean]":return+t===+e}var s="[object Array]"===r;if(!s){if("object"!=typeof t||"object"!=typeof e)return!1;var o=t.constructor,a=e.constructor;if(o!==a&&!(x.isFunction(o)&&o instanceof o&&x.isFunction(a)&&a instanceof a)&&"constructor"in t&&"constructor"in e)return!1}i=i||[],n=n||[];for(var l=i.length;l--;)if(i[l]===t)return n[l]===e;if(i.push(t),n.push(e),s){if(l=t.length,l!==e.length)return!1;for(;l--;)if(!L(t[l],e[l],i,n))return!1}else{var u,h=x.keys(t);if(l=h.length,x.keys(e).length!==l)return!1;for(;l--;)if(u=h[l],!x.has(e,u)||!L(t[u],e[u],i,n))return!1}return i.pop(),n.pop(),!0};x.isEqual=function(t,e){return L(t,e)},x.isEmpty=function(t){return null==t?!0:O(t)&&(x.isArray(t)||x.isString(t)||x.isArguments(t))?0===t.length:0===x.keys(t).length},x.isElement=function(t){return!(!t||1!==t.nodeType)},x.isArray=v||function(t){return"[object Array]"===g.call(t)},x.isObject=function(t){var e=typeof t;return"function"===e||"object"===e&&!!t},x.each(["Arguments","Function","String","Number","Date","RegExp","Error"],function(t){x["is"+t]=function(e){return g.call(e)==="[object "+t+"]"}}),x.isArguments(arguments)||(x.isArguments=function(t){return x.has(t,"callee")}),"function"!=typeof/./&&"object"!=typeof Int8Array&&(x.isFunction=function(t){return"function"==typeof t||!1}),x.isFinite=function(t){return isFinite(t)&&!isNaN(parseFloat(t))},x.isNaN=function(t){return x.isNumber(t)&&t!==+t},x.isBoolean=function(t){return t===!0||t===!1||"[object Boolean]"===g.call(t)},x.isNull=function(t){return null===t},x.isUndefined=function(t){return void 0===t},x.has=function(t,e){return null!=t&&m.call(t,e)},x.noConflict=function(){return l._=u,this},x.identity=function(t){return t},x.constant=function(t){return function(){return t}},x.noop=function(){},x.property=E,x.propertyOf=function(t){return null==t?function(){}:function(e){return t[e]}},x.matcher=x.matches=function(t){return t=x.extendOwn({},t),function(e){return x.isMatch(e,t)}},x.times=function(t,e,i){var n=Array(Math.max(0,t));e=C(e,i,1);for(var r=0;t>r;r++)n[r]=e(r);return n},x.random=function(t,e){return null==e&&(e=t,t=0),t+Math.floor(Math.random()*(e-t+1))},x.now=Date.now||function(){return(new Date).getTime()};var H={"&":"&","<":"<",">":">",'"':""","'":"'","`":"`"},I=x.invert(H),$=function(t){var e=function(e){return t[e]},i="(?:"+x.keys(t).join("|")+")",n=RegExp(i),r=RegExp(i,"g");return function(t){return t=null==t?"":""+t,n.test(t)?t.replace(r,e):t}};x.escape=$(H),x.unescape=$(I),x.result=function(t,e,i){var n=null==t?void 0:t[e];return void 0===n&&(n=i),x.isFunction(n)?n.call(t):n};var F=0;x.uniqueId=function(t){var e=++F+"";return t?t+e:e},x.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var q=/(.)^/,W={"'":"'","\\":"\\","\r":"r","\n":"n","\u2028":"u2028","\u2029":"u2029"},z=/\\|'|\r|\n|\u2028|\u2029/g,B=function(t){return"\\"+W[t]};x.template=function(t,e,i){!e&&i&&(e=i),e=x.defaults({},e,x.templateSettings);var n=RegExp([(e.escape||q).source,(e.interpolate||q).source,(e.evaluate||q).source].join("|")+"|$","g"),r=0,s="__p+='";t.replace(n,function(e,i,n,o,a){return s+=t.slice(r,a).replace(z,B),r=a+e.length,i?s+="'+\n((__t=("+i+"))==null?'':_.escape(__t))+\n'":n?s+="'+\n((__t=("+n+"))==null?'':__t)+\n'":o&&(s+="';\n"+o+"\n__p+='"),e}),s+="';\n",e.variable||(s="with(obj||{}){\n"+s+"}\n"),s="var __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'');};\n"+s+"return __p;\n";try{var o=new Function(e.variable||"obj","_",s)}catch(a){throw a.source=s,a}var l=function(t){return o.call(this,t,x)},u=e.variable||"obj";return l.source="function("+u+"){\n"+s+"}",l},x.chain=function(t){var e=x(t);return e._chain=!0,e};var U=function(t,e){return t._chain?x(e).chain():e};x.mixin=function(t){x.each(x.functions(t),function(e){var i=x[e]=t[e];x.prototype[e]=function(){var t=[this._wrapped];return p.apply(t,arguments),U(this,i.apply(x,t))}})},x.mixin(x),x.each(["pop","push","reverse","shift","sort","splice","unshift"],function(t){var e=h[t];x.prototype[t]=function(){var i=this._wrapped;return e.apply(i,arguments),"shift"!==t&&"splice"!==t||0!==i.length||delete i[0],U(this,i)}}),x.each(["concat","join","slice"],function(t){var e=h[t];x.prototype[t]=function(){return U(this,e.apply(this._wrapped,arguments))}}),x.prototype.value=function(){return this._wrapped},x.prototype.valueOf=x.prototype.toJSON=x.prototype.value,x.prototype.toString=function(){return""+this._wrapped},n=[],r=function(){return x}.apply(e,n),!(void 0!==r&&(t.exports=r))}).call(this)},function(t,e,i){var n,r;(function(s){!function(o){var a="object"==typeof self&&self.self===self&&self||"object"==typeof s&&s.global===s&&s;n=[i(2),i(1),e],r=function(t,e,i){a.Backbone=o(a,i,t,e)}.apply(e,n),!(void 0!==r&&(t.exports=r))}(function(t,e,i,n){var r=t.Backbone,s=Array.prototype.slice;e.VERSION="1.3.3",e.$=n,e.noConflict=function(){return t.Backbone=r,this},e.emulateHTTP=!1,e.emulateJSON=!1;var o=function(t,e,n){switch(t){case 1:return function(){return i[e](this[n])};case 2:return function(t){return i[e](this[n],t)};case 3:return function(t,r){return i[e](this[n],l(t,this),r); -};case 4:return function(t,r,s){return i[e](this[n],l(t,this),r,s)};default:return function(){var t=s.call(arguments);return t.unshift(this[n]),i[e].apply(i,t)}}},a=function(t,e,n){i.each(e,function(e,r){i[r]&&(t.prototype[r]=o(e,r,n))})},l=function(t,e){return i.isFunction(t)?t:i.isObject(t)&&!e._isModel(t)?u(t):i.isString(t)?function(e){return e.get(t)}:t},u=function(t){var e=i.matches(t);return function(t){return e(t.attributes)}},h=e.Events={},c=/\s+/,d=function(t,e,n,r,s){var o,a=0;if(n&&"object"==typeof n){void 0!==r&&"context"in s&&void 0===s.context&&(s.context=r);for(o=i.keys(n);an;n++)i[n]=arguments[n+1];return d(v,this._events,t,void 0,i),this};var v=function(t,e,i,n){if(t){var r=t[e],s=t.all;r&&s&&(s=s.slice()),r&&y(r,n),s&&y(s,[e].concat(n))}return t},y=function(t,e){var i,n=-1,r=t.length,s=e[0],o=e[1],a=e[2];switch(e.length){case 0:for(;++nn;n++)t[n+i]=e[n];for(n=0;nthis.length&&(r=this.length),0>r&&(r+=this.length+1);var s,o,a=[],l=[],u=[],h=[],c={},d=e.add,p=e.merge,f=e.remove,g=!1,m=this.comparator&&null==r&&e.sort!==!1,v=i.isString(this.comparator)?this.comparator:null;for(o=0;ot&&(t+=this.length),this.models[t]},where:function(t,e){return this[e?"find":"filter"](t)},findWhere:function(t){return this.where(t,!0)},sort:function(t){var e=this.comparator;if(!e)throw new Error("Cannot sort a set without a comparator");t||(t={});var n=e.length;return i.isFunction(e)&&(e=i.bind(e,this)),1===n||i.isString(e)?this.models=this.sortBy(e):this.models.sort(e),t.silent||this.trigger("sort",this,t),this},pluck:function(t){return this.map(t+"")},fetch:function(t){t=i.extend({parse:!0},t);var e=t.success,n=this;return t.success=function(i){var r=t.reset?"reset":"set";n[r](i,t),e&&e.call(t.context,n,i,t),n.trigger("sync",n,i,t)},q(this,t),this.sync("read",this,t)},create:function(t,e){e=e?i.clone(e):{};var n=e.wait;if(t=this._prepareModel(t,e),!t)return!1;n||this.add(t,e);var r=this,s=e.success;return e.success=function(t,e,i){n&&r.add(t,i),s&&s.call(i.context,t,e,i)},t.save(null,e),t},parse:function(t,e){return t},clone:function(){return new this.constructor(this.models,{model:this.model,comparator:this.comparator})},modelId:function(t){return t[this.model.prototype.idAttribute||"id"]},_reset:function(){this.length=0,this.models=[],this._byId={}},_prepareModel:function(t,e){if(this._isModel(t))return t.collection||(t.collection=this),t;e=e?i.clone(e):{},e.collection=this;var n=new this.model(t,e);return n.validationError?(this.trigger("invalid",this,n.validationError,e),!1):n},_removeModels:function(t,e){for(var i=[],n=0;n7),this._useHashChange=this._wantsHashChange&&this._hasHashChange,this._wantsPushState=!!this.options.pushState,this._hasPushState=!(!this.history||!this.history.pushState),this._usePushState=this._wantsPushState&&this._hasPushState,this.fragment=this.getFragment(),this.root=("/"+this.root+"/").replace(H,"/"),this._wantsHashChange&&this._wantsPushState){if(!this._hasPushState&&!this.atRoot()){var e=this.root.slice(0,-1)||"/";return this.location.replace(e+"#"+this.getPath()),!0}this._hasPushState&&this.atRoot()&&this.navigate(this.getHash(),{replace:!0})}if(!this._hasHashChange&&this._wantsHashChange&&!this._usePushState){this.iframe=document.createElement("iframe"),this.iframe.src="javascript:0",this.iframe.style.display="none",this.iframe.tabIndex=-1;var n=document.body,r=n.insertBefore(this.iframe,n.firstChild).contentWindow;r.document.open(),r.document.close(),r.location.hash="#"+this.fragment}var s=window.addEventListener||function(t,e){return attachEvent("on"+t,e)};return this._usePushState?s("popstate",this.checkUrl,!1):this._useHashChange&&!this.iframe?s("hashchange",this.checkUrl,!1):this._wantsHashChange&&(this._checkUrlInterval=setInterval(this.checkUrl,this.interval)),this.options.silent?void 0:this.loadUrl()},stop:function(){var t=window.removeEventListener||function(t,e){return detachEvent("on"+t,e)};this._usePushState?t("popstate",this.checkUrl,!1):this._useHashChange&&!this.iframe&&t("hashchange",this.checkUrl,!1),this.iframe&&(document.body.removeChild(this.iframe),this.iframe=null),this._checkUrlInterval&&clearInterval(this._checkUrlInterval),j.started=!1},route:function(t,e){this.handlers.unshift({route:t,callback:e})},checkUrl:function(t){var e=this.getFragment();return e===this.fragment&&this.iframe&&(e=this.getHash(this.iframe.contentWindow)),e===this.fragment?!1:(this.iframe&&this.navigate(e),void this.loadUrl())},loadUrl:function(t){return this.matchRoot()?(t=this.fragment=this.getFragment(t),i.some(this.handlers,function(e){return e.route.test(t)?(e.callback(t),!0):void 0})):!1},navigate:function(t,e){if(!j.started)return!1;e&&e!==!0||(e={trigger:!!e}),t=this.getFragment(t||"");var i=this.root;(""===t||"?"===t.charAt(0))&&(i=i.slice(0,-1)||"/");var n=i+t;if(t=this.decodeFragment(t.replace(I,"")),this.fragment!==t){if(this.fragment=t,this._usePushState)this.history[e.replace?"replaceState":"pushState"]({},document.title,n);else{if(!this._wantsHashChange)return this.location.assign(n);if(this._updateHash(this.location,t,e.replace),this.iframe&&t!==this.getHash(this.iframe.contentWindow)){var r=this.iframe.contentWindow;e.replace||(r.document.open(),r.document.close()),this._updateHash(r.location,t,e.replace)}}return e.trigger?this.loadUrl(t):void 0}},_updateHash:function(t,e,i){if(i){var n=t.href.replace(/(javascript:|#).*$/,"");t.replace(n+"#"+e)}else t.hash="#"+e}}),e.history=new j;var $=function(t,e){var n,r=this;return n=t&&i.has(t,"constructor")?t.constructor:function(){return r.apply(this,arguments)},i.extend(n,r,e),n.prototype=i.create(r.prototype,t),n.prototype.constructor=n,n.__super__=r.prototype,n};b.extend=_.extend=O.extend=T.extend=j.extend=$;var F=function(){throw new Error('A "url" property or function must be specified')},q=function(t,e){var i=e.error;e.error=function(n){i&&i.call(e.context,t,n,e),t.trigger("error",t,n,e)}};return e})}).call(e,function(){return this}())},function(t,e,i){var n,r;(function(i,s,o){n=[],r=function(){function t(t){return'
                                            ")}),a.show({backdrop:!0})}var s=t,o=function(t){this.$overlay=t.overlay,this.$dialog=t.dialog,this.$header=this.$dialog.find(".modal-header"),this.$body=this.$dialog.find(".modal-body"),this.$footer=this.$dialog.find(".modal-footer"),this.$backdrop=t.backdrop,this.$header.find(".close").on("click",s.proxy(this.hide,this))};s.extend(o.prototype,{setContent:function(t){this.$header.hide(),t.title&&(this.$header.find(".title").html(t.title),this.$header.show()),t.closeButton?(this.$header.find(".close").show(),this.$header.show()):this.$header.find(".close").hide(),this.$footer.hide();var e=this.$footer.find(".buttons").html("");t.buttons&&(s.each(t.buttons,function(t,i){e.append(s(" ").text(t).click(i)).append(" ")}),this.$footer.show());var i=this.$footer.find(".extra_buttons").html("");t.extra_buttons&&(s.each(t.extra_buttons,function(t,e){i.append(s("").text(t).click(e)).append(" ")}),this.$footer.show());var n=t.body;"progress"==n&&(n=s("
                                            ")),this.$body.html(n)},show:function(t,e){this.$dialog.is(":visible")||(t.backdrop?this.$backdrop.addClass("in"):this.$backdrop.removeClass("in"),this.$overlay.show(),this.$dialog.show(),this.$overlay.addClass("in"),this.$body.css("min-width",this.$body.width()),this.$body.css("max-height",s(window).height()-this.$footer.outerHeight()-this.$header.outerHeight()-parseInt(this.$dialog.css("padding-top"),10)-parseInt(this.$dialog.css("padding-bottom"),10))),e&&e()},hide:function(){var t=this;t.$dialog.fadeOut(function(){t.$overlay.hide(),t.$backdrop.removeClass("in"),t.$body.children().remove(),t.$body.css("min-width",void 0)})}});var a;return s(function(){a=new o({overlay:s("#top-modal"),dialog:s("#top-modal-dialog"),backdrop:s("#top-modal-backdrop")})}),{Modal:o,hide_modal:e,show_modal:i,show_message:n,show_in_overlay:r}}.apply(e,n),!(void 0!==r&&(t.exports=r))},function(t,e,i){var n,r;(function(s,o,a){n=[i(92),i(10),i(8),i(6)],r=function(t,e,i,n){var r=s.View.extend(n.LoggableMixin).extend({_logNamespace:"layout",el:"body",className:"full-content",_panelIds:["left","center","right"],defaultOptions:{message_box_visible:!1,message_box_content:"",message_box_class:"info",show_inactivity_warning:!1,inactivity_box_content:""},initialize:function(e){this.log(this+".initialize:",e),o.extend(this,o.pick(e,this._panelIds)),this.options=o.defaults(o.omit(e.config,this._panelIds),this.defaultOptions),Galaxy.modal=this.modal=new i.View,this.masthead=new t.View(this.options),this.$el.attr("scroll","no"),this.$el.html(this._template()),this.$el.append(this.masthead.frame.$el),this.$("#masthead").replaceWith(this.masthead.$el),this.$el.append(this.modal.$el),this.$messagebox=this.$("#messagebox"),this.$inactivebox=this.$("#inactivebox")},render:function(){return a(".select2-hidden-accessible").remove(),this.log(this+".render:"),this.masthead.render(),this.renderMessageBox(),this.renderInactivityBox(),this.renderPanels(),this._checkCommunicationServerOnline(),this},renderMessageBox:function(){if(this.options.message_box_visible){var t=this.options.message_box_content||"",e=this.options.message_box_class||"info";this.$el.addClass("has-message-box"),this.$messagebox.attr("class","panel-"+e+"-message").html(t).toggle(!!t).show()}else this.$el.removeClass("has-message-box"),this.$messagebox.hide();return this},renderInactivityBox:function(){if(this.options.show_inactivity_warning){var t=this.options.inactivity_box_content||"",e=a("").attr("href",Galaxy.root+"user/resend_verification").text("Resend verification");this.$el.addClass("has-inactivity-box"),this.$inactivebox.html(t+" ").append(e).toggle(!!t).show()}else this.$el.removeClass("has-inactivity-box"),this.$inactivebox.hide();return this},renderPanels:function(){var t=this;return this._panelIds.forEach(function(e){o.has(t,e)&&(t[e].setElement("#"+e),t[e].render())}),this.left||this.center.$el.css("left",0),this.right||this.center.$el.css("right",0),this},_template:function(){return['
                                            ','
                                            ','
                                            ','
                                            ','
                                            ',this.left?'
                                            ':"",this.center?'
                                            ':"",this.right?'",'
                                            '].join("")},hideSidePanels:function(){this.left&&this.left.hide(),this.right&&this.right.hide()},toString:function(){return"PageLayoutView"},_checkCommunicationServerOnline:function(){var t=window.Galaxy.config.communication_server_host,e=window.Galaxy.config.communication_server_port,i=a("#show-chat-online");void 0!==window.Galaxy.user.attributes.preferences&&"1"===window.Galaxy.user.attributes.preferences.communication_server?a.ajax({url:t+":"+e}).success(function(t){null!==window.Galaxy.user.id&&"hidden"===i.css("visibility")&&i.css("visibility","visible")}).error(function(t){i.css("visibility","hidden")}):i.css("visibility","hidden")}});return{PageLayoutView:r}}.apply(e,n),!(void 0!==r&&(t.exports=r))}).call(e,i(3),i(2),i(1))},function(t,e,i){(function(t){!function(t,e){var i,n;return n=e.document,i=function(){function i(i){var n;try{n=e.localStorage}catch(r){n=!1}this._options=t.extend({name:"tour",steps:[],container:"body",autoscroll:!0,keyboard:!0,storage:n,debug:!1,backdrop:!1,backdropContainer:"body",backdropPadding:0,redirect:!0,orphan:!1,duration:!1,delay:!1,basePath:"",template:'',afterSetState:function(t,e){},afterGetState:function(t,e){},afterRemoveState:function(t){},onStart:function(t){},onEnd:function(t){},onShow:function(t){},onShown:function(t){},onHide:function(t){},onHidden:function(t){},onNext:function(t){},onPrev:function(t){},onPause:function(t,e){},onResume:function(t,e){},onRedirectError:function(t){}},i),this._force=!1,this._inited=!1,this._current=null,this.backdrop={overlay:null,$element:null,$background:null,backgroundShown:!1,overlayElementShown:!1}}return i.prototype.addSteps=function(t){var e,i,n;for(i=0,n=t.length;n>i;i++)e=t[i],this.addStep(e);return this},i.prototype.addStep=function(t){return this._options.steps.push(t),this},i.prototype.getStep=function(e){return null!=this._options.steps[e]?t.extend({id:"step-"+e,path:"",host:"",placement:"right",title:"",content:"

                                            ",next:e===this._options.steps.length-1?-1:e+1,prev:e-1,animation:!0,container:this._options.container,autoscroll:this._options.autoscroll,backdrop:this._options.backdrop,backdropContainer:this._options.backdropContainer,backdropPadding:this._options.backdropPadding,redirect:this._options.redirect,reflexElement:this._options.steps[e].element,orphan:this._options.orphan,duration:this._options.duration,delay:this._options.delay,template:this._options.template,onShow:this._options.onShow,onShown:this._options.onShown,onHide:this._options.onHide,onHidden:this._options.onHidden,onNext:this._options.onNext,onPrev:this._options.onPrev,onPause:this._options.onPause,onResume:this._options.onResume,onRedirectError:this._options.onRedirectError},this._options.steps[e]):void 0},i.prototype.init=function(t){return this._force=t,this.ended()?(this._debug("Tour ended, init prevented."),this):(this.setCurrentStep(),this._initMouseNavigation(),this._initKeyboardNavigation(),this._onResize(function(t){return function(){return t.showStep(t._current)}}(this)),null!==this._current&&this.showStep(this._current),this._inited=!0,this)},i.prototype.start=function(t){var e;return null==t&&(t=!1),this._inited||this.init(t),null===this._current&&(e=this._makePromise(null!=this._options.onStart?this._options.onStart(this):void 0),this._callOnPromiseDone(e,this.showStep,0)),this},i.prototype.next=function(){var t;return t=this.hideStep(this._current),this._callOnPromiseDone(t,this._showNextStep)},i.prototype.prev=function(){var t;return t=this.hideStep(this._current),this._callOnPromiseDone(t,this._showPrevStep)},i.prototype.goTo=function(t){var e;return e=this.hideStep(this._current),this._callOnPromiseDone(e,this.showStep,t)},i.prototype.end=function(){var i,r;return i=function(i){return function(r){return t(n).off("click.tour-"+i._options.name),t(n).off("keyup.tour-"+i._options.name),t(e).off("resize.tour-"+i._options.name),i._setState("end","yes"),i._inited=!1,i._force=!1,i._clearTimer(),null!=i._options.onEnd?i._options.onEnd(i):void 0}}(this),r=this.hideStep(this._current),this._callOnPromiseDone(r,i)},i.prototype.ended=function(){return!this._force&&!!this._getState("end")},i.prototype.restart=function(){return this._removeState("current_step"),this._removeState("end"),this._removeState("redirect_to"),this.start()},i.prototype.pause=function(){var t;return t=this.getStep(this._current), -t&&t.duration?(this._paused=!0,this._duration-=(new Date).getTime()-this._start,e.clearTimeout(this._timer),this._debug("Paused/Stopped step "+(this._current+1)+" timer ("+this._duration+" remaining)."),null!=t.onPause?t.onPause(this,this._duration):void 0):this},i.prototype.resume=function(){var t;return t=this.getStep(this._current),t&&t.duration?(this._paused=!1,this._start=(new Date).getTime(),this._duration=this._duration||t.duration,this._timer=e.setTimeout(function(t){return function(){return t._isLast()?t.next():t.end()}}(this),this._duration),this._debug("Started step "+(this._current+1)+" timer with duration "+this._duration),null!=t.onResume&&this._duration!==t.duration?t.onResume(this,this._duration):void 0):this},i.prototype.hideStep=function(e){var i,n,r;return(r=this.getStep(e))?(this._clearTimer(),n=this._makePromise(null!=r.onHide?r.onHide(this,e):void 0),i=function(i){return function(n){var s;return s=t(r.element),s.data("bs.popover")||s.data("popover")||(s=t("body")),s.popover("destroy").removeClass("tour-"+i._options.name+"-element tour-"+i._options.name+"-"+e+"-element"),s.removeData("bs.popover"),r.reflex&&t(r.reflexElement).removeClass("tour-step-element-reflex").off(""+i._reflexEvent(r.reflex)+".tour-"+i._options.name),r.backdrop&&i._hideBackdrop(),null!=r.onHidden?r.onHidden(i):void 0}}(this),this._callOnPromiseDone(n,i),n):void 0},i.prototype.showStep=function(t){var i,r,s,o;return this.ended()?(this._debug("Tour ended, showStep prevented."),this):(o=this.getStep(t))?(s=t").parent().html()},i.prototype._reflexEvent=function(t){return"[object Boolean]"==={}.toString.call(t)?"click":t},i.prototype._reposition=function(e,i){var r,s,o,a,l,u,h;if(a=e[0].offsetWidth,s=e[0].offsetHeight,h=e.offset(),l=h.left,u=h.top,r=t(n).outerHeight()-h.top-e.outerHeight(),0>r&&(h.top=h.top+r),o=t("html").outerWidth()-h.left-e.outerWidth(),0>o&&(h.left=h.left+o),h.top<0&&(h.top=0),h.left<0&&(h.left=0),e.offset(h),"bottom"===i.placement||"top"===i.placement){if(l!==h.left)return this._replaceArrow(e,2*(h.left-l),a,"left")}else if(u!==h.top)return this._replaceArrow(e,2*(h.top-u),s,"top")},i.prototype._center=function(i){return i.css("top",t(e).outerHeight()/2-i.outerHeight()/2)},i.prototype._replaceArrow=function(t,e,i,n){return t.find(".arrow").css(n,e?50*(1-e/i)+"%":"")},i.prototype._scrollIntoView=function(i,n){var r,s,o,a,l,u;return r=t(i),r.length?(s=t(e),a=r.offset().top,u=s.height(),l=Math.max(0,a-u/2),this._debug("Scroll into view. ScrollTop: "+l+". Element offset: "+a+". Window height: "+u+"."),o=0,t("body, html").stop(!0,!0).animate({scrollTop:Math.ceil(l)},function(t){return function(){return 2===++o?(n(),t._debug("Scroll into view.\nAnimation end element offset: "+r.offset().top+".\nWindow height: "+s.height()+".")):void 0}}(this))):n()},i.prototype._onResize=function(i,n){return t(e).on("resize.tour-"+this._options.name,function(){return clearTimeout(n),n=setTimeout(i,100)})},i.prototype._initMouseNavigation=function(){var e;return e=this,t(n).off("click.tour-"+this._options.name,".popover.tour-"+this._options.name+" *[data-role='prev']").off("click.tour-"+this._options.name,".popover.tour-"+this._options.name+" *[data-role='next']").off("click.tour-"+this._options.name,".popover.tour-"+this._options.name+" *[data-role='end']").off("click.tour-"+this._options.name,".popover.tour-"+this._options.name+" *[data-role='pause-resume']").on("click.tour-"+this._options.name,".popover.tour-"+this._options.name+" *[data-role='next']",function(t){return function(e){return e.preventDefault(),t.next()}}(this)).on("click.tour-"+this._options.name,".popover.tour-"+this._options.name+" *[data-role='prev']",function(t){return function(e){return e.preventDefault(),t.prev()}}(this)).on("click.tour-"+this._options.name,".popover.tour-"+this._options.name+" *[data-role='end']",function(t){return function(e){return e.preventDefault(),t.end()}}(this)).on("click.tour-"+this._options.name,".popover.tour-"+this._options.name+" *[data-role='pause-resume']",function(i){var n;return i.preventDefault(),n=t(this),n.text(e._paused?n.data("pause-text"):n.data("resume-text")),e._paused?e.resume():e.pause()})},i.prototype._initKeyboardNavigation=function(){return this._options.keyboard?t(n).on("keyup.tour-"+this._options.name,function(t){return function(e){if(e.which)switch(e.which){case 39:return e.preventDefault(),t._isLast()?t.next():t.end();case 37:if(e.preventDefault(),t._current>0)return t.prev();break;case 27:return e.preventDefault(),t.end()}}}(this)):void 0},i.prototype._makePromise=function(e){return e&&t.isFunction(e.then)?e:null},i.prototype._callOnPromiseDone=function(t,e,i){return t?t.then(function(t){return function(n){return e.call(t,i)}}(this)):e.call(this,i)},i.prototype._showBackdrop=function(e){return this.backdrop.backgroundShown?void 0:(this.backdrop=t("
                                            ",{"class":"tour-backdrop"}),this.backdrop.backgroundShown=!0,t(e.backdropContainer).append(this.backdrop))},i.prototype._hideBackdrop=function(){return this._hideOverlayElement(),this._hideBackground()},i.prototype._hideBackground=function(){return this.backdrop?(this.backdrop.remove(),this.backdrop.overlay=null,this.backdrop.backgroundShown=!1):void 0},i.prototype._showOverlayElement=function(e,i){var n,r;return n=t(e.element),!n||0===n.length||this.backdrop.overlayElementShown&&!i?void 0:(this.backdrop.overlayElementShown||(this.backdrop.$element=n.addClass("tour-step-backdrop"),this.backdrop.$background=t("
                                            ",{"class":"tour-step-background"}),this.backdrop.$background.appendTo(e.backdropContainer),this.backdrop.overlayElementShown=!0),r={width:n.innerWidth(),height:n.innerHeight(),offset:n.offset()},e.backdropPadding&&(r=this._applyBackdropPadding(e.backdropPadding,r)),this.backdrop.$background.width(r.width).height(r.height).offset(r.offset))},i.prototype._hideOverlayElement=function(){return this.backdrop.overlayElementShown?(this.backdrop.$element.removeClass("tour-step-backdrop"),this.backdrop.$background.remove(),this.backdrop.$element=null,this.backdrop.$background=null,this.backdrop.overlayElementShown=!1):void 0},i.prototype._applyBackdropPadding=function(t,e){return"object"==typeof t?(null==t.top&&(t.top=0),null==t.right&&(t.right=0),null==t.bottom&&(t.bottom=0),null==t.left&&(t.left=0),e.offset.top=e.offset.top-t.top,e.offset.left=e.offset.left-t.left,e.width=e.width+t.left+t.right,e.height=e.height+t.top+t.bottom):(e.offset.top=e.offset.top-t,e.offset.left=e.offset.left-t,e.width=e.width+2*t,e.height=e.height+2*t),e},i.prototype._clearTimer=function(){return e.clearTimeout(this._timer),this._timer=null,this._duration=null},i.prototype._getProtocol=function(t){return t=t.split("://"),t.length>1?t[0]:"http"},i.prototype._getHost=function(t){return t=t.split("//"),t=t.length>1?t[1]:t[0],t.split("/")[0]},i.prototype._getPath=function(t){return t.replace(/\/?$/,"").split("?")[0].split("#")[0]},i.prototype._getQuery=function(t){return this._getParams(t,"?")},i.prototype._getHash=function(t){return this._getParams(t,"#")},i.prototype._getParams=function(t,e){var i,n,r,s,o;if(n=t.split(e),1===n.length)return{};for(n=n[1].split("&"),r={},s=0,o=n.length;o>s;s++)i=n[s],i=i.split("="),r[i[0]]=i[1]||"";return r},i.prototype._equal=function(t,e){var i,n;if("[object Object]"==={}.toString.call(t)&&"[object Object]"==={}.toString.call(e)){for(i in t)if(n=t[i],e[i]!==n)return!1;for(i in e)if(n=e[i],t[i]!==n)return!1;return!0}return t===e},i}(),e.Tour=i}(t,window)}).call(e,i(1))},function(t,e,i){(function(t){/*! jQuery UI - v1.9.1 - 2012-10-29 +function(t){function e(t,e,i,n){var r,s,o,a,l,h,u,p,f=e&&e.ownerDocument,g=e?e.nodeType:9;if(i=i||[],"string"!=typeof t||!t||1!==g&&9!==g&&11!==g)return i;if(!n&&((e?e.ownerDocument||e:F)!==D&&M(e),e=e||D,R)){if(11!==g&&(h=vt.exec(t)))if(r=h[1]){if(9===g){if(!(o=e.getElementById(r)))return i;if(o.id===r)return i.push(o),i}else if(f&&(o=f.getElementById(r))&&I(e,o)&&o.id===r)return i.push(o),i}else{if(h[2])return K.apply(i,e.getElementsByTagName(t)),i;if((r=h[3])&&_.getElementsByClassName&&e.getElementsByClassName)return K.apply(i,e.getElementsByClassName(r)),i}if(_.qsa&&!U[t+" "]&&(!j||!j.test(t))){if(1!==g)f=e,p=t;else if("object"!==e.nodeName.toLowerCase()){for((a=e.getAttribute("id"))?a=a.replace(bt,"\\$&"):e.setAttribute("id",a=$),u=k(t),s=u.length,l=dt.test(a)?"#"+a:"[id='"+a+"']";s--;)u[s]=l+" "+d(u[s]);p=u.join(","),f=yt.test(t)&&c(e.parentNode)||e}if(p)try{return K.apply(i,f.querySelectorAll(p)),i}catch(m){}finally{a===$&&e.removeAttribute("id")}}}return E(t.replace(at,"$1"),e,i,n)}function i(){function t(i,n){return e.push(i+" ")>x.cacheLength&&delete t[e.shift()],t[i+" "]=n}var e=[];return t}function n(t){return t[$]=!0,t}function r(t){var e=D.createElement("div");try{return!!t(e)}catch(i){return!1}finally{e.parentNode&&e.parentNode.removeChild(e),e=null}}function s(t,e){for(var i=t.split("|"),n=i.length;n--;)x.attrHandle[i[n]]=e}function o(t,e){var i=e&&t,n=i&&1===t.nodeType&&1===e.nodeType&&(~e.sourceIndex||G)-(~t.sourceIndex||G);if(n)return n;if(i)for(;i=i.nextSibling;)if(i===e)return-1;return t?1:-1}function a(t){return function(e){var i=e.nodeName.toLowerCase();return"input"===i&&e.type===t}}function l(t){return function(e){var i=e.nodeName.toLowerCase();return("input"===i||"button"===i)&&e.type===t}}function h(t){return n(function(e){return e=+e,n(function(i,n){for(var r,s=t([],i.length,e),o=s.length;o--;)i[r=s[o]]&&(i[r]=!(n[r]=i[r]))})})}function c(t){return t&&"undefined"!=typeof t.getElementsByTagName&&t}function u(){}function d(t){for(var e=0,i=t.length,n="";e1?function(e,i,n){for(var r=t.length;r--;)if(!t[r](e,i,n))return!1;return!0}:t[0]}function g(t,i,n){for(var r=0,s=i.length;r-1&&(n[h]=!(o[h]=u))}}else b=m(b===o?b.splice(f,b.length):b),s?s(null,o,b,l):K.apply(o,b)})}function y(t){for(var e,i,n,r=t.length,s=x.relative[t[0].type],o=s||x.relative[" "],a=s?1:0,l=p(function(t){return t===e},o,!0),h=p(function(t){return tt(e,t)>-1},o,!0),c=[function(t,i,n){var r=!s&&(n||i!==A)||((e=i).nodeType?l(t,i,n):h(t,i,n));return e=null,r}];a1&&f(c),a>1&&d(t.slice(0,a-1).concat({value:" "===t[a-2].type?"*":""})).replace(at,"$1"),i,a0,s=t.length>0,o=function(n,o,a,l,h){var c,u,d,p=0,f="0",g=n&&[],v=[],y=A,b=n||s&&x.find.TAG("*",h),w=q+=null==y?1:Math.random()||.1,_=b.length;for(h&&(A=o===D||o||h);f!==_&&null!=(c=b[f]);f++){if(s&&c){for(u=0,o||c.ownerDocument===D||(M(c),a=!R);d=t[u++];)if(d(c,o||D,a)){l.push(c);break}h&&(q=w)}r&&((c=!d&&c)&&p--,n&&g.push(c))}if(p+=f,r&&f!==p){for(u=0;d=i[u++];)d(g,v,o,a);if(n){if(p>0)for(;f--;)g[f]||v[f]||(v[f]=J.call(l));v=m(v)}K.apply(l,v),h&&!n&&v.length>0&&p+i.length>1&&e.uniqueSort(l)}return h&&(q=w,A=y),g};return r?n(o):o}var w,_,x,C,S,k,T,E,A,N,O,M,D,P,R,j,L,H,I,$="sizzle"+1*new Date,F=t.document,q=0,W=0,z=i(),B=i(),U=i(),V=function(t,e){return t===e&&(O=!0),0},G=1<<31,X={}.hasOwnProperty,Y=[],J=Y.pop,Q=Y.push,K=Y.push,Z=Y.slice,tt=function(t,e){for(var i=0,n=t.length;i+~]|"+it+")"+it+"*"),ct=new RegExp("="+it+"*([^\\]'\"]*?)"+it+"*\\]","g"),ut=new RegExp(st),dt=new RegExp("^"+nt+"$"),pt={ID:new RegExp("^#("+nt+")"),CLASS:new RegExp("^\\.("+nt+")"),TAG:new RegExp("^("+nt+"|[*])"),ATTR:new RegExp("^"+rt),PSEUDO:new RegExp("^"+st),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+it+"*(even|odd|(([+-]|)(\\d*)n|)"+it+"*(?:([+-]|)"+it+"*(\\d+)|))"+it+"*\\)|)","i"),bool:new RegExp("^(?:"+et+")$","i"),needsContext:new RegExp("^"+it+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+it+"*((?:-\\d)?\\d*)"+it+"*\\)|)(?=[^-]|$)","i")},ft=/^(?:input|select|textarea|button)$/i,gt=/^h\d$/i,mt=/^[^{]+\{\s*\[native \w/,vt=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,yt=/[+~]/,bt=/'|\\/g,wt=new RegExp("\\\\([\\da-f]{1,6}"+it+"?|("+it+")|.)","ig"),_t=function(t,e,i){var n="0x"+e-65536;return n!==n||i?e:n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320)},xt=function(){M()};try{K.apply(Y=Z.call(F.childNodes),F.childNodes),Y[F.childNodes.length].nodeType}catch(Ct){K={apply:Y.length?function(t,e){Q.apply(t,Z.call(e))}:function(t,e){for(var i=t.length,n=0;t[i++]=e[n++];);t.length=i-1}}}_=e.support={},S=e.isXML=function(t){var e=t&&(t.ownerDocument||t).documentElement;return!!e&&"HTML"!==e.nodeName},M=e.setDocument=function(t){var e,i,n=t?t.ownerDocument||t:F;return n!==D&&9===n.nodeType&&n.documentElement?(D=n,P=D.documentElement,R=!S(D),(i=D.defaultView)&&i.top!==i&&(i.addEventListener?i.addEventListener("unload",xt,!1):i.attachEvent&&i.attachEvent("onunload",xt)),_.attributes=r(function(t){return t.className="i",!t.getAttribute("className")}),_.getElementsByTagName=r(function(t){return t.appendChild(D.createComment("")),!t.getElementsByTagName("*").length}),_.getElementsByClassName=mt.test(D.getElementsByClassName),_.getById=r(function(t){return P.appendChild(t).id=$,!D.getElementsByName||!D.getElementsByName($).length}),_.getById?(x.find.ID=function(t,e){if("undefined"!=typeof e.getElementById&&R){var i=e.getElementById(t);return i?[i]:[]}},x.filter.ID=function(t){var e=t.replace(wt,_t);return function(t){return t.getAttribute("id")===e}}):(delete x.find.ID,x.filter.ID=function(t){var e=t.replace(wt,_t);return function(t){var i="undefined"!=typeof t.getAttributeNode&&t.getAttributeNode("id");return i&&i.value===e}}),x.find.TAG=_.getElementsByTagName?function(t,e){return"undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t):_.qsa?e.querySelectorAll(t):void 0}:function(t,e){var i,n=[],r=0,s=e.getElementsByTagName(t);if("*"===t){for(;i=s[r++];)1===i.nodeType&&n.push(i);return n}return s},x.find.CLASS=_.getElementsByClassName&&function(t,e){if("undefined"!=typeof e.getElementsByClassName&&R)return e.getElementsByClassName(t)},L=[],j=[],(_.qsa=mt.test(D.querySelectorAll))&&(r(function(t){P.appendChild(t).innerHTML="",t.querySelectorAll("[msallowcapture^='']").length&&j.push("[*^$]="+it+"*(?:''|\"\")"),t.querySelectorAll("[selected]").length||j.push("\\["+it+"*(?:value|"+et+")"),t.querySelectorAll("[id~="+$+"-]").length||j.push("~="),t.querySelectorAll(":checked").length||j.push(":checked"),t.querySelectorAll("a#"+$+"+*").length||j.push(".#.+[+~]")}),r(function(t){var e=D.createElement("input");e.setAttribute("type","hidden"),t.appendChild(e).setAttribute("name","D"),t.querySelectorAll("[name=d]").length&&j.push("name"+it+"*[*^$|!~]?="),t.querySelectorAll(":enabled").length||j.push(":enabled",":disabled"),t.querySelectorAll("*,:x"),j.push(",.*:")})),(_.matchesSelector=mt.test(H=P.matches||P.webkitMatchesSelector||P.mozMatchesSelector||P.oMatchesSelector||P.msMatchesSelector))&&r(function(t){_.disconnectedMatch=H.call(t,"div"),H.call(t,"[s!='']:x"),L.push("!=",st)}),j=j.length&&new RegExp(j.join("|")),L=L.length&&new RegExp(L.join("|")),e=mt.test(P.compareDocumentPosition),I=e||mt.test(P.contains)?function(t,e){var i=9===t.nodeType?t.documentElement:t,n=e&&e.parentNode;return t===n||!(!n||1!==n.nodeType||!(i.contains?i.contains(n):t.compareDocumentPosition&&16&t.compareDocumentPosition(n)))}:function(t,e){if(e)for(;e=e.parentNode;)if(e===t)return!0;return!1},V=e?function(t,e){if(t===e)return O=!0,0;var i=!t.compareDocumentPosition-!e.compareDocumentPosition;return i?i:(i=(t.ownerDocument||t)===(e.ownerDocument||e)?t.compareDocumentPosition(e):1,1&i||!_.sortDetached&&e.compareDocumentPosition(t)===i?t===D||t.ownerDocument===F&&I(F,t)?-1:e===D||e.ownerDocument===F&&I(F,e)?1:N?tt(N,t)-tt(N,e):0:4&i?-1:1)}:function(t,e){if(t===e)return O=!0,0;var i,n=0,r=t.parentNode,s=e.parentNode,a=[t],l=[e];if(!r||!s)return t===D?-1:e===D?1:r?-1:s?1:N?tt(N,t)-tt(N,e):0;if(r===s)return o(t,e);for(i=t;i=i.parentNode;)a.unshift(i);for(i=e;i=i.parentNode;)l.unshift(i);for(;a[n]===l[n];)n++;return n?o(a[n],l[n]):a[n]===F?-1:l[n]===F?1:0},D):D},e.matches=function(t,i){return e(t,null,null,i)},e.matchesSelector=function(t,i){if((t.ownerDocument||t)!==D&&M(t),i=i.replace(ct,"='$1']"),_.matchesSelector&&R&&!U[i+" "]&&(!L||!L.test(i))&&(!j||!j.test(i)))try{var n=H.call(t,i);if(n||_.disconnectedMatch||t.document&&11!==t.document.nodeType)return n}catch(r){}return e(i,D,null,[t]).length>0},e.contains=function(t,e){return(t.ownerDocument||t)!==D&&M(t),I(t,e)},e.attr=function(t,e){(t.ownerDocument||t)!==D&&M(t);var i=x.attrHandle[e.toLowerCase()],n=i&&X.call(x.attrHandle,e.toLowerCase())?i(t,e,!R):void 0;return void 0!==n?n:_.attributes||!R?t.getAttribute(e):(n=t.getAttributeNode(e))&&n.specified?n.value:null},e.error=function(t){throw new Error("Syntax error, unrecognized expression: "+t)},e.uniqueSort=function(t){var e,i=[],n=0,r=0;if(O=!_.detectDuplicates,N=!_.sortStable&&t.slice(0),t.sort(V),O){for(;e=t[r++];)e===t[r]&&(n=i.push(r));for(;n--;)t.splice(i[n],1)}return N=null,t},C=e.getText=function(t){var e,i="",n=0,r=t.nodeType;if(r){if(1===r||9===r||11===r){if("string"==typeof t.textContent)return t.textContent;for(t=t.firstChild;t;t=t.nextSibling)i+=C(t)}else if(3===r||4===r)return t.nodeValue}else for(;e=t[n++];)i+=C(e);return i},x=e.selectors={cacheLength:50,createPseudo:n,match:pt,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(t){return t[1]=t[1].replace(wt,_t),t[3]=(t[3]||t[4]||t[5]||"").replace(wt,_t),"~="===t[2]&&(t[3]=" "+t[3]+" "),t.slice(0,4)},CHILD:function(t){return t[1]=t[1].toLowerCase(),"nth"===t[1].slice(0,3)?(t[3]||e.error(t[0]),t[4]=+(t[4]?t[5]+(t[6]||1):2*("even"===t[3]||"odd"===t[3])),t[5]=+(t[7]+t[8]||"odd"===t[3])):t[3]&&e.error(t[0]),t},PSEUDO:function(t){var e,i=!t[6]&&t[2];return pt.CHILD.test(t[0])?null:(t[3]?t[2]=t[4]||t[5]||"":i&&ut.test(i)&&(e=k(i,!0))&&(e=i.indexOf(")",i.length-e)-i.length)&&(t[0]=t[0].slice(0,e),t[2]=i.slice(0,e)),t.slice(0,3))}},filter:{TAG:function(t){var e=t.replace(wt,_t).toLowerCase();return"*"===t?function(){return!0}:function(t){return t.nodeName&&t.nodeName.toLowerCase()===e}},CLASS:function(t){var e=z[t+" "];return e||(e=new RegExp("(^|"+it+")"+t+"("+it+"|$)"))&&z(t,function(t){return e.test("string"==typeof t.className&&t.className||"undefined"!=typeof t.getAttribute&&t.getAttribute("class")||"")})},ATTR:function(t,i,n){return function(r){var s=e.attr(r,t);return null==s?"!="===i:!i||(s+="","="===i?s===n:"!="===i?s!==n:"^="===i?n&&0===s.indexOf(n):"*="===i?n&&s.indexOf(n)>-1:"$="===i?n&&s.slice(-n.length)===n:"~="===i?(" "+s.replace(ot," ")+" ").indexOf(n)>-1:"|="===i&&(s===n||s.slice(0,n.length+1)===n+"-"))}},CHILD:function(t,e,i,n,r){var s="nth"!==t.slice(0,3),o="last"!==t.slice(-4),a="of-type"===e;return 1===n&&0===r?function(t){return!!t.parentNode}:function(e,i,l){var h,c,u,d,p,f,g=s!==o?"nextSibling":"previousSibling",m=e.parentNode,v=a&&e.nodeName.toLowerCase(),y=!l&&!a,b=!1;if(m){if(s){for(;g;){for(d=e;d=d[g];)if(a?d.nodeName.toLowerCase()===v:1===d.nodeType)return!1;f=g="only"===t&&!f&&"nextSibling"}return!0}if(f=[o?m.firstChild:m.lastChild],o&&y){for(d=m,u=d[$]||(d[$]={}),c=u[d.uniqueID]||(u[d.uniqueID]={}),h=c[t]||[],p=h[0]===q&&h[1],b=p&&h[2],d=p&&m.childNodes[p];d=++p&&d&&d[g]||(b=p=0)||f.pop();)if(1===d.nodeType&&++b&&d===e){c[t]=[q,p,b];break}}else if(y&&(d=e,u=d[$]||(d[$]={}),c=u[d.uniqueID]||(u[d.uniqueID]={}),h=c[t]||[],p=h[0]===q&&h[1],b=p),b===!1)for(;(d=++p&&d&&d[g]||(b=p=0)||f.pop())&&((a?d.nodeName.toLowerCase()!==v:1!==d.nodeType)||!++b||(y&&(u=d[$]||(d[$]={}),c=u[d.uniqueID]||(u[d.uniqueID]={}),c[t]=[q,b]),d!==e)););return b-=r,b===n||b%n===0&&b/n>=0}}},PSEUDO:function(t,i){var r,s=x.pseudos[t]||x.setFilters[t.toLowerCase()]||e.error("unsupported pseudo: "+t);return s[$]?s(i):s.length>1?(r=[t,t,"",i],x.setFilters.hasOwnProperty(t.toLowerCase())?n(function(t,e){for(var n,r=s(t,i),o=r.length;o--;)n=tt(t,r[o]),t[n]=!(e[n]=r[o])}):function(t){return s(t,0,r)}):s}},pseudos:{not:n(function(t){var e=[],i=[],r=T(t.replace(at,"$1"));return r[$]?n(function(t,e,i,n){for(var s,o=r(t,null,n,[]),a=t.length;a--;)(s=o[a])&&(t[a]=!(e[a]=s))}):function(t,n,s){return e[0]=t,r(e,null,s,i),e[0]=null,!i.pop()}}),has:n(function(t){return function(i){return e(t,i).length>0}}),contains:n(function(t){return t=t.replace(wt,_t),function(e){return(e.textContent||e.innerText||C(e)).indexOf(t)>-1}}),lang:n(function(t){return dt.test(t||"")||e.error("unsupported lang: "+t),t=t.replace(wt,_t).toLowerCase(),function(e){var i;do if(i=R?e.lang:e.getAttribute("xml:lang")||e.getAttribute("lang"))return i=i.toLowerCase(),i===t||0===i.indexOf(t+"-");while((e=e.parentNode)&&1===e.nodeType);return!1}}),target:function(e){var i=t.location&&t.location.hash;return i&&i.slice(1)===e.id},root:function(t){return t===P},focus:function(t){return t===D.activeElement&&(!D.hasFocus||D.hasFocus())&&!!(t.type||t.href||~t.tabIndex)},enabled:function(t){return t.disabled===!1},disabled:function(t){return t.disabled===!0},checked:function(t){var e=t.nodeName.toLowerCase();return"input"===e&&!!t.checked||"option"===e&&!!t.selected},selected:function(t){return t.parentNode&&t.parentNode.selectedIndex,t.selected===!0},empty:function(t){for(t=t.firstChild;t;t=t.nextSibling)if(t.nodeType<6)return!1;return!0},parent:function(t){return!x.pseudos.empty(t)},header:function(t){return gt.test(t.nodeName)},input:function(t){return ft.test(t.nodeName)},button:function(t){var e=t.nodeName.toLowerCase();return"input"===e&&"button"===t.type||"button"===e},text:function(t){var e;return"input"===t.nodeName.toLowerCase()&&"text"===t.type&&(null==(e=t.getAttribute("type"))||"text"===e.toLowerCase())},first:h(function(){return[0]}),last:h(function(t,e){return[e-1]}),eq:h(function(t,e,i){return[i<0?i+e:i]}),even:h(function(t,e){for(var i=0;i=0;)t.push(n);return t}),gt:h(function(t,e,i){for(var n=i<0?i+e:i;++n2&&"ID"===(o=s[0]).type&&_.getById&&9===e.nodeType&&R&&x.relative[s[1].type]){if(e=(x.find.ID(o.matches[0].replace(wt,_t),e)||[])[0],!e)return i;h&&(e=e.parentNode),t=t.slice(s.shift().value.length)}for(r=pt.needsContext.test(t)?0:s.length;r--&&(o=s[r],!x.relative[a=o.type]);)if((l=x.find[a])&&(n=l(o.matches[0].replace(wt,_t),yt.test(s[0].type)&&c(e.parentNode)||e))){if(s.splice(r,1),t=n.length&&d(s),!t)return K.apply(i,n),i;break}}return(h||T(t,u))(n,e,!R,i,!e||yt.test(t)&&c(e.parentNode)||e),i},_.sortStable=$.split("").sort(V).join("")===$,_.detectDuplicates=!!O,M(),_.sortDetached=r(function(t){return 1&t.compareDocumentPosition(D.createElement("div"))}),r(function(t){return t.innerHTML="","#"===t.firstChild.getAttribute("href")})||s("type|href|height|width",function(t,e,i){if(!i)return t.getAttribute(e,"type"===e.toLowerCase()?1:2)}),_.attributes&&r(function(t){return t.innerHTML="",t.firstChild.setAttribute("value",""),""===t.firstChild.getAttribute("value")})||s("value",function(t,e,i){if(!i&&"input"===t.nodeName.toLowerCase())return t.defaultValue}),r(function(t){return null==t.getAttribute("disabled")})||s(et,function(t,e,i){var n;if(!i)return t[e]===!0?e.toLowerCase():(n=t.getAttributeNode(e))&&n.specified?n.value:null}),e}(i);vt.find=xt,vt.expr=xt.selectors,vt.expr[":"]=vt.expr.pseudos,vt.uniqueSort=vt.unique=xt.uniqueSort,vt.text=xt.getText,vt.isXMLDoc=xt.isXML,vt.contains=xt.contains;var Ct=function(t,e,i){for(var n=[],r=void 0!==i;(t=t[e])&&9!==t.nodeType;)if(1===t.nodeType){if(r&&vt(t).is(i))break;n.push(t)}return n},St=function(t,e){for(var i=[];t;t=t.nextSibling)1===t.nodeType&&t!==e&&i.push(t);return i},kt=vt.expr.match.needsContext,Tt=/^<([\w-]+)\s*\/?>(?:<\/\1>|)$/,Et=/^.[^:#\[\.,]*$/;vt.filter=function(t,e,i){var n=e[0];return i&&(t=":not("+t+")"),1===e.length&&1===n.nodeType?vt.find.matchesSelector(n,t)?[n]:[]:vt.find.matches(t,vt.grep(e,function(t){return 1===t.nodeType}))},vt.fn.extend({find:function(t){var e,i=[],n=this,r=n.length;if("string"!=typeof t)return this.pushStack(vt(t).filter(function(){for(e=0;e1?vt.unique(i):i),i.selector=this.selector?this.selector+" "+t:t,i},filter:function(t){return this.pushStack(a(this,t||[],!1))},not:function(t){return this.pushStack(a(this,t||[],!0))},is:function(t){return!!a(this,"string"==typeof t&&kt.test(t)?vt(t):t||[],!1).length}});var At,Nt=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,Ot=vt.fn.init=function(t,e,i){var n,r;if(!t)return this;if(i=i||At,"string"==typeof t){if(n="<"===t.charAt(0)&&">"===t.charAt(t.length-1)&&t.length>=3?[null,t,null]:Nt.exec(t),!n||!n[1]&&e)return!e||e.jquery?(e||i).find(t):this.constructor(e).find(t);if(n[1]){if(e=e instanceof vt?e[0]:e,vt.merge(this,vt.parseHTML(n[1],e&&e.nodeType?e.ownerDocument||e:at,!0)),Tt.test(n[1])&&vt.isPlainObject(e))for(n in e)vt.isFunction(this[n])?this[n](e[n]):this.attr(n,e[n]);return this}if(r=at.getElementById(n[2]),r&&r.parentNode){if(r.id!==n[2])return At.find(t);this.length=1,this[0]=r}return this.context=at,this.selector=t,this}return t.nodeType?(this.context=this[0]=t,this.length=1,this):vt.isFunction(t)?"undefined"!=typeof i.ready?i.ready(t):t(vt):(void 0!==t.selector&&(this.selector=t.selector,this.context=t.context),vt.makeArray(t,this))};Ot.prototype=vt.fn,At=vt(at);var Mt=/^(?:parents|prev(?:Until|All))/,Dt={children:!0,contents:!0,next:!0,prev:!0};vt.fn.extend({has:function(t){var e,i=vt(t,this),n=i.length;return this.filter(function(){for(e=0;e-1:1===i.nodeType&&vt.find.matchesSelector(i,t))){s.push(i);break}return this.pushStack(s.length>1?vt.uniqueSort(s):s)},index:function(t){return t?"string"==typeof t?vt.inArray(this[0],vt(t)):vt.inArray(t.jquery?t[0]:t,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(t,e){return this.pushStack(vt.uniqueSort(vt.merge(this.get(),vt(t,e))))},addBack:function(t){return this.add(null==t?this.prevObject:this.prevObject.filter(t))}}),vt.each({parent:function(t){var e=t.parentNode;return e&&11!==e.nodeType?e:null},parents:function(t){return Ct(t,"parentNode")},parentsUntil:function(t,e,i){return Ct(t,"parentNode",i)},next:function(t){return l(t,"nextSibling")},prev:function(t){return l(t,"previousSibling")},nextAll:function(t){return Ct(t,"nextSibling")},prevAll:function(t){return Ct(t,"previousSibling")},nextUntil:function(t,e,i){return Ct(t,"nextSibling",i)},prevUntil:function(t,e,i){return Ct(t,"previousSibling",i)},siblings:function(t){return St((t.parentNode||{}).firstChild,t)},children:function(t){return St(t.firstChild)},contents:function(t){return vt.nodeName(t,"iframe")?t.contentDocument||t.contentWindow.document:vt.merge([],t.childNodes)}},function(t,e){vt.fn[t]=function(i,n){var r=vt.map(this,e,i);return"Until"!==t.slice(-5)&&(n=i),n&&"string"==typeof n&&(r=vt.filter(n,r)),this.length>1&&(Dt[t]||(r=vt.uniqueSort(r)),Mt.test(t)&&(r=r.reverse())),this.pushStack(r)}});var Pt=/\S+/g;vt.Callbacks=function(t){t="string"==typeof t?h(t):vt.extend({},t);var e,i,n,r,s=[],o=[],a=-1,l=function(){for(r=t.once,n=e=!0;o.length;a=-1)for(i=o.shift();++a-1;)s.splice(i,1),i<=a&&a--}),this},has:function(t){return t?vt.inArray(t,s)>-1:s.length>0},empty:function(){return s&&(s=[]),this},disable:function(){return r=o=[],s=i="",this},disabled:function(){return!s},lock:function(){return r=!0,i||c.disable(),this},locked:function(){return!!r},fireWith:function(t,i){return r||(i=i||[],i=[t,i.slice?i.slice():i],o.push(i),e||l()),this},fire:function(){return c.fireWith(this,arguments),this},fired:function(){return!!n}};return c},vt.extend({Deferred:function(t){var e=[["resolve","done",vt.Callbacks("once memory"),"resolved"],["reject","fail",vt.Callbacks("once memory"),"rejected"],["notify","progress",vt.Callbacks("memory")]],i="pending",n={state:function(){return i},always:function(){return r.done(arguments).fail(arguments),this},then:function(){var t=arguments;return vt.Deferred(function(i){vt.each(e,function(e,s){var o=vt.isFunction(t[e])&&t[e];r[s[1]](function(){var t=o&&o.apply(this,arguments);t&&vt.isFunction(t.promise)?t.promise().progress(i.notify).done(i.resolve).fail(i.reject):i[s[0]+"With"](this===n?i.promise():this,o?[t]:arguments)})}),t=null}).promise()},promise:function(t){return null!=t?vt.extend(t,n):n}},r={};return n.pipe=n.then,vt.each(e,function(t,s){var o=s[2],a=s[3];n[s[1]]=o.add,a&&o.add(function(){i=a},e[1^t][2].disable,e[2][2].lock),r[s[0]]=function(){return r[s[0]+"With"](this===r?n:this,arguments),this},r[s[0]+"With"]=o.fireWith}),n.promise(r),t&&t.call(r,r),r},when:function(t){var e,i,n,r=0,s=lt.call(arguments),o=s.length,a=1!==o||t&&vt.isFunction(t.promise)?o:0,l=1===a?t:vt.Deferred(),h=function(t,i,n){return function(r){i[t]=this,n[t]=arguments.length>1?lt.call(arguments):r,n===e?l.notifyWith(i,n):--a||l.resolveWith(i,n)}};if(o>1)for(e=new Array(o),i=new Array(o),n=new Array(o);r0||(Rt.resolveWith(at,[vt]),vt.fn.triggerHandler&&(vt(at).triggerHandler("ready"),vt(at).off("ready"))))}}),vt.ready.promise=function(t){if(!Rt)if(Rt=vt.Deferred(),"complete"===at.readyState||"loading"!==at.readyState&&!at.documentElement.doScroll)i.setTimeout(vt.ready);else if(at.addEventListener)at.addEventListener("DOMContentLoaded",u),i.addEventListener("load",u);else{at.attachEvent("onreadystatechange",u),i.attachEvent("onload",u);var e=!1;try{e=null==i.frameElement&&at.documentElement}catch(n){}e&&e.doScroll&&!function r(){if(!vt.isReady){try{e.doScroll("left")}catch(t){return i.setTimeout(r,50)}c(),vt.ready()}}()}return Rt.promise(t)},vt.ready.promise();var jt;for(jt in vt(gt))break;gt.ownFirst="0"===jt,gt.inlineBlockNeedsLayout=!1,vt(function(){var t,e,i,n;i=at.getElementsByTagName("body")[0],i&&i.style&&(e=at.createElement("div"),n=at.createElement("div"),n.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",i.appendChild(n).appendChild(e),"undefined"!=typeof e.style.zoom&&(e.style.cssText="display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1",gt.inlineBlockNeedsLayout=t=3===e.offsetWidth,t&&(i.style.zoom=1)),i.removeChild(n))}),function(){var t=at.createElement("div");gt.deleteExpando=!0;try{delete t.test}catch(e){gt.deleteExpando=!1}t=null}();var Lt=function(t){var e=vt.noData[(t.nodeName+" ").toLowerCase()],i=+t.nodeType||1;return(1===i||9===i)&&(!e||e!==!0&&t.getAttribute("classid")===e)},Ht=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,It=/([A-Z])/g;vt.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(t){return t=t.nodeType?vt.cache[t[vt.expando]]:t[vt.expando],!!t&&!p(t)},data:function(t,e,i){return f(t,e,i)},removeData:function(t,e){return g(t,e)},_data:function(t,e,i){return f(t,e,i,!0)},_removeData:function(t,e){return g(t,e,!0)}}),vt.fn.extend({data:function(t,e){var i,n,r,s=this[0],o=s&&s.attributes;if(void 0===t){if(this.length&&(r=vt.data(s),1===s.nodeType&&!vt._data(s,"parsedAttrs"))){for(i=o.length;i--;)o[i]&&(n=o[i].name,0===n.indexOf("data-")&&(n=vt.camelCase(n.slice(5)),d(s,n,r[n])));vt._data(s,"parsedAttrs",!0)}return r}return"object"==typeof t?this.each(function(){vt.data(this,t)}):arguments.length>1?this.each(function(){vt.data(this,t,e)}):s?d(s,t,vt.data(s,t)):void 0},removeData:function(t){return this.each(function(){vt.removeData(this,t)})}}),vt.extend({queue:function(t,e,i){var n;if(t)return e=(e||"fx")+"queue",n=vt._data(t,e),i&&(!n||vt.isArray(i)?n=vt._data(t,e,vt.makeArray(i)):n.push(i)),n||[]},dequeue:function(t,e){e=e||"fx";var i=vt.queue(t,e),n=i.length,r=i.shift(),s=vt._queueHooks(t,e),o=function(){vt.dequeue(t,e)};"inprogress"===r&&(r=i.shift(),n--),r&&("fx"===e&&i.unshift("inprogress"),delete s.stop,r.call(t,o,s)),!n&&s&&s.empty.fire()},_queueHooks:function(t,e){var i=e+"queueHooks";return vt._data(t,i)||vt._data(t,i,{empty:vt.Callbacks("once memory").add(function(){vt._removeData(t,e+"queue"),vt._removeData(t,i)})})}}),vt.fn.extend({queue:function(t,e){var i=2;return"string"!=typeof t&&(e=t,t="fx",i--),arguments.length
                                            a",gt.leadingWhitespace=3===t.firstChild.nodeType,gt.tbody=!t.getElementsByTagName("tbody").length,gt.htmlSerialize=!!t.getElementsByTagName("link").length,gt.html5Clone="<:nav>"!==at.createElement("nav").cloneNode(!0).outerHTML,i.type="checkbox",i.checked=!0,e.appendChild(i),gt.appendChecked=i.checked,t.innerHTML="",gt.noCloneChecked=!!t.cloneNode(!0).lastChild.defaultValue,e.appendChild(t),i=at.createElement("input"),i.setAttribute("type","radio"),i.setAttribute("checked","checked"),i.setAttribute("name","t"),t.appendChild(i),gt.checkClone=t.cloneNode(!0).cloneNode(!0).lastChild.checked,gt.noCloneEvent=!!t.addEventListener,t[vt.expando]=1,gt.attributes=!t.getAttribute(vt.expando)}();var Yt={option:[1,""],legend:[1,"
                                            ","
                                            "],area:[1,"",""],param:[1,"",""],thead:[1,"","
                                            "],tr:[2,"","
                                            "],col:[2,"","
                                            "],td:[3,"","
                                            "],_default:gt.htmlSerialize?[0,"",""]:[1,"X
                                            ","
                                            "]};Yt.optgroup=Yt.option,Yt.tbody=Yt.tfoot=Yt.colgroup=Yt.caption=Yt.thead,Yt.th=Yt.td;var Jt=/<|&#?\w+;/,Qt=/-1&&(f=p.split("."),p=f.shift(),f.sort()),o=p.indexOf(":")<0&&"on"+p,t=t[vt.expando]?t:new vt.Event(p,"object"==typeof t&&t),t.isTrigger=r?2:3,t.namespace=f.join("."),t.rnamespace=t.namespace?new RegExp("(^|\\.)"+f.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,t.result=void 0,t.target||(t.target=n),e=null==e?[t]:vt.makeArray(e,[t]),h=vt.event.special[p]||{},r||!h.trigger||h.trigger.apply(n,e)!==!1)){if(!r&&!h.noBubble&&!vt.isWindow(n)){for(l=h.delegateType||p,ee.test(l+p)||(a=a.parentNode);a;a=a.parentNode)d.push(a),c=a;c===(n.ownerDocument||at)&&d.push(c.defaultView||c.parentWindow||i)}for(u=0;(a=d[u++])&&!t.isPropagationStopped();)t.type=u>1?l:h.bindType||p,s=(vt._data(a,"events")||{})[t.type]&&vt._data(a,"handle"),s&&s.apply(a,e),s=o&&a[o],s&&s.apply&&Lt(a)&&(t.result=s.apply(a,e),t.result===!1&&t.preventDefault());if(t.type=p,!r&&!t.isDefaultPrevented()&&(!h._default||h._default.apply(d.pop(),e)===!1)&&Lt(n)&&o&&n[p]&&!vt.isWindow(n)){c=n[o],c&&(n[o]=null),vt.event.triggered=p;try{n[p]()}catch(g){}vt.event.triggered=void 0,c&&(n[o]=c)}return t.result}},dispatch:function(t){t=vt.event.fix(t);var e,i,n,r,s,o=[],a=lt.call(arguments),l=(vt._data(this,"events")||{})[t.type]||[],h=vt.event.special[t.type]||{};if(a[0]=t,t.delegateTarget=this,!h.preDispatch||h.preDispatch.call(this,t)!==!1){for(o=vt.event.handlers.call(this,t,l),e=0;(r=o[e++])&&!t.isPropagationStopped();)for(t.currentTarget=r.elem,i=0;(s=r.handlers[i++])&&!t.isImmediatePropagationStopped();)t.rnamespace&&!t.rnamespace.test(s.namespace)||(t.handleObj=s,t.data=s.data,n=((vt.event.special[s.origType]||{}).handle||s.handler).apply(r.elem,a),void 0!==n&&(t.result=n)===!1&&(t.preventDefault(),t.stopPropagation()));return h.postDispatch&&h.postDispatch.call(this,t),t.result}},handlers:function(t,e){var i,n,r,s,o=[],a=e.delegateCount,l=t.target;if(a&&l.nodeType&&("click"!==t.type||isNaN(t.button)||t.button<1))for(;l!=this;l=l.parentNode||this)if(1===l.nodeType&&(l.disabled!==!0||"click"!==t.type)){for(n=[],i=0;i-1:vt.find(r,this,null,[l]).length),n[r]&&n.push(s);n.length&&o.push({elem:l,handlers:n})}return a]","i"),se=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi,oe=/\s*$/g,ce=v(at),ue=ce.appendChild(at.createElement("div"));vt.extend({htmlPrefilter:function(t){return t.replace(se,"<$1>")},clone:function(t,e,i){var n,r,s,o,a,l=vt.contains(t.ownerDocument,t);if(gt.html5Clone||vt.isXMLDoc(t)||!re.test("<"+t.nodeName+">")?s=t.cloneNode(!0):(ue.innerHTML=t.outerHTML,ue.removeChild(s=ue.firstChild)),!(gt.noCloneEvent&>.noCloneChecked||1!==t.nodeType&&11!==t.nodeType||vt.isXMLDoc(t)))for(n=y(s),a=y(t),o=0;null!=(r=a[o]);++o)n[o]&&O(r,n[o]);if(e)if(i)for(a=a||y(t),n=n||y(s),o=0;null!=(r=a[o]);o++)N(r,n[o]);else N(t,s);return n=y(s,"script"),n.length>0&&b(n,!l&&y(t,"script")),n=a=r=null,s},cleanData:function(t,e){for(var i,n,r,s,o=0,a=vt.expando,l=vt.cache,h=gt.attributes,c=vt.event.special;null!=(i=t[o]);o++)if((e||Lt(i))&&(r=i[a],s=r&&l[r])){if(s.events)for(n in s.events)c[n]?vt.event.remove(i,n):vt.removeEvent(i,n,s.handle);l[r]&&(delete l[r],h||"undefined"==typeof i.removeAttribute?i[a]=void 0:i.removeAttribute(a),ot.push(r))}}}),vt.fn.extend({domManip:M,detach:function(t){return D(this,t,!0)},remove:function(t){return D(this,t)},text:function(t){return zt(this,function(t){return void 0===t?vt.text(this):this.empty().append((this[0]&&this[0].ownerDocument||at).createTextNode(t))},null,t,arguments.length)},append:function(){return M(this,arguments,function(t){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var e=T(this,t);e.appendChild(t)}})},prepend:function(){return M(this,arguments,function(t){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var e=T(this,t);e.insertBefore(t,e.firstChild)}})},before:function(){return M(this,arguments,function(t){this.parentNode&&this.parentNode.insertBefore(t,this)})},after:function(){return M(this,arguments,function(t){this.parentNode&&this.parentNode.insertBefore(t,this.nextSibling)})},empty:function(){for(var t,e=0;null!=(t=this[e]);e++){for(1===t.nodeType&&vt.cleanData(y(t,!1));t.firstChild;)t.removeChild(t.firstChild);t.options&&vt.nodeName(t,"select")&&(t.options.length=0)}return this},clone:function(t,e){return t=null!=t&&t,e=null==e?t:e,this.map(function(){return vt.clone(this,t,e)})},html:function(t){return zt(this,function(t){var e=this[0]||{},i=0,n=this.length;if(void 0===t)return 1===e.nodeType?e.innerHTML.replace(ne,""):void 0;if("string"==typeof t&&!oe.test(t)&&(gt.htmlSerialize||!re.test(t))&&(gt.leadingWhitespace||!Gt.test(t))&&!Yt[(Ut.exec(t)||["",""])[1].toLowerCase()]){t=vt.htmlPrefilter(t);try{for(;it",h.childNodes[0].style.borderCollapse="separate",t=h.getElementsByTagName("td"),t[0].style.cssText="margin:0;border:0;padding:0;display:none",s=0===t[0].offsetHeight,s&&(t[0].style.display="",t[1].style.display="none",s=0===t[0].offsetHeight)),u.removeChild(l)}var e,n,r,s,o,a,l=at.createElement("div"),h=at.createElement("div");h.style&&(h.style.cssText="float:left;opacity:.5",gt.opacity="0.5"===h.style.opacity,gt.cssFloat=!!h.style.cssFloat,h.style.backgroundClip="content-box",h.cloneNode(!0).style.backgroundClip="",gt.clearCloneStyle="content-box"===h.style.backgroundClip,l=at.createElement("div"),l.style.cssText="border:0;width:8px;height:0;top:0;left:-9999px;padding:0;margin-top:1px;position:absolute",h.innerHTML="",l.appendChild(h),gt.boxSizing=""===h.style.boxSizing||""===h.style.MozBoxSizing||""===h.style.WebkitBoxSizing,vt.extend(gt,{reliableHiddenOffsets:function(){return null==e&&t(),s},boxSizingReliable:function(){return null==e&&t(),r},pixelMarginRight:function(){return null==e&&t(),n},pixelPosition:function(){return null==e&&t(),e},reliableMarginRight:function(){return null==e&&t(),o},reliableMarginLeft:function(){return null==e&&t(),a}}))}();var ye,be,we=/^(top|right|bottom|left)$/;i.getComputedStyle?(ye=function(t){var e=t.ownerDocument.defaultView;return e&&e.opener||(e=i),e.getComputedStyle(t)},be=function(t,e,i){var n,r,s,o,a=t.style;return i=i||ye(t),o=i?i.getPropertyValue(e)||i[e]:void 0,""!==o&&void 0!==o||vt.contains(t.ownerDocument,t)||(o=vt.style(t,e)),i&&!gt.pixelMarginRight()&&ge.test(o)&&fe.test(e)&&(n=a.width,r=a.minWidth,s=a.maxWidth,a.minWidth=a.maxWidth=a.width=o,o=i.width,a.width=n,a.minWidth=r,a.maxWidth=s),void 0===o?o:o+""}):ve.currentStyle&&(ye=function(t){return t.currentStyle},be=function(t,e,i){var n,r,s,o,a=t.style;return i=i||ye(t),o=i?i[e]:void 0,null==o&&a&&a[e]&&(o=a[e]),ge.test(o)&&!we.test(e)&&(n=a.left,r=t.runtimeStyle,s=r&&r.left,s&&(r.left=t.currentStyle.left),a.left="fontSize"===e?"1em":o,o=a.pixelLeft+"px",a.left=n,s&&(r.left=s)),void 0===o?o:o+""||"auto"});var _e=/alpha\([^)]*\)/i,xe=/opacity\s*=\s*([^)]*)/i,Ce=/^(none|table(?!-c[ea]).+)/,Se=new RegExp("^("+$t+")(.*)$","i"),ke={position:"absolute",visibility:"hidden",display:"block"},Te={letterSpacing:"0",fontWeight:"400"},Ee=["Webkit","O","Moz","ms"],Ae=at.createElement("div").style;vt.extend({cssHooks:{opacity:{get:function(t,e){if(e){var i=be(t,"opacity");return""===i?"1":i}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":gt.cssFloat?"cssFloat":"styleFloat"},style:function(t,e,i,n){if(t&&3!==t.nodeType&&8!==t.nodeType&&t.style){var r,s,o,a=vt.camelCase(e),l=t.style;if(e=vt.cssProps[a]||(vt.cssProps[a]=L(a)||a),o=vt.cssHooks[e]||vt.cssHooks[a],void 0===i)return o&&"get"in o&&void 0!==(r=o.get(t,!1,n))?r:l[e];if(s=typeof i,"string"===s&&(r=Ft.exec(i))&&r[1]&&(i=m(t,e,r),s="number"),null!=i&&i===i&&("number"===s&&(i+=r&&r[3]||(vt.cssNumber[a]?"":"px")),gt.clearCloneStyle||""!==i||0!==e.indexOf("background")||(l[e]="inherit"),!(o&&"set"in o&&void 0===(i=o.set(t,i,n)))))try{l[e]=i}catch(h){}}},css:function(t,e,i,n){var r,s,o,a=vt.camelCase(e);return e=vt.cssProps[a]||(vt.cssProps[a]=L(a)||a),o=vt.cssHooks[e]||vt.cssHooks[a],o&&"get"in o&&(s=o.get(t,!0,i)),void 0===s&&(s=be(t,e,n)),"normal"===s&&e in Te&&(s=Te[e]),""===i||i?(r=parseFloat(s),i===!0||isFinite(r)?r||0:s):s}}),vt.each(["height","width"],function(t,e){vt.cssHooks[e]={get:function(t,i,n){if(i)return Ce.test(vt.css(t,"display"))&&0===t.offsetWidth?me(t,ke,function(){return F(t,e,n)}):F(t,e,n)},set:function(t,i,n){var r=n&&ye(t);return I(t,i,n?$(t,e,n,gt.boxSizing&&"border-box"===vt.css(t,"boxSizing",!1,r),r):0)}}}),gt.opacity||(vt.cssHooks.opacity={get:function(t,e){return xe.test((e&&t.currentStyle?t.currentStyle.filter:t.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":e?"1":""},set:function(t,e){var i=t.style,n=t.currentStyle,r=vt.isNumeric(e)?"alpha(opacity="+100*e+")":"",s=n&&n.filter||i.filter||"";i.zoom=1,(e>=1||""===e)&&""===vt.trim(s.replace(_e,""))&&i.removeAttribute&&(i.removeAttribute("filter"),""===e||n&&!n.filter)||(i.filter=_e.test(s)?s.replace(_e,r):s+" "+r)}}),vt.cssHooks.marginRight=j(gt.reliableMarginRight,function(t,e){if(e)return me(t,{display:"inline-block"},be,[t,"marginRight"])}),vt.cssHooks.marginLeft=j(gt.reliableMarginLeft,function(t,e){if(e)return(parseFloat(be(t,"marginLeft"))||(vt.contains(t.ownerDocument,t)?t.getBoundingClientRect().left-me(t,{marginLeft:0},function(){return t.getBoundingClientRect().left}):0))+"px"}),vt.each({margin:"",padding:"",border:"Width"},function(t,e){vt.cssHooks[t+e]={expand:function(i){for(var n=0,r={},s="string"==typeof i?i.split(" "):[i];n<4;n++)r[t+qt[n]+e]=s[n]||s[n-2]||s[0];return r}},fe.test(t)||(vt.cssHooks[t+e].set=I)}),vt.fn.extend({css:function(t,e){return zt(this,function(t,e,i){var n,r,s={},o=0;if(vt.isArray(e)){for(n=ye(t),r=e.length;o1)},show:function(){return H(this,!0)},hide:function(){return H(this)},toggle:function(t){return"boolean"==typeof t?t?this.show():this.hide():this.each(function(){Wt(this)?vt(this).show():vt(this).hide()})}}),vt.Tween=q,q.prototype={constructor:q,init:function(t,e,i,n,r,s){this.elem=t,this.prop=i,this.easing=r||vt.easing._default,this.options=e,this.start=this.now=this.cur(),this.end=n,this.unit=s||(vt.cssNumber[i]?"":"px")},cur:function(){var t=q.propHooks[this.prop];return t&&t.get?t.get(this):q.propHooks._default.get(this)},run:function(t){var e,i=q.propHooks[this.prop];return this.options.duration?this.pos=e=vt.easing[this.easing](t,this.options.duration*t,0,1,this.options.duration):this.pos=e=t,this.now=(this.end-this.start)*e+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),i&&i.set?i.set(this):q.propHooks._default.set(this),this}},q.prototype.init.prototype=q.prototype,q.propHooks={_default:{get:function(t){var e;return 1!==t.elem.nodeType||null!=t.elem[t.prop]&&null==t.elem.style[t.prop]?t.elem[t.prop]:(e=vt.css(t.elem,t.prop,""),e&&"auto"!==e?e:0)},set:function(t){vt.fx.step[t.prop]?vt.fx.step[t.prop](t):1!==t.elem.nodeType||null==t.elem.style[vt.cssProps[t.prop]]&&!vt.cssHooks[t.prop]?t.elem[t.prop]=t.now:vt.style(t.elem,t.prop,t.now+t.unit)}}},q.propHooks.scrollTop=q.propHooks.scrollLeft={set:function(t){t.elem.nodeType&&t.elem.parentNode&&(t.elem[t.prop]=t.now)}},vt.easing={linear:function(t){return t},swing:function(t){return.5-Math.cos(t*Math.PI)/2},_default:"swing"},vt.fx=q.prototype.init,vt.fx.step={};var Ne,Oe,Me=/^(?:toggle|show|hide)$/,De=/queueHooks$/;vt.Animation=vt.extend(G,{tweeners:{"*":[function(t,e){var i=this.createTween(t,e);return m(i.elem,t,Ft.exec(e),i),i}]},tweener:function(t,e){vt.isFunction(t)?(e=t,t=["*"]):t=t.match(Pt);for(var i,n=0,r=t.length;n
                                            a",t=i.getElementsByTagName("a")[0],e.setAttribute("type","checkbox"),i.appendChild(e),t=i.getElementsByTagName("a")[0],t.style.cssText="top:1px",gt.getSetAttribute="t"!==i.className,gt.style=/top/.test(t.getAttribute("style")),gt.hrefNormalized="/a"===t.getAttribute("href"),gt.checkOn=!!e.value,gt.optSelected=r.selected,gt.enctype=!!at.createElement("form").enctype,n.disabled=!0,gt.optDisabled=!r.disabled,e=at.createElement("input"),e.setAttribute("value",""),gt.input=""===e.getAttribute("value"),e.value="t",e.setAttribute("type","radio"),gt.radioValue="t"===e.value}();var Pe=/\r/g,Re=/[\x20\t\r\n\f]+/g;vt.fn.extend({val:function(t){var e,i,n,r=this[0];{if(arguments.length)return n=vt.isFunction(t),this.each(function(i){var r;1===this.nodeType&&(r=n?t.call(this,i,vt(this).val()):t,null==r?r="":"number"==typeof r?r+="":vt.isArray(r)&&(r=vt.map(r,function(t){return null==t?"":t+""})),e=vt.valHooks[this.type]||vt.valHooks[this.nodeName.toLowerCase()],e&&"set"in e&&void 0!==e.set(this,r,"value")||(this.value=r))});if(r)return e=vt.valHooks[r.type]||vt.valHooks[r.nodeName.toLowerCase()],e&&"get"in e&&void 0!==(i=e.get(r,"value"))?i:(i=r.value,"string"==typeof i?i.replace(Pe,""):null==i?"":i)}}}),vt.extend({valHooks:{option:{get:function(t){var e=vt.find.attr(t,"value");return null!=e?e:vt.trim(vt.text(t)).replace(Re," ")}},select:{get:function(t){for(var e,i,n=t.options,r=t.selectedIndex,s="select-one"===t.type||r<0,o=s?null:[],a=s?r+1:n.length,l=r<0?a:s?r:0;l-1)try{n.selected=i=!0}catch(a){n.scrollHeight}else n.selected=!1;return i||(t.selectedIndex=-1),r}}}}),vt.each(["radio","checkbox"],function(){vt.valHooks[this]={set:function(t,e){if(vt.isArray(e))return t.checked=vt.inArray(vt(t).val(),e)>-1}},gt.checkOn||(vt.valHooks[this].get=function(t){return null===t.getAttribute("value")?"on":t.value})});var je,Le,He=vt.expr.attrHandle,Ie=/^(?:checked|selected)$/i,$e=gt.getSetAttribute,Fe=gt.input;vt.fn.extend({attr:function(t,e){return zt(this,vt.attr,t,e,arguments.length>1)},removeAttr:function(t){return this.each(function(){vt.removeAttr(this,t)})}}),vt.extend({attr:function(t,e,i){var n,r,s=t.nodeType;if(3!==s&&8!==s&&2!==s)return"undefined"==typeof t.getAttribute?vt.prop(t,e,i):(1===s&&vt.isXMLDoc(t)||(e=e.toLowerCase(),r=vt.attrHooks[e]||(vt.expr.match.bool.test(e)?Le:je)),void 0!==i?null===i?void vt.removeAttr(t,e):r&&"set"in r&&void 0!==(n=r.set(t,i,e))?n:(t.setAttribute(e,i+""),i):r&&"get"in r&&null!==(n=r.get(t,e))?n:(n=vt.find.attr(t,e),null==n?void 0:n))},attrHooks:{type:{set:function(t,e){if(!gt.radioValue&&"radio"===e&&vt.nodeName(t,"input")){var i=t.value;return t.setAttribute("type",e),i&&(t.value=i),e}}}},removeAttr:function(t,e){var i,n,r=0,s=e&&e.match(Pt);if(s&&1===t.nodeType)for(;i=s[r++];)n=vt.propFix[i]||i,vt.expr.match.bool.test(i)?Fe&&$e||!Ie.test(i)?t[n]=!1:t[vt.camelCase("default-"+i)]=t[n]=!1:vt.attr(t,i,""),t.removeAttribute($e?i:n)}}),Le={set:function(t,e,i){return e===!1?vt.removeAttr(t,i):Fe&&$e||!Ie.test(i)?t.setAttribute(!$e&&vt.propFix[i]||i,i):t[vt.camelCase("default-"+i)]=t[i]=!0,i}},vt.each(vt.expr.match.bool.source.match(/\w+/g),function(t,e){var i=He[e]||vt.find.attr;Fe&&$e||!Ie.test(e)?He[e]=function(t,e,n){var r,s;return n||(s=He[e],He[e]=r,r=null!=i(t,e,n)?e.toLowerCase():null,He[e]=s),r}:He[e]=function(t,e,i){if(!i)return t[vt.camelCase("default-"+e)]?e.toLowerCase():null}}),Fe&&$e||(vt.attrHooks.value={set:function(t,e,i){return vt.nodeName(t,"input")?void(t.defaultValue=e):je&&je.set(t,e,i)}}),$e||(je={set:function(t,e,i){var n=t.getAttributeNode(i);if(n||t.setAttributeNode(n=t.ownerDocument.createAttribute(i)),n.value=e+="","value"===i||e===t.getAttribute(i))return e}},He.id=He.name=He.coords=function(t,e,i){var n;if(!i)return(n=t.getAttributeNode(e))&&""!==n.value?n.value:null},vt.valHooks.button={get:function(t,e){var i=t.getAttributeNode(e);if(i&&i.specified)return i.value},set:je.set},vt.attrHooks.contenteditable={set:function(t,e,i){je.set(t,""!==e&&e,i)}},vt.each(["width","height"],function(t,e){vt.attrHooks[e]={set:function(t,i){if(""===i)return t.setAttribute(e,"auto"),i}}})),gt.style||(vt.attrHooks.style={get:function(t){return t.style.cssText||void 0},set:function(t,e){return t.style.cssText=e+""}});var qe=/^(?:input|select|textarea|button|object)$/i,We=/^(?:a|area)$/i;vt.fn.extend({prop:function(t,e){return zt(this,vt.prop,t,e,arguments.length>1)},removeProp:function(t){return t=vt.propFix[t]||t,this.each(function(){try{this[t]=void 0,delete this[t]}catch(e){}})}}),vt.extend({prop:function(t,e,i){var n,r,s=t.nodeType;if(3!==s&&8!==s&&2!==s)return 1===s&&vt.isXMLDoc(t)||(e=vt.propFix[e]||e,r=vt.propHooks[e]),void 0!==i?r&&"set"in r&&void 0!==(n=r.set(t,i,e))?n:t[e]=i:r&&"get"in r&&null!==(n=r.get(t,e))?n:t[e]},propHooks:{tabIndex:{get:function(t){var e=vt.find.attr(t,"tabindex");return e?parseInt(e,10):qe.test(t.nodeName)||We.test(t.nodeName)&&t.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),gt.hrefNormalized||vt.each(["href","src"],function(t,e){vt.propHooks[e]={get:function(t){return t.getAttribute(e,4)}}}),gt.optSelected||(vt.propHooks.selected={get:function(t){var e=t.parentNode;return e&&(e.selectedIndex,e.parentNode&&e.parentNode.selectedIndex),null},set:function(t){var e=t.parentNode;e&&(e.selectedIndex,e.parentNode&&e.parentNode.selectedIndex)}}),vt.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){vt.propFix[this.toLowerCase()]=this}),gt.enctype||(vt.propFix.enctype="encoding");var ze=/[\t\r\n\f]/g;vt.fn.extend({addClass:function(t){var e,i,n,r,s,o,a,l=0;if(vt.isFunction(t))return this.each(function(e){vt(this).addClass(t.call(this,e,X(this)))});if("string"==typeof t&&t)for(e=t.match(Pt)||[];i=this[l++];)if(r=X(i),n=1===i.nodeType&&(" "+r+" ").replace(ze," ")){for(o=0;s=e[o++];)n.indexOf(" "+s+" ")<0&&(n+=s+" ");a=vt.trim(n),r!==a&&vt.attr(i,"class",a)}return this},removeClass:function(t){var e,i,n,r,s,o,a,l=0;if(vt.isFunction(t))return this.each(function(e){vt(this).removeClass(t.call(this,e,X(this)))});if(!arguments.length)return this.attr("class","");if("string"==typeof t&&t)for(e=t.match(Pt)||[];i=this[l++];)if(r=X(i),n=1===i.nodeType&&(" "+r+" ").replace(ze," ")){for(o=0;s=e[o++];)for(;n.indexOf(" "+s+" ")>-1;)n=n.replace(" "+s+" "," ");a=vt.trim(n),r!==a&&vt.attr(i,"class",a)}return this},toggleClass:function(t,e){var i=typeof t;return"boolean"==typeof e&&"string"===i?e?this.addClass(t):this.removeClass(t):vt.isFunction(t)?this.each(function(i){vt(this).toggleClass(t.call(this,i,X(this),e),e)}):this.each(function(){var e,n,r,s;if("string"===i)for(n=0,r=vt(this),s=t.match(Pt)||[];e=s[n++];)r.hasClass(e)?r.removeClass(e):r.addClass(e);else void 0!==t&&"boolean"!==i||(e=X(this),e&&vt._data(this,"__className__",e),vt.attr(this,"class",e||t===!1?"":vt._data(this,"__className__")||"")); +})},hasClass:function(t){var e,i,n=0;for(e=" "+t+" ";i=this[n++];)if(1===i.nodeType&&(" "+X(i)+" ").replace(ze," ").indexOf(e)>-1)return!0;return!1}}),vt.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(t,e){vt.fn[e]=function(t,i){return arguments.length>0?this.on(e,null,t,i):this.trigger(e)}}),vt.fn.extend({hover:function(t,e){return this.mouseenter(t).mouseleave(e||t)}});var Be=i.location,Ue=vt.now(),Ve=/\?/,Ge=/(,)|(\[|{)|(}|])|"(?:[^"\\\r\n]|\\["\\\/bfnrt]|\\u[\da-fA-F]{4})*"\s*:?|true|false|null|-?(?!0\d)\d+(?:\.\d+|)(?:[eE][+-]?\d+|)/g;vt.parseJSON=function(t){if(i.JSON&&i.JSON.parse)return i.JSON.parse(t+"");var e,n=null,r=vt.trim(t+"");return r&&!vt.trim(r.replace(Ge,function(t,i,r,s){return e&&i&&(n=0),0===n?t:(e=r||i,n+=!s-!r,"")}))?Function("return "+r)():vt.error("Invalid JSON: "+t)},vt.parseXML=function(t){var e,n;if(!t||"string"!=typeof t)return null;try{i.DOMParser?(n=new i.DOMParser,e=n.parseFromString(t,"text/xml")):(e=new i.ActiveXObject("Microsoft.XMLDOM"),e.async="false",e.loadXML(t))}catch(r){e=void 0}return e&&e.documentElement&&!e.getElementsByTagName("parsererror").length||vt.error("Invalid XML: "+t),e};var Xe=/#.*$/,Ye=/([?&])_=[^&]*/,Je=/^(.*?):[ \t]*([^\r\n]*)\r?$/gm,Qe=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Ke=/^(?:GET|HEAD)$/,Ze=/^\/\//,ti=/^([\w.+-]+:)(?:\/\/(?:[^\/?#]*@|)([^\/?#:]*)(?::(\d+)|)|)/,ei={},ii={},ni="*/".concat("*"),ri=Be.href,si=ti.exec(ri.toLowerCase())||[];vt.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:ri,type:"GET",isLocal:Qe.test(si[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":ni,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":vt.parseJSON,"text xml":vt.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(t,e){return e?Q(Q(t,vt.ajaxSettings),e):Q(vt.ajaxSettings,t)},ajaxPrefilter:Y(ei),ajaxTransport:Y(ii),ajax:function(t,e){function n(t,e,n,r){var s,u,y,b,_,C=e;2!==w&&(w=2,l&&i.clearTimeout(l),c=void 0,a=r||"",x.readyState=t>0?4:0,s=t>=200&&t<300||304===t,n&&(b=K(d,x,n)),b=Z(d,b,x,s),s?(d.ifModified&&(_=x.getResponseHeader("Last-Modified"),_&&(vt.lastModified[o]=_),_=x.getResponseHeader("etag"),_&&(vt.etag[o]=_)),204===t||"HEAD"===d.type?C="nocontent":304===t?C="notmodified":(C=b.state,u=b.data,y=b.error,s=!y)):(y=C,!t&&C||(C="error",t<0&&(t=0))),x.status=t,x.statusText=(e||C)+"",s?g.resolveWith(p,[u,C,x]):g.rejectWith(p,[x,C,y]),x.statusCode(v),v=void 0,h&&f.trigger(s?"ajaxSuccess":"ajaxError",[x,d,s?u:y]),m.fireWith(p,[x,C]),h&&(f.trigger("ajaxComplete",[x,d]),--vt.active||vt.event.trigger("ajaxStop")))}"object"==typeof t&&(e=t,t=void 0),e=e||{};var r,s,o,a,l,h,c,u,d=vt.ajaxSetup({},e),p=d.context||d,f=d.context&&(p.nodeType||p.jquery)?vt(p):vt.event,g=vt.Deferred(),m=vt.Callbacks("once memory"),v=d.statusCode||{},y={},b={},w=0,_="canceled",x={readyState:0,getResponseHeader:function(t){var e;if(2===w){if(!u)for(u={};e=Je.exec(a);)u[e[1].toLowerCase()]=e[2];e=u[t.toLowerCase()]}return null==e?null:e},getAllResponseHeaders:function(){return 2===w?a:null},setRequestHeader:function(t,e){var i=t.toLowerCase();return w||(t=b[i]=b[i]||t,y[t]=e),this},overrideMimeType:function(t){return w||(d.mimeType=t),this},statusCode:function(t){var e;if(t)if(w<2)for(e in t)v[e]=[v[e],t[e]];else x.always(t[x.status]);return this},abort:function(t){var e=t||_;return c&&c.abort(e),n(0,e),this}};if(g.promise(x).complete=m.add,x.success=x.done,x.error=x.fail,d.url=((t||d.url||ri)+"").replace(Xe,"").replace(Ze,si[1]+"//"),d.type=e.method||e.type||d.method||d.type,d.dataTypes=vt.trim(d.dataType||"*").toLowerCase().match(Pt)||[""],null==d.crossDomain&&(r=ti.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]===si[1]&&r[2]===si[2]&&(r[3]||("http:"===r[1]?"80":"443"))===(si[3]||("http:"===si[1]?"80":"443")))),d.data&&d.processData&&"string"!=typeof d.data&&(d.data=vt.param(d.data,d.traditional)),J(ei,d,e,x),2===w)return x;h=vt.event&&d.global,h&&0===vt.active++&&vt.event.trigger("ajaxStart"),d.type=d.type.toUpperCase(),d.hasContent=!Ke.test(d.type),o=d.url,d.hasContent||(d.data&&(o=d.url+=(Ve.test(o)?"&":"?")+d.data,delete d.data),d.cache===!1&&(d.url=Ye.test(o)?o.replace(Ye,"$1_="+Ue++):o+(Ve.test(o)?"&":"?")+"_="+Ue++)),d.ifModified&&(vt.lastModified[o]&&x.setRequestHeader("If-Modified-Since",vt.lastModified[o]),vt.etag[o]&&x.setRequestHeader("If-None-Match",vt.etag[o])),(d.data&&d.hasContent&&d.contentType!==!1||e.contentType)&&x.setRequestHeader("Content-Type",d.contentType),x.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+("*"!==d.dataTypes[0]?", "+ni+"; q=0.01":""):d.accepts["*"]);for(s in d.headers)x.setRequestHeader(s,d.headers[s]);if(d.beforeSend&&(d.beforeSend.call(p,x,d)===!1||2===w))return x.abort();_="abort";for(s in{success:1,error:1,complete:1})x[s](d[s]);if(c=J(ii,d,e,x)){if(x.readyState=1,h&&f.trigger("ajaxSend",[x,d]),2===w)return x;d.async&&d.timeout>0&&(l=i.setTimeout(function(){x.abort("timeout")},d.timeout));try{w=1,c.send(y,n)}catch(C){if(!(w<2))throw C;n(-1,C)}}else n(-1,"No Transport");return x},getJSON:function(t,e,i){return vt.get(t,e,i,"json")},getScript:function(t,e){return vt.get(t,void 0,e,"script")}}),vt.each(["get","post"],function(t,e){vt[e]=function(t,i,n,r){return vt.isFunction(i)&&(r=r||n,n=i,i=void 0),vt.ajax(vt.extend({url:t,type:e,dataType:r,data:i,success:n},vt.isPlainObject(t)&&t))}}),vt._evalUrl=function(t){return vt.ajax({url:t,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,"throws":!0})},vt.fn.extend({wrapAll:function(t){if(vt.isFunction(t))return this.each(function(e){vt(this).wrapAll(t.call(this,e))});if(this[0]){var e=vt(t,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&e.insertBefore(this[0]),e.map(function(){for(var t=this;t.firstChild&&1===t.firstChild.nodeType;)t=t.firstChild;return t}).append(this)}return this},wrapInner:function(t){return vt.isFunction(t)?this.each(function(e){vt(this).wrapInner(t.call(this,e))}):this.each(function(){var e=vt(this),i=e.contents();i.length?i.wrapAll(t):e.append(t)})},wrap:function(t){var e=vt.isFunction(t);return this.each(function(i){vt(this).wrapAll(e?t.call(this,i):t)})},unwrap:function(){return this.parent().each(function(){vt.nodeName(this,"body")||vt(this).replaceWith(this.childNodes)}).end()}}),vt.expr.filters.hidden=function(t){return gt.reliableHiddenOffsets()?t.offsetWidth<=0&&t.offsetHeight<=0&&!t.getClientRects().length:et(t)},vt.expr.filters.visible=function(t){return!vt.expr.filters.hidden(t)};var oi=/%20/g,ai=/\[\]$/,li=/\r?\n/g,hi=/^(?:submit|button|image|reset|file)$/i,ci=/^(?:input|select|textarea|keygen)/i;vt.param=function(t,e){var i,n=[],r=function(t,e){e=vt.isFunction(e)?e():null==e?"":e,n[n.length]=encodeURIComponent(t)+"="+encodeURIComponent(e)};if(void 0===e&&(e=vt.ajaxSettings&&vt.ajaxSettings.traditional),vt.isArray(t)||t.jquery&&!vt.isPlainObject(t))vt.each(t,function(){r(this.name,this.value)});else for(i in t)it(i,t[i],e,r);return n.join("&").replace(oi,"+")},vt.fn.extend({serialize:function(){return vt.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var t=vt.prop(this,"elements");return t?vt.makeArray(t):this}).filter(function(){var t=this.type;return this.name&&!vt(this).is(":disabled")&&ci.test(this.nodeName)&&!hi.test(t)&&(this.checked||!Bt.test(t))}).map(function(t,e){var i=vt(this).val();return null==i?null:vt.isArray(i)?vt.map(i,function(t){return{name:e.name,value:t.replace(li,"\r\n")}}):{name:e.name,value:i.replace(li,"\r\n")}}).get()}}),vt.ajaxSettings.xhr=void 0!==i.ActiveXObject?function(){return this.isLocal?rt():at.documentMode>8?nt():/^(get|post|head|put|delete|options)$/i.test(this.type)&&nt()||rt()}:nt;var ui=0,di={},pi=vt.ajaxSettings.xhr();i.attachEvent&&i.attachEvent("onunload",function(){for(var t in di)di[t](void 0,!0)}),gt.cors=!!pi&&"withCredentials"in pi,pi=gt.ajax=!!pi,pi&&vt.ajaxTransport(function(t){if(!t.crossDomain||gt.cors){var e;return{send:function(n,r){var s,o=t.xhr(),a=++ui;if(o.open(t.type,t.url,t.async,t.username,t.password),t.xhrFields)for(s in t.xhrFields)o[s]=t.xhrFields[s];t.mimeType&&o.overrideMimeType&&o.overrideMimeType(t.mimeType),t.crossDomain||n["X-Requested-With"]||(n["X-Requested-With"]="XMLHttpRequest");for(s in n)void 0!==n[s]&&o.setRequestHeader(s,n[s]+"");o.send(t.hasContent&&t.data||null),e=function(i,n){var s,l,h;if(e&&(n||4===o.readyState))if(delete di[a],e=void 0,o.onreadystatechange=vt.noop,n)4!==o.readyState&&o.abort();else{h={},s=o.status,"string"==typeof o.responseText&&(h.text=o.responseText);try{l=o.statusText}catch(c){l=""}s||!t.isLocal||t.crossDomain?1223===s&&(s=204):s=h.text?200:404}h&&r(s,l,h,o.getAllResponseHeaders())},t.async?4===o.readyState?i.setTimeout(e):o.onreadystatechange=di[a]=e:e()},abort:function(){e&&e(void 0,!0)}}}}),vt.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(t){return vt.globalEval(t),t}}}),vt.ajaxPrefilter("script",function(t){void 0===t.cache&&(t.cache=!1),t.crossDomain&&(t.type="GET",t.global=!1)}),vt.ajaxTransport("script",function(t){if(t.crossDomain){var e,i=at.head||vt("head")[0]||at.documentElement;return{send:function(n,r){e=at.createElement("script"),e.async=!0,t.scriptCharset&&(e.charset=t.scriptCharset),e.src=t.url,e.onload=e.onreadystatechange=function(t,i){(i||!e.readyState||/loaded|complete/.test(e.readyState))&&(e.onload=e.onreadystatechange=null,e.parentNode&&e.parentNode.removeChild(e),e=null,i||r(200,"success"))},i.insertBefore(e,i.firstChild)},abort:function(){e&&e.onload(void 0,!0)}}}});var fi=[],gi=/(=)\?(?=&|$)|\?\?/;vt.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var t=fi.pop()||vt.expando+"_"+Ue++;return this[t]=!0,t}}),vt.ajaxPrefilter("json jsonp",function(t,e,n){var r,s,o,a=t.jsonp!==!1&&(gi.test(t.url)?"url":"string"==typeof t.data&&0===(t.contentType||"").indexOf("application/x-www-form-urlencoded")&&gi.test(t.data)&&"data");if(a||"jsonp"===t.dataTypes[0])return r=t.jsonpCallback=vt.isFunction(t.jsonpCallback)?t.jsonpCallback():t.jsonpCallback,a?t[a]=t[a].replace(gi,"$1"+r):t.jsonp!==!1&&(t.url+=(Ve.test(t.url)?"&":"?")+t.jsonp+"="+r),t.converters["script json"]=function(){return o||vt.error(r+" was not called"),o[0]},t.dataTypes[0]="json",s=i[r],i[r]=function(){o=arguments},n.always(function(){void 0===s?vt(i).removeProp(r):i[r]=s,t[r]&&(t.jsonpCallback=e.jsonpCallback,fi.push(r)),o&&vt.isFunction(s)&&s(o[0]),o=s=void 0}),"script"}),vt.parseHTML=function(t,e,i){if(!t||"string"!=typeof t)return null;"boolean"==typeof e&&(i=e,e=!1),e=e||at;var n=Tt.exec(t),r=!i&&[];return n?[e.createElement(n[1])]:(n=_([t],e,r),r&&r.length&&vt(r).remove(),vt.merge([],n.childNodes))};var mi=vt.fn.load;vt.fn.load=function(t,e,i){if("string"!=typeof t&&mi)return mi.apply(this,arguments);var n,r,s,o=this,a=t.indexOf(" ");return a>-1&&(n=vt.trim(t.slice(a,t.length)),t=t.slice(0,a)),vt.isFunction(e)?(i=e,e=void 0):e&&"object"==typeof e&&(r="POST"),o.length>0&&vt.ajax({url:t,type:r||"GET",dataType:"html",data:e}).done(function(t){s=arguments,o.html(n?vt("
                                            ").append(vt.parseHTML(t)).find(n):t)}).always(i&&function(t,e){o.each(function(){i.apply(this,s||[t.responseText,e,t])})}),this},vt.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(t,e){vt.fn[e]=function(t){return this.on(e,t)}}),vt.expr.filters.animated=function(t){return vt.grep(vt.timers,function(e){return t===e.elem}).length},vt.offset={setOffset:function(t,e,i){var n,r,s,o,a,l,h,c=vt.css(t,"position"),u=vt(t),d={};"static"===c&&(t.style.position="relative"),a=u.offset(),s=vt.css(t,"top"),l=vt.css(t,"left"),h=("absolute"===c||"fixed"===c)&&vt.inArray("auto",[s,l])>-1,h?(n=u.position(),o=n.top,r=n.left):(o=parseFloat(s)||0,r=parseFloat(l)||0),vt.isFunction(e)&&(e=e.call(t,i,vt.extend({},a))),null!=e.top&&(d.top=e.top-a.top+o),null!=e.left&&(d.left=e.left-a.left+r),"using"in e?e.using.call(t,d):u.css(d)}},vt.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){vt.offset.setOffset(this,t,e)});var e,i,n={top:0,left:0},r=this[0],s=r&&r.ownerDocument;if(s)return e=s.documentElement,vt.contains(e,r)?("undefined"!=typeof r.getBoundingClientRect&&(n=r.getBoundingClientRect()),i=st(s),{top:n.top+(i.pageYOffset||e.scrollTop)-(e.clientTop||0),left:n.left+(i.pageXOffset||e.scrollLeft)-(e.clientLeft||0)}):n},position:function(){if(this[0]){var t,e,i={top:0,left:0},n=this[0];return"fixed"===vt.css(n,"position")?e=n.getBoundingClientRect():(t=this.offsetParent(),e=this.offset(),vt.nodeName(t[0],"html")||(i=t.offset()),i.top+=vt.css(t[0],"borderTopWidth",!0),i.left+=vt.css(t[0],"borderLeftWidth",!0)),{top:e.top-i.top-vt.css(n,"marginTop",!0),left:e.left-i.left-vt.css(n,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){for(var t=this.offsetParent;t&&!vt.nodeName(t,"html")&&"static"===vt.css(t,"position");)t=t.offsetParent;return t||ve})}}),vt.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,e){var i=/Y/.test(e);vt.fn[t]=function(n){return zt(this,function(t,n,r){var s=st(t);return void 0===r?s?e in s?s[e]:s.document.documentElement[n]:t[n]:void(s?s.scrollTo(i?vt(s).scrollLeft():r,i?r:vt(s).scrollTop()):t[n]=r)},t,n,arguments.length,null)}}),vt.each(["top","left"],function(t,e){vt.cssHooks[e]=j(gt.pixelPosition,function(t,i){if(i)return i=be(t,e),ge.test(i)?vt(t).position()[e]+"px":i})}),vt.each({Height:"height",Width:"width"},function(t,e){vt.each({padding:"inner"+t,content:e,"":"outer"+t},function(i,n){vt.fn[n]=function(n,r){var s=arguments.length&&(i||"boolean"!=typeof n),o=i||(n===!0||r===!0?"margin":"border");return zt(this,function(e,i,n){var r;return vt.isWindow(e)?e.document.documentElement["client"+t]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+t],r["scroll"+t],e.body["offset"+t],r["offset"+t],r["client"+t])):void 0===n?vt.css(e,i,o):vt.style(e,i,n,o)},e,s?n:void 0,s,null)}})}),vt.fn.extend({bind:function(t,e,i){return this.on(t,null,e,i)},unbind:function(t,e){return this.off(t,null,e)},delegate:function(t,e,i,n){return this.on(e,t,i,n)},undelegate:function(t,e,i){return 1===arguments.length?this.off(t,"**"):this.off(e,t||"**",i)}}),vt.fn.size=function(){return this.length},vt.fn.andSelf=vt.fn.addBack,n=[],r=function(){return vt}.apply(e,n),!(void 0!==r&&(t.exports=r));var vi=i.jQuery,yi=i.$;return vt.noConflict=function(t){return i.$===vt&&(i.$=yi),t&&i.jQuery===vt&&(i.jQuery=vi),vt},s||(i.jQuery=i.$=vt),vt})},function(t,e,i){var n,r;(function(){function i(t){function e(e,i,n,r,s,o){for(;s>=0&&s0?0:a-1;return arguments.length<3&&(r=i[o?o[l]:l],l+=t),e(i,n,r,o,l,a)}}function s(t){return function(e,i,n){i=S(i,n);for(var r=N(e),s=t>0?0:r-1;s>=0&&s0?o=s>=0?s:Math.max(s+a,o):a=s>=0?Math.min(s+1,a):s+a+1;else if(i&&s&&a)return s=i(n,r),n[s]===r?s:-1;if(r!==r)return s=e(f.call(n,o,a),x.isNaN),s>=0?s+o:-1;for(s=t>0?o:a-1;s>=0&&s=0&&e<=A};x.each=x.forEach=function(t,e,i){e=C(e,i);var n,r;if(O(t))for(n=0,r=t.length;n=0},x.invoke=function(t,e){var i=f.call(arguments,2),n=x.isFunction(e);return x.map(t,function(t){var r=n?e:t[e];return null==r?r:r.apply(t,i)})},x.pluck=function(t,e){return x.map(t,x.property(e))},x.where=function(t,e){return x.filter(t,x.matcher(e))},x.findWhere=function(t,e){return x.find(t,x.matcher(e))},x.max=function(t,e,i){var n,r,s=-(1/0),o=-(1/0);if(null==e&&null!=t){t=O(t)?t:x.values(t);for(var a=0,l=t.length;as&&(s=n)}else e=S(e,i),x.each(t,function(t,i,n){r=e(t,i,n),(r>o||r===-(1/0)&&s===-(1/0))&&(s=t,o=r)});return s},x.min=function(t,e,i){var n,r,s=1/0,o=1/0;if(null==e&&null!=t){t=O(t)?t:x.values(t);for(var a=0,l=t.length;an||void 0===i)return 1;if(ie?(o&&(clearTimeout(o),o=null),a=h,s=t.apply(n,r),o||(n=r=null)):o||i.trailing===!1||(o=setTimeout(l,c)),s}},x.debounce=function(t,e,i){var n,r,s,o,a,l=function(){var h=x.now()-o;h=0?n=setTimeout(l,e-h):(n=null,i||(a=t.apply(s,r),n||(s=r=null)))};return function(){s=this,r=arguments,o=x.now();var h=i&&!n;return n||(n=setTimeout(l,e)),h&&(a=t.apply(s,r),s=r=null),a}},x.wrap=function(t,e){return x.partial(e,t)},x.negate=function(t){return function(){return!t.apply(this,arguments)}},x.compose=function(){var t=arguments,e=t.length-1;return function(){for(var i=e,n=t[e].apply(this,arguments);i--;)n=t[i].call(this,n);return n}},x.after=function(t,e){return function(){if(--t<1)return e.apply(this,arguments)}},x.before=function(t,e){var i;return function(){return--t>0&&(i=e.apply(this,arguments)),t<=1&&(e=null),i}},x.once=x.partial(x.before,2);var R=!{toString:null}.propertyIsEnumerable("toString"),j=["valueOf","isPrototypeOf","toString","propertyIsEnumerable","hasOwnProperty","toLocaleString"];x.keys=function(t){if(!x.isObject(t))return[];if(y)return y(t);var e=[];for(var i in t)x.has(t,i)&&e.push(i);return R&&a(t,e),e},x.allKeys=function(t){if(!x.isObject(t))return[];var e=[];for(var i in t)e.push(i);return R&&a(t,e),e},x.values=function(t){for(var e=x.keys(t),i=e.length,n=Array(i),r=0;r":">",'"':""","'":"'","`":"`"},I=x.invert(H),$=function(t){var e=function(e){return t[e]},i="(?:"+x.keys(t).join("|")+")",n=RegExp(i),r=RegExp(i,"g");return function(t){return t=null==t?"":""+t,n.test(t)?t.replace(r,e):t}};x.escape=$(H),x.unescape=$(I),x.result=function(t,e,i){var n=null==t?void 0:t[e];return void 0===n&&(n=i),x.isFunction(n)?n.call(t):n};var F=0;x.uniqueId=function(t){var e=++F+"";return t?t+e:e},x.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var q=/(.)^/,W={"'":"'","\\":"\\","\r":"r","\n":"n","\u2028":"u2028","\u2029":"u2029"},z=/\\|'|\r|\n|\u2028|\u2029/g,B=function(t){return"\\"+W[t]};x.template=function(t,e,i){!e&&i&&(e=i),e=x.defaults({},e,x.templateSettings);var n=RegExp([(e.escape||q).source,(e.interpolate||q).source,(e.evaluate||q).source].join("|")+"|$","g"),r=0,s="__p+='";t.replace(n,function(e,i,n,o,a){return s+=t.slice(r,a).replace(z,B),r=a+e.length,i?s+="'+\n((__t=("+i+"))==null?'':_.escape(__t))+\n'":n?s+="'+\n((__t=("+n+"))==null?'':__t)+\n'":o&&(s+="';\n"+o+"\n__p+='"),e}),s+="';\n",e.variable||(s="with(obj||{}){\n"+s+"}\n"),s="var __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'');};\n"+s+"return __p;\n";try{var o=new Function(e.variable||"obj","_",s)}catch(a){throw a.source=s,a}var l=function(t){return o.call(this,t,x)},h=e.variable||"obj";return l.source="function("+h+"){\n"+s+"}",l},x.chain=function(t){var e=x(t);return e._chain=!0,e};var U=function(t,e){return t._chain?x(e).chain():e};x.mixin=function(t){x.each(x.functions(t),function(e){var i=x[e]=t[e];x.prototype[e]=function(){var t=[this._wrapped];return p.apply(t,arguments),U(this,i.apply(x,t))}})},x.mixin(x),x.each(["pop","push","reverse","shift","sort","splice","unshift"],function(t){var e=c[t];x.prototype[t]=function(){var i=this._wrapped;return e.apply(i,arguments),"shift"!==t&&"splice"!==t||0!==i.length||delete i[0],U(this,i)}}),x.each(["concat","join","slice"],function(t){var e=c[t];x.prototype[t]=function(){return U(this,e.apply(this._wrapped,arguments))}}),x.prototype.value=function(){return this._wrapped},x.prototype.valueOf=x.prototype.toJSON=x.prototype.value,x.prototype.toString=function(){return""+this._wrapped},n=[],r=function(){return x}.apply(e,n),!(void 0!==r&&(t.exports=r))}).call(this)},function(t,e,i){var n,r;(function(s){!function(o){var a="object"==typeof self&&self.self===self&&self||"object"==typeof s&&s.global===s&&s;n=[i(2),i(1),e],r=function(t,e,i){a.Backbone=o(a,i,t,e)}.apply(e,n),!(void 0!==r&&(t.exports=r))}(function(t,e,i,n){var r=t.Backbone,s=Array.prototype.slice;e.VERSION="1.3.3",e.$=n,e.noConflict=function(){return t.Backbone=r,this},e.emulateHTTP=!1,e.emulateJSON=!1;var o=function(t,e,n){switch(t){case 1:return function(){return i[e](this[n])};case 2:return function(t){return i[e](this[n],t)};case 3:return function(t,r){return i[e](this[n],l(t,this),r)};case 4:return function(t,r,s){return i[e](this[n],l(t,this),r,s)};default:return function(){var t=s.call(arguments);return t.unshift(this[n]),i[e].apply(i,t)}}},a=function(t,e,n){i.each(e,function(e,r){i[r]&&(t.prototype[r]=o(e,r,n))})},l=function(t,e){return i.isFunction(t)?t:i.isObject(t)&&!e._isModel(t)?h(t):i.isString(t)?function(e){ +return e.get(t)}:t},h=function(t){var e=i.matches(t);return function(t){return e(t.attributes)}},c=e.Events={},u=/\s+/,d=function(t,e,n,r,s){var o,a=0;if(n&&"object"==typeof n){void 0!==r&&"context"in s&&void 0===s.context&&(s.context=r);for(o=i.keys(n);athis.length&&(r=this.length),r<0&&(r+=this.length+1);var s,o,a=[],l=[],h=[],c=[],u={},d=e.add,p=e.merge,f=e.remove,g=!1,m=this.comparator&&null==r&&e.sort!==!1,v=i.isString(this.comparator)?this.comparator:null;for(o=0;o7),this._useHashChange=this._wantsHashChange&&this._hasHashChange,this._wantsPushState=!!this.options.pushState,this._hasPushState=!(!this.history||!this.history.pushState),this._usePushState=this._wantsPushState&&this._hasPushState,this.fragment=this.getFragment(),this.root=("/"+this.root+"/").replace(H,"/"),this._wantsHashChange&&this._wantsPushState){if(!this._hasPushState&&!this.atRoot()){var e=this.root.slice(0,-1)||"/";return this.location.replace(e+"#"+this.getPath()),!0}this._hasPushState&&this.atRoot()&&this.navigate(this.getHash(),{replace:!0})}if(!this._hasHashChange&&this._wantsHashChange&&!this._usePushState){this.iframe=document.createElement("iframe"),this.iframe.src="javascript:0",this.iframe.style.display="none",this.iframe.tabIndex=-1;var n=document.body,r=n.insertBefore(this.iframe,n.firstChild).contentWindow;r.document.open(),r.document.close(),r.location.hash="#"+this.fragment}var s=window.addEventListener||function(t,e){return attachEvent("on"+t,e)};if(this._usePushState?s("popstate",this.checkUrl,!1):this._useHashChange&&!this.iframe?s("hashchange",this.checkUrl,!1):this._wantsHashChange&&(this._checkUrlInterval=setInterval(this.checkUrl,this.interval)),!this.options.silent)return this.loadUrl()},stop:function(){var t=window.removeEventListener||function(t,e){return detachEvent("on"+t,e)};this._usePushState?t("popstate",this.checkUrl,!1):this._useHashChange&&!this.iframe&&t("hashchange",this.checkUrl,!1),this.iframe&&(document.body.removeChild(this.iframe),this.iframe=null),this._checkUrlInterval&&clearInterval(this._checkUrlInterval),j.started=!1},route:function(t,e){this.handlers.unshift({route:t,callback:e})},checkUrl:function(t){var e=this.getFragment();return e===this.fragment&&this.iframe&&(e=this.getHash(this.iframe.contentWindow)),e!==this.fragment&&(this.iframe&&this.navigate(e),void this.loadUrl())},loadUrl:function(t){return!!this.matchRoot()&&(t=this.fragment=this.getFragment(t),i.some(this.handlers,function(e){if(e.route.test(t))return e.callback(t),!0}))},navigate:function(t,e){if(!j.started)return!1;e&&e!==!0||(e={trigger:!!e}),t=this.getFragment(t||"");var i=this.root;""!==t&&"?"!==t.charAt(0)||(i=i.slice(0,-1)||"/");var n=i+t;if(t=this.decodeFragment(t.replace(I,"")),this.fragment!==t){if(this.fragment=t,this._usePushState)this.history[e.replace?"replaceState":"pushState"]({},document.title,n);else{if(!this._wantsHashChange)return this.location.assign(n);if(this._updateHash(this.location,t,e.replace),this.iframe&&t!==this.getHash(this.iframe.contentWindow)){var r=this.iframe.contentWindow;e.replace||(r.document.open(),r.document.close()),this._updateHash(r.location,t,e.replace)}}return e.trigger?this.loadUrl(t):void 0}},_updateHash:function(t,e,i){if(i){var n=t.href.replace(/(javascript:|#).*$/,"");t.replace(n+"#"+e)}else t.hash="#"+e}}),e.history=new j;var $=function(t,e){var n,r=this;return n=t&&i.has(t,"constructor")?t.constructor:function(){return r.apply(this,arguments)},i.extend(n,r,e),n.prototype=i.create(r.prototype,t),n.prototype.constructor=n,n.__super__=r.prototype,n};b.extend=_.extend=O.extend=T.extend=j.extend=$;var F=function(){throw new Error('A "url" property or function must be specified')},q=function(t,e){var i=e.error;e.error=function(n){i&&i.call(e.context,t,n,e),t.trigger("error",t,n,e)}};return e})}).call(e,function(){return this}())},function(t,e,i){var n,r;(function(i,s,o){n=[],r=function(){function t(t){return'
                                            ")}),a.show({backdrop:!0})}var s=t,o=function(t){this.$overlay=t.overlay,this.$dialog=t.dialog,this.$header=this.$dialog.find(".modal-header"),this.$body=this.$dialog.find(".modal-body"),this.$footer=this.$dialog.find(".modal-footer"),this.$backdrop=t.backdrop,this.$header.find(".close").on("click",s.proxy(this.hide,this))};s.extend(o.prototype,{setContent:function(t){this.$header.hide(),t.title&&(this.$header.find(".title").html(t.title),this.$header.show()),t.closeButton?(this.$header.find(".close").show(),this.$header.show()):this.$header.find(".close").hide(),this.$footer.hide();var e=this.$footer.find(".buttons").html("");t.buttons&&(s.each(t.buttons,function(t,i){e.append(s(" ").text(t).click(i)).append(" ")}),this.$footer.show());var i=this.$footer.find(".extra_buttons").html("");t.extra_buttons&&(s.each(t.extra_buttons,function(t,e){i.append(s("").text(t).click(e)).append(" ")}),this.$footer.show());var n=t.body;"progress"==n&&(n=s("
                                            ")),this.$body.html(n)},show:function(t,e){this.$dialog.is(":visible")||(t.backdrop?this.$backdrop.addClass("in"):this.$backdrop.removeClass("in"),this.$overlay.show(),this.$dialog.show(),this.$overlay.addClass("in"),this.$body.css("min-width",this.$body.width()),this.$body.css("max-height",s(window).height()-this.$footer.outerHeight()-this.$header.outerHeight()-parseInt(this.$dialog.css("padding-top"),10)-parseInt(this.$dialog.css("padding-bottom"),10))),e&&e()},hide:function(){var t=this;t.$dialog.fadeOut(function(){t.$overlay.hide(),t.$backdrop.removeClass("in"),t.$body.children().remove(),t.$body.css("min-width",void 0)})}});var a;return s(function(){a=new o({overlay:s("#top-modal"),dialog:s("#top-modal-dialog"),backdrop:s("#top-modal-backdrop")})}),{Modal:o,hide_modal:e,show_modal:i,show_message:n,show_in_overlay:r}}.apply(e,n),!(void 0!==r&&(t.exports=r))},function(t,e,i){var n,r;(function(s,o,a){n=[i(92),i(10),i(8),i(6)],r=function(t,e,i,n){var r=s.View.extend(n.LoggableMixin).extend({_logNamespace:"layout",el:"body",className:"full-content",_panelIds:["left","center","right"],defaultOptions:{message_box_visible:!1,message_box_content:"",message_box_class:"info",show_inactivity_warning:!1,inactivity_box_content:""},initialize:function(e){this.log(this+".initialize:",e),o.extend(this,o.pick(e,this._panelIds)),this.options=o.defaults(o.omit(e.config,this._panelIds),this.defaultOptions),Galaxy.modal=this.modal=new i.View,this.masthead=new t.View(this.options),this.$el.attr("scroll","no"),this.$el.html(this._template()),this.$el.append(this.masthead.frame.$el),this.$("#masthead").replaceWith(this.masthead.$el),this.$el.append(this.modal.$el),this.$messagebox=this.$("#messagebox"),this.$inactivebox=this.$("#inactivebox")},render:function(){return a(".select2-hidden-accessible").remove(),this.log(this+".render:"),this.masthead.render(),this.renderMessageBox(),this.renderInactivityBox(),this.renderPanels(),this._checkCommunicationServerOnline(),this},renderMessageBox:function(){if(this.options.message_box_visible){var t=this.options.message_box_content||"",e=this.options.message_box_class||"info";this.$el.addClass("has-message-box"),this.$messagebox.attr("class","panel-"+e+"-message").html(t).toggle(!!t).show()}else this.$el.removeClass("has-message-box"),this.$messagebox.hide();return this},renderInactivityBox:function(){if(this.options.show_inactivity_warning){var t=this.options.inactivity_box_content||"",e=a("").attr("href",Galaxy.root+"user/resend_verification").text("Resend verification");this.$el.addClass("has-inactivity-box"),this.$inactivebox.html(t+" ").append(e).toggle(!!t).show()}else this.$el.removeClass("has-inactivity-box"),this.$inactivebox.hide();return this},renderPanels:function(){var t=this;return this._panelIds.forEach(function(e){o.has(t,e)&&(t[e].setElement("#"+e),t[e].render())}),this.left||this.center.$el.css("left",0),this.right||this.center.$el.css("right",0),this},_template:function(){return['
                                            ','
                                            ','
                                            ','
                                            ','
                                            ',this.left?'
                                            ':"",this.center?'
                                            ':"",this.right?'",'
                                            '].join("")},hideSidePanels:function(){this.left&&this.left.hide(),this.right&&this.right.hide()},toString:function(){return"PageLayoutView"},_checkCommunicationServerOnline:function(){var t=window.Galaxy.config.communication_server_host,e=window.Galaxy.config.communication_server_port,i=a("#show-chat-online");void 0!==window.Galaxy.user.attributes.preferences&&"1"===window.Galaxy.user.attributes.preferences.communication_server?a.ajax({url:t+":"+e}).success(function(t){null!==window.Galaxy.user.id&&"hidden"===i.css("visibility")&&i.css("visibility","visible")}).error(function(t){i.css("visibility","hidden")}):i.css("visibility","hidden")}});return{PageLayoutView:r}}.apply(e,n),!(void 0!==r&&(t.exports=r))}).call(e,i(3),i(2),i(1))},function(t,e,i){(function(t){!function(t,e){var i,n;return n=e.document,i=function(){function i(i){var n;try{n=e.localStorage}catch(r){n=!1}this._options=t.extend({name:"tour",steps:[],container:"body",autoscroll:!0,keyboard:!0,storage:n,debug:!1,backdrop:!1,backdropContainer:"body",backdropPadding:0,redirect:!0,orphan:!1,duration:!1,delay:!1,basePath:"",template:'',afterSetState:function(t,e){},afterGetState:function(t,e){},afterRemoveState:function(t){},onStart:function(t){},onEnd:function(t){},onShow:function(t){},onShown:function(t){},onHide:function(t){},onHidden:function(t){},onNext:function(t){},onPrev:function(t){},onPause:function(t,e){},onResume:function(t,e){},onRedirectError:function(t){}},i),this._force=!1,this._inited=!1,this._current=null,this.backdrop={overlay:null,$element:null,$background:null,backgroundShown:!1,overlayElementShown:!1}}return i.prototype.addSteps=function(t){var e,i,n;for(i=0,n=t.length;i

                                            ",next:e===this._options.steps.length-1?-1:e+1,prev:e-1,animation:!0,container:this._options.container,autoscroll:this._options.autoscroll,backdrop:this._options.backdrop,backdropContainer:this._options.backdropContainer,backdropPadding:this._options.backdropPadding,redirect:this._options.redirect,reflexElement:this._options.steps[e].element,orphan:this._options.orphan,duration:this._options.duration,delay:this._options.delay,template:this._options.template,onShow:this._options.onShow,onShown:this._options.onShown,onHide:this._options.onHide,onHidden:this._options.onHidden,onNext:this._options.onNext,onPrev:this._options.onPrev,onPause:this._options.onPause,onResume:this._options.onResume,onRedirectError:this._options.onRedirectError},this._options.steps[e])},i.prototype.init=function(t){return this._force=t,this.ended()?(this._debug("Tour ended, init prevented."),this):(this.setCurrentStep(),this._initMouseNavigation(),this._initKeyboardNavigation(),this._onResize(function(t){return function(){return t.showStep(t._current)}}(this)),null!==this._current&&this.showStep(this._current),this._inited=!0,this)},i.prototype.start=function(t){var e;return null==t&&(t=!1),this._inited||this.init(t),null===this._current&&(e=this._makePromise(null!=this._options.onStart?this._options.onStart(this):void 0),this._callOnPromiseDone(e,this.showStep,0)),this},i.prototype.next=function(){var t;return t=this.hideStep(this._current),this._callOnPromiseDone(t,this._showNextStep)},i.prototype.prev=function(){var t;return t=this.hideStep(this._current),this._callOnPromiseDone(t,this._showPrevStep)},i.prototype.goTo=function(t){var e;return e=this.hideStep(this._current),this._callOnPromiseDone(e,this.showStep,t)},i.prototype.end=function(){var i,r;return i=function(i){return function(r){if(t(n).off("click.tour-"+i._options.name),t(n).off("keyup.tour-"+i._options.name),t(e).off("resize.tour-"+i._options.name),i._setState("end","yes"),i._inited=!1,i._force=!1,i._clearTimer(),null!=i._options.onEnd)return i._options.onEnd(i)}}(this),r=this.hideStep(this._current),this._callOnPromiseDone(r,i)},i.prototype.ended=function(){return!this._force&&!!this._getState("end")},i.prototype.restart=function(){return this._removeState("current_step"),this._removeState("end"),this._removeState("redirect_to"),this.start()},i.prototype.pause=function(){var t;return t=this.getStep(this._current),t&&t.duration?(this._paused=!0,this._duration-=(new Date).getTime()-this._start,e.clearTimeout(this._timer),this._debug("Paused/Stopped step "+(this._current+1)+" timer ("+this._duration+" remaining)."),null!=t.onPause?t.onPause(this,this._duration):void 0):this},i.prototype.resume=function(){var t;return t=this.getStep(this._current),t&&t.duration?(this._paused=!1,this._start=(new Date).getTime(),this._duration=this._duration||t.duration,this._timer=e.setTimeout(function(t){ +return function(){return t._isLast()?t.next():t.end()}}(this),this._duration),this._debug("Started step "+(this._current+1)+" timer with duration "+this._duration),null!=t.onResume&&this._duration!==t.duration?t.onResume(this,this._duration):void 0):this},i.prototype.hideStep=function(e){var i,n,r;if(r=this.getStep(e))return this._clearTimer(),n=this._makePromise(null!=r.onHide?r.onHide(this,e):void 0),i=function(i){return function(n){var s;if(s=t(r.element),s.data("bs.popover")||s.data("popover")||(s=t("body")),s.popover("destroy").removeClass("tour-"+i._options.name+"-element tour-"+i._options.name+"-"+e+"-element"),s.removeData("bs.popover"),r.reflex&&t(r.reflexElement).removeClass("tour-step-element-reflex").off(""+i._reflexEvent(r.reflex)+".tour-"+i._options.name),r.backdrop&&i._hideBackdrop(),null!=r.onHidden)return r.onHidden(i)}}(this),this._callOnPromiseDone(n,i),n},i.prototype.showStep=function(t){var i,r,s,o;return this.ended()?(this._debug("Tour ended, showStep prevented."),this):(o=this.getStep(t))?(s=t").parent().html()},i.prototype._reflexEvent=function(t){return"[object Boolean]"==={}.toString.call(t)?"click":t},i.prototype._reposition=function(e,i){var r,s,o,a,l,h,c;if(a=e[0].offsetWidth,s=e[0].offsetHeight,c=e.offset(),l=c.left,h=c.top,r=t(n).outerHeight()-c.top-e.outerHeight(),r<0&&(c.top=c.top+r),o=t("html").outerWidth()-c.left-e.outerWidth(),o<0&&(c.left=c.left+o),c.top<0&&(c.top=0),c.left<0&&(c.left=0),e.offset(c),"bottom"===i.placement||"top"===i.placement){if(l!==c.left)return this._replaceArrow(e,2*(c.left-l),a,"left")}else if(h!==c.top)return this._replaceArrow(e,2*(c.top-h),s,"top")},i.prototype._center=function(i){return i.css("top",t(e).outerHeight()/2-i.outerHeight()/2)},i.prototype._replaceArrow=function(t,e,i,n){return t.find(".arrow").css(n,e?50*(1-e/i)+"%":"")},i.prototype._scrollIntoView=function(i,n){var r,s,o,a,l,h;return r=t(i),r.length?(s=t(e),a=r.offset().top,h=s.height(),l=Math.max(0,a-h/2),this._debug("Scroll into view. ScrollTop: "+l+". Element offset: "+a+". Window height: "+h+"."),o=0,t("body, html").stop(!0,!0).animate({scrollTop:Math.ceil(l)},function(t){return function(){if(2===++o)return n(),t._debug("Scroll into view.\nAnimation end element offset: "+r.offset().top+".\nWindow height: "+s.height()+".")}}(this))):n()},i.prototype._onResize=function(i,n){return t(e).on("resize.tour-"+this._options.name,function(){return clearTimeout(n),n=setTimeout(i,100)})},i.prototype._initMouseNavigation=function(){var e;return e=this,t(n).off("click.tour-"+this._options.name,".popover.tour-"+this._options.name+" *[data-role='prev']").off("click.tour-"+this._options.name,".popover.tour-"+this._options.name+" *[data-role='next']").off("click.tour-"+this._options.name,".popover.tour-"+this._options.name+" *[data-role='end']").off("click.tour-"+this._options.name,".popover.tour-"+this._options.name+" *[data-role='pause-resume']").on("click.tour-"+this._options.name,".popover.tour-"+this._options.name+" *[data-role='next']",function(t){return function(e){return e.preventDefault(),t.next()}}(this)).on("click.tour-"+this._options.name,".popover.tour-"+this._options.name+" *[data-role='prev']",function(t){return function(e){return e.preventDefault(),t.prev()}}(this)).on("click.tour-"+this._options.name,".popover.tour-"+this._options.name+" *[data-role='end']",function(t){return function(e){return e.preventDefault(),t.end()}}(this)).on("click.tour-"+this._options.name,".popover.tour-"+this._options.name+" *[data-role='pause-resume']",function(i){var n;return i.preventDefault(),n=t(this),n.text(e._paused?n.data("pause-text"):n.data("resume-text")),e._paused?e.resume():e.pause()})},i.prototype._initKeyboardNavigation=function(){if(this._options.keyboard)return t(n).on("keyup.tour-"+this._options.name,function(t){return function(e){if(e.which)switch(e.which){case 39:return e.preventDefault(),t._isLast()?t.next():t.end();case 37:if(e.preventDefault(),t._current>0)return t.prev();break;case 27:return e.preventDefault(),t.end()}}}(this))},i.prototype._makePromise=function(e){return e&&t.isFunction(e.then)?e:null},i.prototype._callOnPromiseDone=function(t,e,i){return t?t.then(function(t){return function(n){return e.call(t,i)}}(this)):e.call(this,i)},i.prototype._showBackdrop=function(e){if(!this.backdrop.backgroundShown)return this.backdrop=t("
                                            ",{"class":"tour-backdrop"}),this.backdrop.backgroundShown=!0,t(e.backdropContainer).append(this.backdrop)},i.prototype._hideBackdrop=function(){return this._hideOverlayElement(),this._hideBackground()},i.prototype._hideBackground=function(){if(this.backdrop)return this.backdrop.remove(),this.backdrop.overlay=null,this.backdrop.backgroundShown=!1},i.prototype._showOverlayElement=function(e,i){var n,r;if(n=t(e.element),n&&0!==n.length&&(!this.backdrop.overlayElementShown||i))return this.backdrop.overlayElementShown||(this.backdrop.$element=n.addClass("tour-step-backdrop"),this.backdrop.$background=t("
                                            ",{"class":"tour-step-background"}),this.backdrop.$background.appendTo(e.backdropContainer),this.backdrop.overlayElementShown=!0),r={width:n.innerWidth(),height:n.innerHeight(),offset:n.offset()},e.backdropPadding&&(r=this._applyBackdropPadding(e.backdropPadding,r)),this.backdrop.$background.width(r.width).height(r.height).offset(r.offset)},i.prototype._hideOverlayElement=function(){if(this.backdrop.overlayElementShown)return this.backdrop.$element.removeClass("tour-step-backdrop"),this.backdrop.$background.remove(),this.backdrop.$element=null,this.backdrop.$background=null,this.backdrop.overlayElementShown=!1},i.prototype._applyBackdropPadding=function(t,e){return"object"==typeof t?(null==t.top&&(t.top=0),null==t.right&&(t.right=0),null==t.bottom&&(t.bottom=0),null==t.left&&(t.left=0),e.offset.top=e.offset.top-t.top,e.offset.left=e.offset.left-t.left,e.width=e.width+t.left+t.right,e.height=e.height+t.top+t.bottom):(e.offset.top=e.offset.top-t,e.offset.left=e.offset.left-t,e.width=e.width+2*t,e.height=e.height+2*t),e},i.prototype._clearTimer=function(){return e.clearTimeout(this._timer),this._timer=null,this._duration=null},i.prototype._getProtocol=function(t){return t=t.split("://"),t.length>1?t[0]:"http"},i.prototype._getHost=function(t){return t=t.split("//"),t=t.length>1?t[1]:t[0],t.split("/")[0]},i.prototype._getPath=function(t){return t.replace(/\/?$/,"").split("?")[0].split("#")[0]},i.prototype._getQuery=function(t){return this._getParams(t,"?")},i.prototype._getHash=function(t){return this._getParams(t,"#")},i.prototype._getParams=function(t,e){var i,n,r,s,o;if(n=t.split(e),1===n.length)return{};for(n=n[1].split("&"),r={},s=0,o=n.length;s").outerWidth(1).jquery||t.each(["Width","Height"],function(i,n){function r(e,i,n,r){return t.each(s,function(){i-=parseFloat(t.css(e,"padding"+this))||0,n&&(i-=parseFloat(t.css(e,"border"+this+"Width"))||0),r&&(i-=parseFloat(t.css(e,"margin"+this))||0)}),i}var s="Width"===n?["Left","Right"]:["Top","Bottom"],o=n.toLowerCase(),a={innerWidth:t.fn.innerWidth,innerHeight:t.fn.innerHeight,outerWidth:t.fn.outerWidth,outerHeight:t.fn.outerHeight};t.fn["inner"+n]=function(i){return i===e?a["inner"+n].call(this):this.each(function(){t(this).css(o,r(this,i)+"px")})},t.fn["outer"+n]=function(e,i){return"number"!=typeof e?a["outer"+n].call(this,e):this.each(function(){t(this).css(o,r(this,e,!0,i)+"px")})}}),t.extend(t.expr[":"],{data:t.expr.createPseudo?t.expr.createPseudo(function(e){return function(i){return!!t.data(i,e)}}):function(e,i,n){return!!t.data(e,n[3])},focusable:function(e){return i(e,!isNaN(t.attr(e,"tabindex")))},tabbable:function(e){var n=t.attr(e,"tabindex"),r=isNaN(n);return(r||n>=0)&&i(e,!r)}}),t(function(){var e=document.body,i=e.appendChild(i=document.createElement("div"));i.offsetHeight,t.extend(i.style,{minHeight:"100px",height:"auto",padding:0,borderWidth:0}),t.support.minHeight=100===i.offsetHeight,t.support.selectstart="onselectstart"in i,e.removeChild(i).style.display="none"}),function(){var e=/msie ([\w.]+)/.exec(navigator.userAgent.toLowerCase())||[];t.ui.ie=e.length?!0:!1,t.ui.ie6=6===parseFloat(e[1],10)}(),t.fn.extend({disableSelection:function(){return this.bind((t.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection",function(t){t.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}}),t.extend(t.ui,{plugin:{add:function(e,i,n){var r,s=t.ui[e].prototype;for(r in n)s.plugins[r]=s.plugins[r]||[],s.plugins[r].push([i,n[r]])},call:function(t,e,i){var n,r=t.plugins[e];if(r&&t.element[0].parentNode&&11!==t.element[0].parentNode.nodeType)for(n=0;n0?!0:(e[n]=1,r=e[n]>0,e[n]=0,r)},isOverAxis:function(t,e,i){return t>e&&e+i>t},isOver:function(e,i,n,r,s,o){return t.ui.isOverAxis(e,n,s)&&t.ui.isOverAxis(i,r,o)}}))}(t),function(t,e){var i=0,n=Array.prototype.slice,r=t.cleanData;t.cleanData=function(e){for(var i,n=0;null!=(i=e[n]);n++)try{t(i).triggerHandler("remove")}catch(s){}r(e)},t.widget=function(e,i,n){var r,s,o,a,l=e.split(".")[0];e=e.split(".")[1],r=l+"-"+e,n||(n=i,i=t.Widget),t.expr[":"][r.toLowerCase()]=function(e){return!!t.data(e,r)},t[l]=t[l]||{},s=t[l][e],o=t[l][e]=function(t,e){return this._createWidget?void(arguments.length&&this._createWidget(t,e)):new o(t,e)},t.extend(o,s,{version:n.version,_proto:t.extend({},n),_childConstructors:[]}),a=new i,a.options=t.widget.extend({},a.options),t.each(n,function(e,r){t.isFunction(r)&&(n[e]=function(){var t=function(){return i.prototype[e].apply(this,arguments)},n=function(t){return i.prototype[e].apply(this,t)};return function(){var e,i=this._super,s=this._superApply;return this._super=t,this._superApply=n,e=r.apply(this,arguments),this._super=i,this._superApply=s,e}}())}),o.prototype=t.widget.extend(a,{widgetEventPrefix:a.widgetEventPrefix||e},n,{constructor:o,namespace:l,widgetName:e,widgetBaseClass:r,widgetFullName:r}),s?(t.each(s._childConstructors,function(e,i){var n=i.prototype;t.widget(n.namespace+"."+n.widgetName,o,i._proto)}),delete s._childConstructors):i._childConstructors.push(o),t.widget.bridge(e,o)},t.widget.extend=function(i){for(var r,s,o=n.call(arguments,1),a=0,l=o.length;l>a;a++)for(r in o[a])s=o[a][r],o[a].hasOwnProperty(r)&&s!==e&&(t.isPlainObject(s)?i[r]=t.isPlainObject(i[r])?t.widget.extend({},i[r],s):t.widget.extend({},s):i[r]=s);return i},t.widget.bridge=function(i,r){var s=r.prototype.widgetFullName;t.fn[i]=function(o){var a="string"==typeof o,l=n.call(arguments,1),u=this;return o=!a&&l.length?t.widget.extend.apply(null,[o].concat(l)):o,a?this.each(function(){var n,r=t.data(this,s);return r?t.isFunction(r[o])&&"_"!==o.charAt(0)?(n=r[o].apply(r,l),n!==r&&n!==e?(u=n&&n.jquery?u.pushStack(n.get()):n,!1):void 0):t.error("no such method '"+o+"' for "+i+" widget instance"):t.error("cannot call methods on "+i+" prior to initialization; attempted to call method '"+o+"'")}):this.each(function(){var e=t.data(this,s);e?e.option(o||{})._init():new r(o,this)}),u}},t.Widget=function(){},t.Widget._childConstructors=[],t.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",defaultElement:"
                                            ",options:{disabled:!1,create:null},_createWidget:function(e,n){n=t(n||this.defaultElement||this)[0],this.element=t(n),this.uuid=i++,this.eventNamespace="."+this.widgetName+this.uuid,this.options=t.widget.extend({},this.options,this._getCreateOptions(),e),this.bindings=t(),this.hoverable=t(),this.focusable=t(),n!==this&&(t.data(n,this.widgetName,this),t.data(n,this.widgetFullName,this),this._on(this.element,{remove:function(t){t.target===n&&this.destroy()}}),this.document=t(n.style?n.ownerDocument:n.document||n),this.window=t(this.document[0].defaultView||this.document[0].parentWindow)),this._create(),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:t.noop,_getCreateEventData:t.noop,_create:t.noop,_init:t.noop,destroy:function(){this._destroy(),this.element.unbind(this.eventNamespace).removeData(this.widgetName).removeData(this.widgetFullName).removeData(t.camelCase(this.widgetFullName)),this.widget().unbind(this.eventNamespace).removeAttr("aria-disabled").removeClass(this.widgetFullName+"-disabled ui-state-disabled"),this.bindings.unbind(this.eventNamespace),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")},_destroy:t.noop,widget:function(){return this.element},option:function(i,n){var r,s,o,a=i;if(0===arguments.length)return t.widget.extend({},this.options);if("string"==typeof i)if(a={},r=i.split("."),i=r.shift(),r.length){for(s=a[i]=t.widget.extend({},this.options[i]),o=0;o=9||e.button?this._mouseStarted?(this._mouseDrag(e),e.preventDefault()):(this._mouseDistanceMet(e)&&this._mouseDelayMet(e)&&(this._mouseStarted=this._mouseStart(this._mouseDownEvent,e)!==!1,this._mouseStarted?this._mouseDrag(e):this._mouseUp(e)),!this._mouseStarted):this._mouseUp(e)},_mouseUp:function(e){return t(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate),this._mouseStarted&&(this._mouseStarted=!1,e.target===this._mouseDownEvent.target&&t.data(e.target,this.widgetName+".preventClickEvent",!0),this._mouseStop(e)),!1},_mouseDistanceMet:function(t){return Math.max(Math.abs(this._mouseDownEvent.pageX-t.pageX),Math.abs(this._mouseDownEvent.pageY-t.pageY))>=this.options.distance},_mouseDelayMet:function(t){return this.mouseDelayMet},_mouseStart:function(t){},_mouseDrag:function(t){},_mouseStop:function(t){},_mouseCapture:function(t){return!0}})}(t),function(e,i){function n(t,e,i){return[parseInt(t[0],10)*(p.test(t[0])?e/100:1),parseInt(t[1],10)*(p.test(t[1])?i/100:1)]}function r(t,i){return parseInt(e.css(t,i),10)||0}e.ui=e.ui||{};var s,o=Math.max,a=Math.abs,l=Math.round,u=/left|center|right/,h=/top|center|bottom/,c=/[\+\-]\d+%?/,d=/^\w+/,p=/%$/,f=e.fn.position;e.position={scrollbarWidth:function(){if(s!==i)return s;var t,n,r=e("
                                            "),o=r.children()[0];return e("body").append(r),t=o.offsetWidth,r.css("overflow","scroll"),n=o.offsetWidth,t===n&&(n=r[0].clientWidth),r.remove(),s=t-n},getScrollInfo:function(t){var i=t.isWindow?"":t.element.css("overflow-x"),n=t.isWindow?"":t.element.css("overflow-y"),r="scroll"===i||"auto"===i&&t.widthn?"left":i>0?"right":"center",vertical:0>l?"top":r>0?"bottom":"middle"};d>s&&a(i+n)p&&a(r+l)o(a(r),a(l))?u.important="horizontal":u.important="vertical",t.using.call(this,e,u)}),c.offset(e.extend(T,{using:h}))})},e.ui.position={fit:{left:function(t,e){var i,n=e.within,r=n.isWindow?n.scrollLeft:n.offset.left,s=n.width,a=t.left-e.collisionPosition.marginLeft,l=r-a,u=a+e.collisionWidth-s-r;e.collisionWidth>s?l>0&&0>=u?(i=t.left+l+e.collisionWidth-s-r,t.left+=l-i):u>0&&0>=l?t.left=r:l>u?t.left=r+s-e.collisionWidth:t.left=r:l>0?t.left+=l:u>0?t.left-=u:t.left=o(t.left-a,t.left)},top:function(t,e){var i,n=e.within,r=n.isWindow?n.scrollTop:n.offset.top,s=e.within.height,a=t.top-e.collisionPosition.marginTop,l=r-a,u=a+e.collisionHeight-s-r;e.collisionHeight>s?l>0&&0>=u?(i=t.top+l+e.collisionHeight-s-r,t.top+=l-i):u>0&&0>=l?t.top=r:l>u?t.top=r+s-e.collisionHeight:t.top=r:l>0?t.top+=l:u>0?t.top-=u:t.top=o(t.top-a,t.top)}},flip:{left:function(t,e){var i,n,r=e.within,s=r.offset.left+r.scrollLeft,o=r.width,l=r.isWindow?r.scrollLeft:r.offset.left,u=t.left-e.collisionPosition.marginLeft,h=u-l,c=u+e.collisionWidth-o-l,d="left"===e.my[0]?-e.elemWidth:"right"===e.my[0]?e.elemWidth:0,p="left"===e.at[0]?e.targetWidth:"right"===e.at[0]?-e.targetWidth:0,f=-2*e.offset[0];0>h?(i=t.left+d+p+f+e.collisionWidth-o-s,(0>i||i0&&(n=t.left-e.collisionPosition.marginLeft+d+p+f-l,(n>0||a(n)h?(n=t.top+p+f+g+e.collisionHeight-o-s,t.top+p+f+g>h&&(0>n||n0&&(i=t.top-e.collisionPosition.marginTop+p+f+g-l,t.top+p+f+g>c&&(i>0||a(i)10&&11>r,t.innerHTML="",i.removeChild(t)}(),e.uiBackCompat!==!1&&function(t){var e=t.fn.position;t.fn.position=function(n){if(!n||!n.offset)return e.call(this,n);var r=n.offset.split(" "),s=n.at.split(" ");return 1===r.length&&(r[1]=r[0]),/^\d/.test(r[0])&&(r[0]="+"+r[0]),/^\d/.test(r[1])&&(r[1]="+"+r[1]),1===s.length&&(/left|center|right/.test(s[0])?s[1]="center":(s[1]=s[0],s[0]="center")),e.call(this,t.extend(n,{at:s[0]+r[0]+" "+s[1]+r[1],offset:i}))}}(t)}(t),function(t,e){var i=0;t.widget("ui.autocomplete",{version:"1.9.1",defaultElement:"",options:{appendTo:"body",autoFocus:!1,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null,change:null,close:null,focus:null,open:null,response:null,search:null,select:null},pending:0,_create:function(){var e,i,n;this.isMultiLine=this._isMultiLine(),this.valueMethod=this.element[this.element.is("input,textarea")?"val":"text"],this.isNewMenu=!0,this.element.addClass("ui-autocomplete-input").attr("autocomplete","off"),this._on(this.element,{keydown:function(r){if(this.element.prop("readOnly"))return e=!0,n=!0,i=!0,void 0;e=!1,n=!1,i=!1;var s=t.ui.keyCode;switch(r.keyCode){case s.PAGE_UP:e=!0,this._move("previousPage",r);break;case s.PAGE_DOWN:e=!0,this._move("nextPage",r);break;case s.UP:e=!0,this._keyEvent("previous",r);break;case s.DOWN:e=!0,this._keyEvent("next",r);break;case s.ENTER:case s.NUMPAD_ENTER:this.menu.active&&(e=!0,r.preventDefault(),this.menu.select(r));break;case s.TAB:this.menu.active&&this.menu.select(r);break;case s.ESCAPE:this.menu.element.is(":visible")&&(this._value(this.term),this.close(r),r.preventDefault());break;default:i=!0,this._searchTimeout(r)}},keypress:function(n){if(e)return e=!1,void n.preventDefault();if(!i){var r=t.ui.keyCode;switch(n.keyCode){case r.PAGE_UP:this._move("previousPage",n);break;case r.PAGE_DOWN:this._move("nextPage",n);break;case r.UP:this._keyEvent("previous",n);break;case r.DOWN:this._keyEvent("next",n)}}},input:function(t){return n?(n=!1,void t.preventDefault()):void this._searchTimeout(t)},focus:function(){this.selectedItem=null,this.previous=this._value()},blur:function(t){return this.cancelBlur?void delete this.cancelBlur:(clearTimeout(this.searching),this.close(t),this._change(t),void 0)}}),this._initSource(),this.menu=t("
                                            ").appendTo(this.element).addClass("ui-slider-range ui-widget-header"+("min"===r.range||"max"===r.range?" ui-slider-range-"+r.range:""))),n=r.values&&r.values.length||1,e=s.length;n>e;e++)a.push(o);this.handles=s.add(t(a.join("")).appendTo(this.element)),this.handle=this.handles.eq(0),this.handles.add(this.range).filter("a").click(function(t){t.preventDefault()}).mouseenter(function(){r.disabled||t(this).addClass("ui-state-hover")}).mouseleave(function(){t(this).removeClass("ui-state-hover")}).focus(function(){r.disabled?t(this).blur():(t(".ui-slider .ui-state-focus").removeClass("ui-state-focus"),t(this).addClass("ui-state-focus"))}).blur(function(){t(this).removeClass("ui-state-focus")}),this.handles.each(function(e){t(this).data("ui-slider-handle-index",e)}),this._on(this.handles,{keydown:function(e){var n,r,s,o,a=t(e.target).data("ui-slider-handle-index");switch(e.keyCode){case t.ui.keyCode.HOME:case t.ui.keyCode.END:case t.ui.keyCode.PAGE_UP:case t.ui.keyCode.PAGE_DOWN:case t.ui.keyCode.UP:case t.ui.keyCode.RIGHT:case t.ui.keyCode.DOWN:case t.ui.keyCode.LEFT:if(e.preventDefault(),!this._keySliding&&(this._keySliding=!0,t(e.target).addClass("ui-state-active"),n=this._start(e,a),n===!1))return}switch(o=this.options.step,r=s=this.options.values&&this.options.values.length?this.values(a):this.value(),e.keyCode){case t.ui.keyCode.HOME:s=this._valueMin();break;case t.ui.keyCode.END:s=this._valueMax();break;case t.ui.keyCode.PAGE_UP:s=this._trimAlignValue(r+(this._valueMax()-this._valueMin())/i);break;case t.ui.keyCode.PAGE_DOWN:s=this._trimAlignValue(r-(this._valueMax()-this._valueMin())/i);break;case t.ui.keyCode.UP:case t.ui.keyCode.RIGHT:if(r===this._valueMax())return;s=this._trimAlignValue(r+o);break;case t.ui.keyCode.DOWN:case t.ui.keyCode.LEFT:if(r===this._valueMin())return;s=this._trimAlignValue(r-o)}this._slide(e,a,s)},keyup:function(e){var i=t(e.target).data("ui-slider-handle-index");this._keySliding&&(this._keySliding=!1,this._stop(e,i),this._change(e,i),t(e.target).removeClass("ui-state-active"))}}),this._refreshValue(),this._animateOff=!1},_destroy:function(){this.handles.remove(),this.range.remove(),this.element.removeClass("ui-slider ui-slider-horizontal ui-slider-vertical ui-slider-disabled ui-widget ui-widget-content ui-corner-all"),this._mouseDestroy()},_mouseCapture:function(e){var i,n,r,s,o,a,l,u,h=this,c=this.options;return c.disabled?!1:(this.elementSize={width:this.element.outerWidth(),height:this.element.outerHeight()},this.elementOffset=this.element.offset(),i={x:e.pageX,y:e.pageY},n=this._normValueFromMouse(i),r=this._valueMax()-this._valueMin()+1,this.handles.each(function(e){var i=Math.abs(n-h.values(e));r>i&&(r=i,s=t(this),o=e)}),c.range===!0&&this.values(1)===c.min&&(o+=1,s=t(this.handles[o])),a=this._start(e,o),a===!1?!1:(this._mouseSliding=!0,this._handleIndex=o,s.addClass("ui-state-active").focus(),l=s.offset(),u=!t(e.target).parents().andSelf().is(".ui-slider-handle"),this._clickOffset=u?{left:0,top:0}:{left:e.pageX-l.left-s.width()/2,top:e.pageY-l.top-s.height()/2-(parseInt(s.css("borderTopWidth"),10)||0)-(parseInt(s.css("borderBottomWidth"),10)||0)+(parseInt(s.css("marginTop"),10)||0)},this.handles.hasClass("ui-state-hover")||this._slide(e,o,n),this._animateOff=!0,!0))},_mouseStart:function(){return!0},_mouseDrag:function(t){var e={x:t.pageX,y:t.pageY},i=this._normValueFromMouse(e);return this._slide(t,this._handleIndex,i),!1},_mouseStop:function(t){return this.handles.removeClass("ui-state-active"),this._mouseSliding=!1,this._stop(t,this._handleIndex),this._change(t,this._handleIndex),this._handleIndex=null,this._clickOffset=null,this._animateOff=!1,!1},_detectOrientation:function(){this.orientation="vertical"===this.options.orientation?"vertical":"horizontal"},_normValueFromMouse:function(t){var e,i,n,r,s;return"horizontal"===this.orientation?(e=this.elementSize.width,i=t.x-this.elementOffset.left-(this._clickOffset?this._clickOffset.left:0)):(e=this.elementSize.height,i=t.y-this.elementOffset.top-(this._clickOffset?this._clickOffset.top:0)),n=i/e,n>1&&(n=1),0>n&&(n=0),"vertical"===this.orientation&&(n=1-n),r=this._valueMax()-this._valueMin(),s=this._valueMin()+n*r,this._trimAlignValue(s)},_start:function(t,e){var i={handle:this.handles[e],value:this.value()};return this.options.values&&this.options.values.length&&(i.value=this.values(e),i.values=this.values()),this._trigger("start",t,i)},_slide:function(t,e,i){var n,r,s;this.options.values&&this.options.values.length?(n=this.values(e?0:1),2===this.options.values.length&&this.options.range===!0&&(0===e&&i>n||1===e&&n>i)&&(i=n),i!==this.values(e)&&(r=this.values(),r[e]=i,s=this._trigger("slide",t,{handle:this.handles[e],value:i,values:r}),n=this.values(e?0:1),s!==!1&&this.values(e,i,!0))):i!==this.value()&&(s=this._trigger("slide",t,{handle:this.handles[e],value:i}),s!==!1&&this.value(i))},_stop:function(t,e){var i={handle:this.handles[e],value:this.value()};this.options.values&&this.options.values.length&&(i.value=this.values(e),i.values=this.values()),this._trigger("stop",t,i)},_change:function(t,e){if(!this._keySliding&&!this._mouseSliding){var i={handle:this.handles[e],value:this.value()};this.options.values&&this.options.values.length&&(i.value=this.values(e),i.values=this.values()),this._trigger("change",t,i)}},value:function(t){return arguments.length?(this.options.value=this._trimAlignValue(t),this._refreshValue(),this._change(null,0),void 0):this._value()},values:function(e,i){var n,r,s;if(arguments.length>1)return this.options.values[e]=this._trimAlignValue(i),this._refreshValue(),this._change(null,e),void 0;if(!arguments.length)return this._values();if(!t.isArray(arguments[0]))return this.options.values&&this.options.values.length?this._values(e):this.value();for(n=this.options.values,r=arguments[0],s=0;sn;n+=1)this._change(null,n);this._animateOff=!1;break;case"min":case"max":this._animateOff=!0,this._refreshValue(),this._animateOff=!1}},_value:function(){var t=this.options.value;return t=this._trimAlignValue(t)},_values:function(t){var e,i,n;if(arguments.length)return e=this.options.values[t],e=this._trimAlignValue(e);for(i=this.options.values.slice(),n=0;n=this._valueMax())return this._valueMax();var e=this.options.step>0?this.options.step:1,i=(t-this._valueMin())%e,n=t-i;return 2*Math.abs(i)>=e&&(n+=i>0?e:-e),parseFloat(n.toFixed(5))},_valueMin:function(){return this.options.min},_valueMax:function(){return this.options.max},_refreshValue:function(){var e,i,n,r,s,o=this.options.range,a=this.options,l=this,u=this._animateOff?!1:a.animate,h={};this.options.values&&this.options.values.length?this.handles.each(function(n){i=(l.values(n)-l._valueMin())/(l._valueMax()-l._valueMin())*100,h["horizontal"===l.orientation?"left":"bottom"]=i+"%",t(this).stop(1,1)[u?"animate":"css"](h,a.animate),l.options.range===!0&&("horizontal"===l.orientation?(0===n&&l.range.stop(1,1)[u?"animate":"css"]({left:i+"%"},a.animate),1===n&&l.range[u?"animate":"css"]({width:i-e+"%"},{queue:!1,duration:a.animate})):(0===n&&l.range.stop(1,1)[u?"animate":"css"]({bottom:i+"%"},a.animate),1===n&&l.range[u?"animate":"css"]({height:i-e+"%"},{queue:!1,duration:a.animate}))),e=i}):(n=this.value(),r=this._valueMin(),s=this._valueMax(),i=s!==r?(n-r)/(s-r)*100:0,h["horizontal"===this.orientation?"left":"bottom"]=i+"%",this.handle.stop(1,1)[u?"animate":"css"](h,a.animate),"min"===o&&"horizontal"===this.orientation&&this.range.stop(1,1)[u?"animate":"css"]({width:i+"%"},a.animate),"max"===o&&"horizontal"===this.orientation&&this.range[u?"animate":"css"]({width:100-i+"%"},{queue:!1,duration:a.animate}),"min"===o&&"vertical"===this.orientation&&this.range.stop(1,1)[u?"animate":"css"]({height:i+"%"},a.animate),"max"===o&&"vertical"===this.orientation&&this.range[u?"animate":"css"]({height:100-i+"%"},{queue:!1,duration:a.animate}))}})}(t)}).call(e,i(1))},function(t,e,i){(function(t){!function(t){function e(i){function n(){e(r)}var r=i.data||i;switch(i.type){case"mouseenter":r.dist2=0,r.event=i,i.type="hoverstart",t.event.dispatch.call(this,i)!==!1&&(r.elem=this,t.event.add(this,"mousemove",e,r),r.timer=setTimeout(n,r.delay));break;case"mousemove":r.dist2+=Math.pow(i.pageX-r.event.pageX,2)+Math.pow(i.pageY-r.event.pageY,2),r.event=i;break;case"mouseleave":clearTimeout(r.timer),r.hovered?(i.type="hoverend",t.event.dispatch.call(this,i),r.hovered--):t.event.remove(r.elem,"mousemove",e);break;default:r.dist2<=Math.pow(r.speed*(r.delay/1e3),2)?(t.event.remove(r.elem,"mousemove",e),r.event.type="hover",t.event.dispatch.call(r.elem,r.event)!==!1&&r.hovered++):r.timer=setTimeout(n,r.delay),r.dist2=0}}t.fn._hover=t.fn.hover,t.fn.hover=function(t,e,i){return i&&this.bind("hoverstart",t),e&&this.bind("hoverend",i?i:e),t?this.bind("hover",i?e:t):this.trigger("hover")};var i=t.event.special.hover={delay:100,speed:100,setup:function(n){n=t.extend({speed:i.speed,delay:i.delay,hovered:0},n||{}),t.event.add(this,"mouseenter mouseleave",e,n)},teardown:function(){t.event.remove(this,"mouseenter mouseleave",e)}}}(t)}).call(e,i(1))},function(t,e,i){(function(t){!function(t){"use strict";function e(e){var i=e.data;e.isDefaultPrevented()||(e.preventDefault(),t(e.target).ajaxSubmit(i))}function i(e){var i=e.target,n=t(i);if(!n.is("[type=submit],[type=image]")){var r=n.closest("[type=submit]");if(0===r.length)return;i=r[0]}var s=this;if(s.clk=i,"image"==i.type)if(void 0!==e.offsetX)s.clk_x=e.offsetX,s.clk_y=e.offsetY;else if("function"==typeof t.fn.offset){var o=n.offset();s.clk_x=e.pageX-o.left,s.clk_y=e.pageY-o.top}else s.clk_x=e.pageX-i.offsetLeft,s.clk_y=e.pageY-i.offsetTop;setTimeout(function(){s.clk=s.clk_x=s.clk_y=null},100)}function n(){if(t.fn.ajaxSubmit.debug){var e="[jquery.form] "+Array.prototype.join.call(arguments,"");window.console&&window.console.log?window.console.log(e):window.opera&&window.opera.postError&&window.opera.postError(e)}}var r={};r.fileapi=void 0!==t("").get(0).files,r.formdata=void 0!==window.FormData;var s=!!t.fn.prop;t.fn.attr2=function(){if(!s)return this.attr.apply(this,arguments);var t=this.prop.apply(this,arguments);return t&&t.jquery||"string"==typeof t?t:this.attr.apply(this,arguments)},t.fn.ajaxSubmit=function(e){function i(i){var n,r,s=t.param(i,e.traditional).split("&"),o=s.length,a=[];for(n=0;o>n;n++)s[n]=s[n].replace(/\+/g," "),r=s[n].split("="),a.push([decodeURIComponent(r[0]),decodeURIComponent(r[1])]);return a}function o(n){for(var r=new FormData,s=0;s').val(d.extraData[u].value).appendTo(C)[0]):o.push(t('').val(d.extraData[u]).appendTo(C)[0]));d.iframeTarget||m.appendTo("body"),v.attachEvent?v.attachEvent("onload",a):v.addEventListener("load",a,!1),setTimeout(e,15);try{C.submit()}catch(h){var p=document.createElement("form").submit;p.apply(C)}}finally{C.setAttribute("action",s),i?C.setAttribute("target",i):c.removeAttr("target"),t(o).remove()}}function a(e){if(!y.aborted&&!M){if(O=r(v),O||(n("cannot access response document"),e=T),e===k&&y)return y.abort("timeout"),void S.reject(y,"timeout");if(e==T&&y)return y.abort("server abort"),void S.reject(y,"error","server abort");if(O&&O.location.href!=d.iframeSrc||_){v.detachEvent?v.detachEvent("onload",a):v.removeEventListener("load",a,!1);var i,s="success";try{if(_)throw"timeout";var o="xml"==d.dataType||O.XMLDocument||t.isXMLDoc(O);if(n("isXml="+o),!o&&window.opera&&(null===O.body||!O.body.innerHTML)&&--D)return n("requeing onLoad callback, DOM not available"),void setTimeout(a,250);var l=O.body?O.body:O.documentElement;y.responseText=l?l.innerHTML:null,y.responseXML=O.XMLDocument?O.XMLDocument:O,o&&(d.dataType="xml"),y.getResponseHeader=function(t){var e={"content-type":d.dataType};return e[t.toLowerCase()]},l&&(y.status=Number(l.getAttribute("status"))||y.status,y.statusText=l.getAttribute("statusText")||y.statusText);var u=(d.dataType||"").toLowerCase(),h=/(json|script|text)/.test(u);if(h||d.textarea){var c=O.getElementsByTagName("textarea")[0];if(c)y.responseText=c.value,y.status=Number(c.getAttribute("status"))||y.status,y.statusText=c.getAttribute("statusText")||y.statusText;else if(h){var f=O.getElementsByTagName("pre")[0],g=O.getElementsByTagName("body")[0];f?y.responseText=f.textContent?f.textContent:f.innerText:g&&(y.responseText=g.textContent?g.textContent:g.innerText)}}else"xml"==u&&!y.responseXML&&y.responseText&&(y.responseXML=P(y.responseText));try{N=j(y,u,d)}catch(b){s="parsererror",y.error=i=b||s}}catch(b){n("error caught: ",b),s="error",y.error=i=b||s}y.aborted&&(n("upload aborted"),s=null),y.status&&(s=y.status>=200&&y.status<300||304===y.status?"success":"error"),"success"===s?(d.success&&d.success.call(d.context,N,"success",y),S.resolve(y.responseText,"success",y),p&&t.event.trigger("ajaxSuccess",[y,d])):s&&(void 0===i&&(i=y.statusText),d.error&&d.error.call(d.context,y,s,i),S.reject(y,"error",i),p&&t.event.trigger("ajaxError",[y,d,i])),p&&t.event.trigger("ajaxComplete",[y,d]),p&&!--t.active&&t.event.trigger("ajaxStop"),d.complete&&d.complete.call(d.context,y,s),M=!0,d.timeout&&clearTimeout(x),setTimeout(function(){d.iframeTarget?m.attr("src",d.iframeSrc):m.remove(),y.responseXML=null},100)}}}var u,h,d,p,f,m,v,y,b,w,_,x,C=c[0],S=t.Deferred();if(S.abort=function(t){y.abort(t)},i)for(h=0;h'),m.css({position:"absolute",top:"-1000px",left:"-1000px"})),v=m[0],y={aborted:0,responseText:null,responseXML:null,status:0,statusText:"n/a",getAllResponseHeaders:function(){},getResponseHeader:function(){},setRequestHeader:function(){},abort:function(e){var i="timeout"===e?"timeout":"aborted";n("aborting upload... "+i),this.aborted=1;try{v.contentWindow.document.execCommand&&v.contentWindow.document.execCommand("Stop")}catch(r){}m.attr("src",d.iframeSrc),y.error=i,d.error&&d.error.call(d.context,y,i,e),p&&t.event.trigger("ajaxError",[y,d,i]),d.complete&&d.complete.call(d.context,y,i)}},p=d.global,p&&0===t.active++&&t.event.trigger("ajaxStart"),p&&t.event.trigger("ajaxSend",[y,d]),d.beforeSend&&d.beforeSend.call(d.context,y,d)===!1)return d.global&&t.active--,S.reject(),S;if(y.aborted)return S.reject(),S;b=C.clk,b&&(w=b.name,w&&!b.disabled&&(d.extraData=d.extraData||{},d.extraData[w]=b.value,"image"==b.type&&(d.extraData[w+".x"]=C.clk_x,d.extraData[w+".y"]=C.clk_y)));var k=1,T=2,E=t("meta[name=csrf-token]").attr("content"),A=t("meta[name=csrf-param]").attr("content");A&&E&&(d.extraData=d.extraData||{},d.extraData[A]=E),d.forceSync?o():setTimeout(o,10);var N,O,M,D=50,P=t.parseXML||function(t,e){return window.ActiveXObject?(e=new ActiveXObject("Microsoft.XMLDOM"),e.async="false",e.loadXML(t)):e=(new DOMParser).parseFromString(t,"text/xml"),e&&e.documentElement&&"parsererror"!=e.documentElement.nodeName?e:null},R=t.parseJSON||function(t){return window.eval("("+t+")")},j=function(e,i,n){var r=e.getResponseHeader("content-type")||"",s="xml"===i||!i&&r.indexOf("xml")>=0,o=s?e.responseXML:e.responseText;return s&&"parsererror"===o.documentElement.nodeName&&t.error&&t.error("parsererror"),n&&n.dataFilter&&(o=n.dataFilter(o,i)),"string"==typeof o&&("json"===i||!i&&r.indexOf("json")>=0?o=R(o):("script"===i||!i&&r.indexOf("javascript")>=0)&&t.globalEval(o)),o};return S}if(!this.length)return n("ajaxSubmit: skipping submit process - no element selected"),this;var l,u,h,c=this;"function"==typeof e?e={success:e}:void 0===e&&(e={}),l=e.type||this.attr2("method"),u=e.url||this.attr2("action"),h="string"==typeof u?t.trim(u):"",h=h||window.location.href||"",h&&(h=(h.match(/^([^#]+)/)||[])[1]),e=t.extend(!0,{url:h,success:t.ajaxSettings.success,type:l||t.ajaxSettings.type,iframeSrc:/^https/i.test(window.location.href||"")?"javascript:false":"about:blank"},e);var d={};if(this.trigger("form-pre-serialize",[this,e,d]),d.veto)return n("ajaxSubmit: submit vetoed via form-pre-serialize trigger"),this;if(e.beforeSerialize&&e.beforeSerialize(this,e)===!1)return n("ajaxSubmit: submit aborted via beforeSerialize callback"),this;var p=e.traditional;void 0===p&&(p=t.ajaxSettings.traditional);var f,g=[],m=this.formToArray(e.semantic,g);if(e.data&&(e.extraData=e.data,f=t.param(e.data,p)),e.beforeSubmit&&e.beforeSubmit(m,this,e)===!1)return n("ajaxSubmit: submit aborted via beforeSubmit callback"),this;if(this.trigger("form-submit-validate",[m,this,e,d]),d.veto)return n("ajaxSubmit: submit vetoed via form-submit-validate trigger"),this;var v=t.param(m,p);f&&(v=v?v+"&"+f:f),"GET"==e.type.toUpperCase()?(e.url+=(e.url.indexOf("?")>=0?"&":"?")+v,e.data=null):e.data=v;var y=[];if(e.resetForm&&y.push(function(){c.resetForm()}),e.clearForm&&y.push(function(){c.clearForm(e.includeHidden)}),!e.dataType&&e.target){var b=e.success||function(){};y.push(function(i){var n=e.replaceTarget?"replaceWith":"html"; -t(e.target)[n](i).each(b,arguments)})}else e.success&&y.push(e.success);if(e.success=function(t,i,n){for(var r=e.context||this,s=0,o=y.length;o>s;s++)y[s].apply(r,[t,i,n||c,c])},e.error){var w=e.error;e.error=function(t,i,n){var r=e.context||this;w.apply(r,[t,i,n,c])}}if(e.complete){var _=e.complete;e.complete=function(t,i){var n=e.context||this;_.apply(n,[t,i,c])}}var x=t("input[type=file]:enabled",this).filter(function(){return""!==t(this).val()}),C=x.length>0,S="multipart/form-data",k=c.attr("enctype")==S||c.attr("encoding")==S,T=r.fileapi&&r.formdata;n("fileAPI :"+T);var E,A=(C||k)&&!T;e.iframe!==!1&&(e.iframe||A)?e.closeKeepAlive?t.get(e.closeKeepAlive,function(){E=a(m)}):E=a(m):E=(C||k)&&T?o(m):t.ajax(e),c.removeData("jqxhr").data("jqxhr",E);for(var N=0;Na;a++)if(c=o[a],u=c.name,u&&!c.disabled)if(e&&s.clk&&"image"==c.type)s.clk==c&&(n.push({name:u,value:t(c).val(),type:c.type}),n.push({name:u+".x",value:s.clk_x},{name:u+".y",value:s.clk_y}));else if(h=t.fieldValue(c,!0),h&&h.constructor==Array)for(i&&i.push(c),l=0,p=h.length;p>l;l++)n.push({name:u,value:h[l]});else if(r.fileapi&&"file"==c.type){i&&i.push(c);var f=c.files;if(f.length)for(l=0;ls;s++)i.push({name:n,value:r[s]});else null!==r&&"undefined"!=typeof r&&i.push({name:this.name,value:r})}}),t.param(i)},t.fn.fieldValue=function(e){for(var i=[],n=0,r=this.length;r>n;n++){var s=this[n],o=t.fieldValue(s,e);null===o||"undefined"==typeof o||o.constructor==Array&&!o.length||(o.constructor==Array?t.merge(i,o):i.push(o))}return i},t.fieldValue=function(e,i){var n=e.name,r=e.type,s=e.tagName.toLowerCase();if(void 0===i&&(i=!0),i&&(!n||e.disabled||"reset"==r||"button"==r||("checkbox"==r||"radio"==r)&&!e.checked||("submit"==r||"image"==r)&&e.form&&e.form.clk!=e||"select"==s&&-1==e.selectedIndex))return null;if("select"==s){var o=e.selectedIndex;if(0>o)return null;for(var a=[],l=e.options,u="select-one"==r,h=u?o+1:l.length,c=u?o:0;h>c;c++){var d=l[c];if(d.selected){var p=d.value;if(p||(p=d.attributes&&d.attributes.value&&!d.attributes.value.specified?d.text:d.value),u)return p;a.push(p)}}return a}return t(e).val()},t.fn.clearForm=function(e){return this.each(function(){t("input,select,textarea",this).clearFields(e)})},t.fn.clearFields=t.fn.clearInputs=function(e){var i=/^(?:color|date|datetime|email|month|number|password|range|search|tel|text|time|url|week)$/i;return this.each(function(){var n=this.type,r=this.tagName.toLowerCase();i.test(n)||"textarea"==r?this.value="":"checkbox"==n||"radio"==n?this.checked=!1:"select"==r?this.selectedIndex=-1:"file"==n?/MSIE/.test(navigator.userAgent)?t(this).replaceWith(t(this).clone(!0)):t(this).val(""):e&&(e===!0&&/hidden/.test(n)||"string"==typeof e&&t(this).is(e))&&(this.value="")})},t.fn.resetForm=function(){return this.each(function(){("function"==typeof this.reset||"object"==typeof this.reset&&!this.reset.nodeType)&&this.reset()})},t.fn.enable=function(t){return void 0===t&&(t=!0),this.each(function(){this.disabled=!t})},t.fn.selected=function(e){return void 0===e&&(e=!0),this.each(function(){var i=this.type;if("checkbox"==i||"radio"==i)this.checked=e;else if("option"==this.tagName.toLowerCase()){var n=t(this).parent("select");e&&n[0]&&"select-one"==n[0].type&&n.find("option").selected(!1),this.selected=e}})},t.fn.ajaxSubmit.debug=!1}("undefined"!=typeof t?t:window.Zepto)}).call(e,i(1))},function(t,e,i){(function(t,e){t&&function(t){if(!t.support.opacity&&!t.support.style)try{document.execCommand("BackgroundImageCache",!1,!0)}catch(e){}t.fn.rating=function(e){if(0==this.length)return this;if("string"==typeof arguments[0]){if(this.length>1){var i=arguments;return this.each(function(){t.fn.rating.apply(t(this),i)})}return t.fn.rating[arguments[0]].apply(this,t.makeArray(arguments).slice(1)||[]),this}var e=t.extend({},t.fn.rating.options,e||{});return t.fn.rating.calls++,this.not(".star-rating-applied").addClass("star-rating-applied").each(function(){var i,n=t(this),r=(this.name||"unnamed-rating").replace(/\[|\]/g,"_").replace(/^\_+|\_+$/g,""),s=t(this.form||document.body),o=s.data("rating");o&&o.call==t.fn.rating.calls||(o={count:0,call:t.fn.rating.calls});var a=o[r]||s.data("rating"+r);a&&(i=a.data("rating")),a&&i?i.count++:(i=t.extend({},e||{},(t.metadata?n.metadata():t.meta?n.data():null)||{},{count:0,stars:[],inputs:[]}),i.serial=o.count++,a=t(''),n.before(a),a.addClass("rating-to-be-drawn"),(n.attr("disabled")||n.hasClass("disabled"))&&(i.readOnly=!0),n.hasClass("required")&&(i.required=!0),a.append(i.cancel=t('").on("mouseover",function(){t(this).rating("drain"),t(this).addClass("star-rating-hover")}).on("mouseout",function(){t(this).rating("draw"),t(this).removeClass("star-rating-hover")}).on("click",function(){t(this).rating("select")}).data("rating",i)));var l=t('
                                            ");if(a.append(l),this.id&&l.attr("id",this.id),this.className&&l.addClass(this.className),i.half&&(i.split=2),"number"==typeof i.split&&i.split>0){var u=(t.fn.width?l.width():0)||i.starWidth,h=i.count%i.split,c=Math.floor(u/i.split);l.width(c).find("a").css({"margin-left":"-"+h*c+"px"})}i.readOnly?l.addClass("star-rating-readonly"):l.addClass("star-rating-live").on("mouseover",function(){t(this).rating("fill"),t(this).rating("focus")}).on("mouseout",function(){t(this).rating("draw"),t(this).rating("blur")}).on("click",function(){t(this).rating("select")}),this.checked&&(i.current=l),"A"==this.nodeName&&t(this).hasClass("selected")&&(i.current=l),n.hide(),n.on("change.rating",function(e){return e.selfTriggered?!1:void t(this).rating("select")}),l.data("rating.input",n.data("rating.star",l)),i.stars[i.stars.length]=l[0],i.inputs[i.inputs.length]=n[0],i.rater=o[r]=a,i.context=s,n.data("rating",i),a.data("rating",i),l.data("rating",i),s.data("rating",o),s.data("rating"+r,a)}),t(".rating-to-be-drawn").rating("draw").removeClass("rating-to-be-drawn"),this},t.extend(t.fn.rating,{calls:0,focus:function(){var e=this.data("rating");if(!e)return this;if(!e.focus)return this;var i=t(this).data("rating.input")||t("INPUT"==this.tagName?this:null);e.focus&&e.focus.apply(i[0],[i.val(),t("a",i.data("rating.star"))[0]])},blur:function(){var e=this.data("rating");if(!e)return this;if(!e.blur)return this;var i=t(this).data("rating.input")||t("INPUT"==this.tagName?this:null);e.blur&&e.blur.apply(i[0],[i.val(),t("a",i.data("rating.star"))[0]])},fill:function(){var t=this.data("rating");return t?void(t.readOnly||(this.rating("drain"),this.prevAll().addBack().filter(".rater-"+t.serial).addClass("star-rating-hover"))):this},drain:function(){var t=this.data("rating");return t?void(t.readOnly||t.rater.children().filter(".rater-"+t.serial).removeClass("star-rating-on").removeClass("star-rating-hover")):this},draw:function(){var e=this.data("rating");if(!e)return this;this.rating("drain");var i=t(e.current),n=i.length?i.prevAll().addBack().filter(".rater-"+e.serial):null;n&&n.addClass("star-rating-on"),e.cancel[e.readOnly||e.required?"hide":"show"](),this.siblings()[e.readOnly?"addClass":"removeClass"]("star-rating-readonly")},select:function(e,i){var n=this.data("rating");if(!n)return this;if(!n.readOnly){if(n.current=null,"undefined"!=typeof e||this.length>1){if("number"==typeof e)return t(n.stars[e]).rating("select",void 0,i);if("string"==typeof e)return t.each(n.stars,function(){t(this).data("rating.input").val()==e&&t(this).rating("select",void 0,i)}),this}else n.current="INPUT"==this[0].tagName?this.data("rating.star"):this.is(".rater-"+n.serial)?this:null;this.data("rating",n),this.rating("draw");var r=t(n.current?n.current.data("rating.input"):null),s=t(n.inputs).filter(":checked"),o=t(n.inputs).not(r);return o.prop("checked",!1),r.prop("checked",!0),t(r.length?r:s).trigger({type:"change",selfTriggered:!0}),(i||void 0==i)&&n.callback&&n.callback.apply(r[0],[r.val(),t("a",n.current)[0]]),this}},readOnly:function(e,i){var n=this.data("rating");return n?(n.readOnly=e||void 0==e?!0:!1,i?t(n.inputs).attr("disabled","disabled"):t(n.inputs).removeAttr("disabled"),this.data("rating",n),void this.rating("draw")):this},disable:function(){this.rating("readOnly",!0,!0)},enable:function(){this.rating("readOnly",!1,!1)}}),t.fn.rating.options={cancel:"Cancel Rating",cancelValue:"",split:0,starWidth:16},t(function(){t("input[type=radio].star").rating()})}(e)}).call(e,i(1),i(1))},function(t,e,i){(function(t){!function(t){"undefined"==typeof t.fn.each2&&t.extend(t.fn,{each2:function(e){for(var i=t([0]),n=-1,r=this.length;++ni;i+=1)if(o(t,e[i]))return i;return-1}function s(){var e=t($);e.appendTo("body");var i={width:e.width()-e[0].clientWidth,height:e.height()-e[0].clientHeight};return e.remove(),i}function o(t,i){return t===i?!0:t===e||i===e?!1:null===t||null===i?!1:t.constructor===String?t+""==i+"":i.constructor===String?i+""==t+"":!1}function a(e,i){var n,r,s;if(null===e||e.length<1)return[];for(n=e.split(i),r=0,s=n.length;s>r;r+=1)n[r]=t.trim(n[r]);return n}function l(t){return t.outerWidth(!1)-t.width()}function u(i){var n="keyup-change-value";i.on("keydown",function(){t.data(i,n)===e&&t.data(i,n,i.val())}),i.on("keyup",function(){var r=t.data(i,n);r!==e&&i.val()!==r&&(t.removeData(i,n),i.trigger("keyup-change"))})}function h(i){i.on("mousemove",function(i){var n=I;(n===e||n.x!==i.pageX||n.y!==i.pageY)&&t(i.target).trigger("mousemove-filtered",i)})}function c(t,i,n){n=n||e;var r;return function(){var e=arguments;window.clearTimeout(r),r=window.setTimeout(function(){i.apply(n,e)},t)}}function d(t,e){var i=c(t,function(t){e.trigger("scroll-debounced",t)});e.on("scroll",function(t){r(t.target,e.get())>=0&&i(t)})}function p(t){t[0]!==document.activeElement&&window.setTimeout(function(){var e,i=t[0],n=t.val().length;t.focus();var r=i.offsetWidth>0||i.offsetHeight>0;r&&i===document.activeElement&&(i.setSelectionRange?i.setSelectionRange(n,n):i.createTextRange&&(e=i.createTextRange(),e.collapse(!1),e.select()))},0)}function f(e){e=t(e)[0];var i=0,n=0;if("selectionStart"in e)i=e.selectionStart,n=e.selectionEnd-i;else if("selection"in document){e.focus();var r=document.selection.createRange();n=document.selection.createRange().text.length,r.moveStart("character",-e.value.length),i=r.text.length-n}return{offset:i,length:n}}function g(t){t.preventDefault(),t.stopPropagation()}function m(t){t.preventDefault(),t.stopImmediatePropagation()}function v(e){if(!j){var i=e[0].currentStyle||window.getComputedStyle(e[0],null);j=t(document.createElement("div")).css({position:"absolute",left:"-10000px",top:"-10000px",display:"none",fontSize:i.fontSize,fontFamily:i.fontFamily,fontStyle:i.fontStyle,fontWeight:i.fontWeight,letterSpacing:i.letterSpacing,textTransform:i.textTransform,whiteSpace:"nowrap"}),j.attr("class","select2-sizer"),t("body").append(j)}return j.text(e.val()),j.width()}function y(e,i,n){var r,s,o=[];r=t.trim(e.attr("class")),r&&(r=""+r,t(r.split(/\s+/)).each2(function(){0===this.indexOf("select2-")&&o.push(this)})),r=t.trim(i.attr("class")),r&&(r=""+r,t(r.split(/\s+/)).each2(function(){0!==this.indexOf("select2-")&&(s=n(this),s&&o.push(s))})),e.attr("class",o.join(" "))}function b(t,e,i,r){var s=n(t.toUpperCase()).indexOf(n(e.toUpperCase())),o=e.length;return 0>s?void i.push(r(t)):(i.push(r(t.substring(0,s))),i.push(""),i.push(r(t.substring(s,s+o))),i.push(""),void i.push(r(t.substring(s+o,t.length))))}function w(t){var e={"\\":"\","&":"&","<":"<",">":">",'"':""","'":"'","/":"/"};return String(t).replace(/[&<>"'\/\\]/g,function(t){return e[t]})}function _(i){var n,r=null,s=i.quietMillis||100,o=i.url,a=this;return function(l){window.clearTimeout(n),n=window.setTimeout(function(){var n=i.data,s=o,u=i.transport||t.fn.select2.ajaxDefaults.transport,h={type:i.type||"GET",cache:i.cache||!1,jsonpCallback:i.jsonpCallback||e,dataType:i.dataType||"json"},c=t.extend({},t.fn.select2.ajaxDefaults.params,h);n=n?n.call(a,l.term,l.page,l.context):null,s="function"==typeof s?s.call(a,l.term,l.page,l.context):s,r&&"function"==typeof r.abort&&r.abort(),i.params&&(t.isFunction(i.params)?t.extend(c,i.params.call(a)):t.extend(c,i.params)),t.extend(c,{url:s,dataType:i.dataType,data:n,success:function(t){var e=i.results(t,l.page,l);l.callback(e)},error:function(t,e,i){var n={hasError:!0,jqXHR:t,textStatus:e,errorThrown:i};l.callback(n)}}),r=u.call(a,c)},s)}}function x(e){var i,n,r=e,s=function(t){return""+t.text};t.isArray(r)&&(n=r,r={results:n}),t.isFunction(r)===!1&&(n=r,r=function(){return n});var o=r();return o.text&&(s=o.text,t.isFunction(s)||(i=o.text,s=function(t){return t[i]})),function(e){var i,n=e.term,o={results:[]};return""===n?void e.callback(r()):(i=function(r,o){var a,l;if(r=r[0],r.children){a={};for(l in r)r.hasOwnProperty(l)&&(a[l]=r[l]);a.children=[],t(r.children).each2(function(t,e){i(e,a.children)}),(a.children.length||e.matcher(n,s(a),r))&&o.push(a)}else e.matcher(n,s(r),r)&&o.push(r)},t(r().results).each2(function(t,e){i(e,o.results)}),void e.callback(o))}}function C(i){var n=t.isFunction(i);return function(r){var s=r.term,o={results:[]},a=n?i(r):i;t.isArray(a)&&(t(a).each(function(){var t=this.text!==e,i=t?this.text:this;(""===s||r.matcher(s,i))&&o.results.push(t?this:{id:this,text:this})}),r.callback(o))}}function S(e,i){if(t.isFunction(e))return!0;if(!e)return!1;if("string"==typeof e)return!0;throw new Error(i+" must be a string, function, or falsy value")}function k(e,i){if(t.isFunction(e)){var n=Array.prototype.slice.call(arguments,2);return e.apply(i,n)}return e}function T(e){var i=0;return t.each(e,function(t,e){e.children?i+=T(e.children):i++}),i}function E(t,i,n,r){var s,a,l,u,h,c=t,d=!1;if(!r.createSearchChoice||!r.tokenSeparators||r.tokenSeparators.length<1)return e;for(;;){for(a=-1,l=0,u=r.tokenSeparators.length;u>l&&(h=r.tokenSeparators[l],a=t.indexOf(h),!(a>=0));l++);if(0>a)break;if(s=t.substring(0,a),t=t.substring(a+h.length),s.length>0&&(s=r.createSearchChoice.call(this,s,i),s!==e&&null!==s&&r.id(s)!==e&&null!==r.id(s))){for(d=!1,l=0,u=i.length;u>l;l++)if(o(r.id(s),r.id(i[l]))){d=!0;break}d||n(s)}}return c!==t?t:void 0}function A(){var e=this;t.each(arguments,function(t,i){e[i].remove(),e[i]=null})}function N(e,i){var n=function(){};return n.prototype=new e,n.prototype.constructor=n,n.prototype.parent=e.prototype,n.prototype=t.extend(n.prototype,i),n}if(window.Select2===e){var O,M,D,P,R,j,L,H,I={x:0,y:0},O={TAB:9,ENTER:13,ESC:27,SPACE:32,LEFT:37,UP:38,RIGHT:39,DOWN:40,SHIFT:16,CTRL:17,ALT:18,PAGE_UP:33,PAGE_DOWN:34,HOME:36,END:35,BACKSPACE:8,DELETE:46,isArrow:function(t){switch(t=t.which?t.which:t){case O.LEFT:case O.RIGHT:case O.UP:case O.DOWN:return!0}return!1},isControl:function(t){var e=t.which;switch(e){case O.SHIFT:case O.CTRL:case O.ALT:return!0}return t.metaKey?!0:!1},isFunctionKey:function(t){return t=t.which?t.which:t,t>=112&&123>=t}},$="
                                            ",F={"Ⓐ":"A","A":"A","À":"A","Á":"A","Â":"A","Ầ":"A","Ấ":"A","Ẫ":"A","Ẩ":"A","Ã":"A","Ā":"A","Ă":"A","Ằ":"A","Ắ":"A","Ẵ":"A","Ẳ":"A","Ȧ":"A","Ǡ":"A","Ä":"A","Ǟ":"A","Ả":"A","Å":"A","Ǻ":"A","Ǎ":"A","Ȁ":"A","Ȃ":"A","Ạ":"A","Ậ":"A","Ặ":"A","Ḁ":"A","Ą":"A","Ⱥ":"A","Ɐ":"A","Ꜳ":"AA","Æ":"AE","Ǽ":"AE","Ǣ":"AE","Ꜵ":"AO","Ꜷ":"AU","Ꜹ":"AV","Ꜻ":"AV","Ꜽ":"AY","Ⓑ":"B","B":"B","Ḃ":"B","Ḅ":"B","Ḇ":"B","Ƀ":"B","Ƃ":"B","Ɓ":"B","Ⓒ":"C","C":"C","Ć":"C","Ĉ":"C","Ċ":"C","Č":"C","Ç":"C","Ḉ":"C","Ƈ":"C","Ȼ":"C","Ꜿ":"C","Ⓓ":"D","D":"D","Ḋ":"D","Ď":"D","Ḍ":"D","Ḑ":"D","Ḓ":"D","Ḏ":"D","Đ":"D","Ƌ":"D","Ɗ":"D","Ɖ":"D","Ꝺ":"D","DZ":"DZ","DŽ":"DZ","Dz":"Dz","Dž":"Dz","Ⓔ":"E","E":"E","È":"E","É":"E","Ê":"E","Ề":"E","Ế":"E","Ễ":"E","Ể":"E","Ẽ":"E","Ē":"E","Ḕ":"E","Ḗ":"E","Ĕ":"E","Ė":"E","Ë":"E","Ẻ":"E","Ě":"E","Ȅ":"E","Ȇ":"E","Ẹ":"E","Ệ":"E","Ȩ":"E","Ḝ":"E","Ę":"E","Ḙ":"E","Ḛ":"E","Ɛ":"E","Ǝ":"E","Ⓕ":"F","F":"F","Ḟ":"F","Ƒ":"F","Ꝼ":"F","Ⓖ":"G","G":"G","Ǵ":"G","Ĝ":"G","Ḡ":"G","Ğ":"G","Ġ":"G","Ǧ":"G","Ģ":"G","Ǥ":"G","Ɠ":"G","Ꞡ":"G","Ᵹ":"G","Ꝿ":"G","Ⓗ":"H","H":"H","Ĥ":"H","Ḣ":"H","Ḧ":"H","Ȟ":"H","Ḥ":"H","Ḩ":"H","Ḫ":"H","Ħ":"H","Ⱨ":"H","Ⱶ":"H","Ɥ":"H","Ⓘ":"I","I":"I","Ì":"I","Í":"I","Î":"I","Ĩ":"I","Ī":"I","Ĭ":"I","İ":"I","Ï":"I","Ḯ":"I","Ỉ":"I","Ǐ":"I","Ȉ":"I","Ȋ":"I","Ị":"I","Į":"I","Ḭ":"I","Ɨ":"I","Ⓙ":"J","J":"J","Ĵ":"J","Ɉ":"J","Ⓚ":"K","K":"K","Ḱ":"K","Ǩ":"K","Ḳ":"K","Ķ":"K","Ḵ":"K","Ƙ":"K","Ⱪ":"K","Ꝁ":"K","Ꝃ":"K","Ꝅ":"K","Ꞣ":"K","Ⓛ":"L","L":"L","Ŀ":"L","Ĺ":"L","Ľ":"L","Ḷ":"L","Ḹ":"L","Ļ":"L","Ḽ":"L","Ḻ":"L","Ł":"L","Ƚ":"L","Ɫ":"L","Ⱡ":"L","Ꝉ":"L","Ꝇ":"L","Ꞁ":"L","LJ":"LJ","Lj":"Lj","Ⓜ":"M","M":"M","Ḿ":"M","Ṁ":"M","Ṃ":"M","Ɱ":"M","Ɯ":"M","Ⓝ":"N","N":"N","Ǹ":"N","Ń":"N","Ñ":"N","Ṅ":"N","Ň":"N","Ṇ":"N","Ņ":"N","Ṋ":"N","Ṉ":"N","Ƞ":"N","Ɲ":"N","Ꞑ":"N","Ꞥ":"N","NJ":"NJ","Nj":"Nj","Ⓞ":"O","O":"O","Ò":"O","Ó":"O","Ô":"O","Ồ":"O","Ố":"O","Ỗ":"O","Ổ":"O","Õ":"O","Ṍ":"O","Ȭ":"O","Ṏ":"O","Ō":"O","Ṑ":"O","Ṓ":"O","Ŏ":"O","Ȯ":"O","Ȱ":"O","Ö":"O","Ȫ":"O","Ỏ":"O","Ő":"O","Ǒ":"O","Ȍ":"O","Ȏ":"O","Ơ":"O","Ờ":"O","Ớ":"O","Ỡ":"O","Ở":"O","Ợ":"O","Ọ":"O","Ộ":"O","Ǫ":"O","Ǭ":"O","Ø":"O","Ǿ":"O","Ɔ":"O","Ɵ":"O","Ꝋ":"O","Ꝍ":"O","Ƣ":"OI","Ꝏ":"OO","Ȣ":"OU","Ⓟ":"P","P":"P","Ṕ":"P","Ṗ":"P","Ƥ":"P","Ᵽ":"P","Ꝑ":"P","Ꝓ":"P","Ꝕ":"P","Ⓠ":"Q","Q":"Q","Ꝗ":"Q","Ꝙ":"Q","Ɋ":"Q","Ⓡ":"R","R":"R","Ŕ":"R","Ṙ":"R","Ř":"R","Ȑ":"R","Ȓ":"R","Ṛ":"R","Ṝ":"R","Ŗ":"R","Ṟ":"R","Ɍ":"R","Ɽ":"R","Ꝛ":"R","Ꞧ":"R","Ꞃ":"R","Ⓢ":"S","S":"S","ẞ":"S","Ś":"S","Ṥ":"S","Ŝ":"S","Ṡ":"S","Š":"S","Ṧ":"S","Ṣ":"S","Ṩ":"S","Ș":"S","Ş":"S","Ȿ":"S","Ꞩ":"S","Ꞅ":"S","Ⓣ":"T","T":"T","Ṫ":"T","Ť":"T","Ṭ":"T","Ț":"T","Ţ":"T","Ṱ":"T","Ṯ":"T","Ŧ":"T","Ƭ":"T","Ʈ":"T","Ⱦ":"T","Ꞇ":"T","Ꜩ":"TZ","Ⓤ":"U","U":"U","Ù":"U","Ú":"U","Û":"U","Ũ":"U","Ṹ":"U","Ū":"U","Ṻ":"U","Ŭ":"U","Ü":"U","Ǜ":"U","Ǘ":"U","Ǖ":"U","Ǚ":"U","Ủ":"U","Ů":"U","Ű":"U","Ǔ":"U","Ȕ":"U","Ȗ":"U","Ư":"U","Ừ":"U","Ứ":"U","Ữ":"U","Ử":"U","Ự":"U","Ụ":"U","Ṳ":"U","Ų":"U","Ṷ":"U","Ṵ":"U","Ʉ":"U","Ⓥ":"V","V":"V","Ṽ":"V","Ṿ":"V","Ʋ":"V","Ꝟ":"V","Ʌ":"V","Ꝡ":"VY","Ⓦ":"W","W":"W","Ẁ":"W","Ẃ":"W","Ŵ":"W","Ẇ":"W","Ẅ":"W","Ẉ":"W","Ⱳ":"W","Ⓧ":"X","X":"X","Ẋ":"X","Ẍ":"X","Ⓨ":"Y","Y":"Y","Ỳ":"Y","Ý":"Y","Ŷ":"Y","Ỹ":"Y","Ȳ":"Y","Ẏ":"Y","Ÿ":"Y","Ỷ":"Y","Ỵ":"Y","Ƴ":"Y","Ɏ":"Y","Ỿ":"Y","Ⓩ":"Z","Z":"Z","Ź":"Z","Ẑ":"Z","Ż":"Z","Ž":"Z","Ẓ":"Z","Ẕ":"Z","Ƶ":"Z","Ȥ":"Z","Ɀ":"Z","Ⱬ":"Z","Ꝣ":"Z","ⓐ":"a","a":"a","ẚ":"a","à":"a","á":"a","â":"a","ầ":"a","ấ":"a","ẫ":"a","ẩ":"a","ã":"a","ā":"a","ă":"a","ằ":"a","ắ":"a","ẵ":"a","ẳ":"a","ȧ":"a","ǡ":"a","ä":"a","ǟ":"a","ả":"a","å":"a","ǻ":"a","ǎ":"a","ȁ":"a","ȃ":"a","ạ":"a","ậ":"a","ặ":"a","ḁ":"a","ą":"a","ⱥ":"a","ɐ":"a","ꜳ":"aa","æ":"ae","ǽ":"ae","ǣ":"ae","ꜵ":"ao","ꜷ":"au","ꜹ":"av","ꜻ":"av","ꜽ":"ay","ⓑ":"b","b":"b","ḃ":"b","ḅ":"b","ḇ":"b","ƀ":"b","ƃ":"b","ɓ":"b","ⓒ":"c","c":"c","ć":"c","ĉ":"c","ċ":"c","č":"c","ç":"c","ḉ":"c","ƈ":"c","ȼ":"c","ꜿ":"c","ↄ":"c","ⓓ":"d","d":"d","ḋ":"d","ď":"d","ḍ":"d","ḑ":"d","ḓ":"d","ḏ":"d","đ":"d","ƌ":"d","ɖ":"d","ɗ":"d","ꝺ":"d","dz":"dz","dž":"dz","ⓔ":"e","e":"e","è":"e","é":"e","ê":"e","ề":"e","ế":"e","ễ":"e","ể":"e","ẽ":"e","ē":"e","ḕ":"e","ḗ":"e","ĕ":"e","ė":"e","ë":"e","ẻ":"e","ě":"e","ȅ":"e","ȇ":"e","ẹ":"e","ệ":"e","ȩ":"e","ḝ":"e","ę":"e","ḙ":"e","ḛ":"e","ɇ":"e","ɛ":"e","ǝ":"e","ⓕ":"f","f":"f","ḟ":"f","ƒ":"f","ꝼ":"f","ⓖ":"g","g":"g","ǵ":"g","ĝ":"g","ḡ":"g","ğ":"g","ġ":"g","ǧ":"g","ģ":"g","ǥ":"g","ɠ":"g","ꞡ":"g","ᵹ":"g","ꝿ":"g","ⓗ":"h","h":"h","ĥ":"h","ḣ":"h","ḧ":"h","ȟ":"h","ḥ":"h","ḩ":"h","ḫ":"h","ẖ":"h","ħ":"h","ⱨ":"h","ⱶ":"h","ɥ":"h","ƕ":"hv","ⓘ":"i","i":"i","ì":"i","í":"i","î":"i","ĩ":"i","ī":"i","ĭ":"i","ï":"i","ḯ":"i","ỉ":"i","ǐ":"i","ȉ":"i","ȋ":"i","ị":"i","į":"i","ḭ":"i","ɨ":"i","ı":"i","ⓙ":"j","j":"j","ĵ":"j","ǰ":"j","ɉ":"j","ⓚ":"k","k":"k","ḱ":"k","ǩ":"k","ḳ":"k","ķ":"k","ḵ":"k","ƙ":"k","ⱪ":"k","ꝁ":"k","ꝃ":"k","ꝅ":"k","ꞣ":"k","ⓛ":"l","l":"l","ŀ":"l","ĺ":"l","ľ":"l","ḷ":"l","ḹ":"l","ļ":"l","ḽ":"l","ḻ":"l","ſ":"l","ł":"l","ƚ":"l","ɫ":"l","ⱡ":"l","ꝉ":"l","ꞁ":"l","ꝇ":"l","lj":"lj","ⓜ":"m","m":"m","ḿ":"m","ṁ":"m","ṃ":"m","ɱ":"m","ɯ":"m","ⓝ":"n","n":"n","ǹ":"n","ń":"n","ñ":"n","ṅ":"n","ň":"n","ṇ":"n","ņ":"n","ṋ":"n","ṉ":"n","ƞ":"n","ɲ":"n","ʼn":"n","ꞑ":"n","ꞥ":"n","nj":"nj","ⓞ":"o","o":"o","ò":"o","ó":"o","ô":"o","ồ":"o","ố":"o","ỗ":"o","ổ":"o","õ":"o","ṍ":"o","ȭ":"o","ṏ":"o","ō":"o","ṑ":"o","ṓ":"o","ŏ":"o","ȯ":"o","ȱ":"o","ö":"o","ȫ":"o","ỏ":"o","ő":"o","ǒ":"o","ȍ":"o","ȏ":"o","ơ":"o","ờ":"o","ớ":"o","ỡ":"o","ở":"o","ợ":"o","ọ":"o","ộ":"o","ǫ":"o","ǭ":"o","ø":"o","ǿ":"o","ɔ":"o","ꝋ":"o","ꝍ":"o","ɵ":"o","ƣ":"oi","ȣ":"ou","ꝏ":"oo","ⓟ":"p","p":"p","ṕ":"p","ṗ":"p","ƥ":"p","ᵽ":"p","ꝑ":"p","ꝓ":"p","ꝕ":"p","ⓠ":"q","q":"q","ɋ":"q","ꝗ":"q","ꝙ":"q","ⓡ":"r","r":"r","ŕ":"r","ṙ":"r","ř":"r","ȑ":"r","ȓ":"r","ṛ":"r","ṝ":"r","ŗ":"r","ṟ":"r","ɍ":"r","ɽ":"r","ꝛ":"r","ꞧ":"r","ꞃ":"r","ⓢ":"s","s":"s","ß":"s","ś":"s","ṥ":"s","ŝ":"s","ṡ":"s","š":"s","ṧ":"s","ṣ":"s","ṩ":"s","ș":"s","ş":"s","ȿ":"s","ꞩ":"s","ꞅ":"s","ẛ":"s","ⓣ":"t","t":"t","ṫ":"t","ẗ":"t","ť":"t","ṭ":"t","ț":"t","ţ":"t","ṱ":"t","ṯ":"t","ŧ":"t","ƭ":"t","ʈ":"t","ⱦ":"t","ꞇ":"t","ꜩ":"tz","ⓤ":"u","u":"u","ù":"u","ú":"u","û":"u","ũ":"u","ṹ":"u","ū":"u","ṻ":"u","ŭ":"u","ü":"u","ǜ":"u","ǘ":"u","ǖ":"u","ǚ":"u","ủ":"u","ů":"u","ű":"u","ǔ":"u","ȕ":"u","ȗ":"u","ư":"u","ừ":"u","ứ":"u","ữ":"u","ử":"u","ự":"u","ụ":"u","ṳ":"u","ų":"u","ṷ":"u","ṵ":"u","ʉ":"u","ⓥ":"v","v":"v","ṽ":"v","ṿ":"v","ʋ":"v","ꝟ":"v","ʌ":"v","ꝡ":"vy","ⓦ":"w","w":"w","ẁ":"w","ẃ":"w","ŵ":"w","ẇ":"w","ẅ":"w","ẘ":"w","ẉ":"w","ⱳ":"w","ⓧ":"x","x":"x","ẋ":"x","ẍ":"x","ⓨ":"y","y":"y","ỳ":"y","ý":"y","ŷ":"y","ỹ":"y","ȳ":"y","ẏ":"y","ÿ":"y","ỷ":"y","ẙ":"y","ỵ":"y","ƴ":"y","ɏ":"y","ỿ":"y","ⓩ":"z","z":"z","ź":"z","ẑ":"z","ż":"z","ž":"z","ẓ":"z","ẕ":"z","ƶ":"z","ȥ":"z","ɀ":"z","ⱬ":"z","ꝣ":"z","Ά":"Α","Έ":"Ε","Ή":"Η","Ί":"Ι","Ϊ":"Ι","Ό":"Ο","Ύ":"Υ","Ϋ":"Υ","Ώ":"Ω","ά":"α","έ":"ε","ή":"η","ί":"ι","ϊ":"ι","ΐ":"ι","ό":"ο","ύ":"υ","ϋ":"υ","ΰ":"υ","ω":"ω","ς":"σ"};L=t(document),R=function(){var t=1;return function(){return t++}}(),M=N(Object,{bind:function(t){var e=this;return function(){t.apply(e,arguments)}},init:function(i){var n,r,o=".select2-results";this.opts=i=this.prepareOpts(i),this.id=i.id,i.element.data("select2")!==e&&null!==i.element.data("select2")&&i.element.data("select2").destroy(),this.container=this.createContainer(),this.liveRegion=t("",{role:"status","aria-live":"polite"}).addClass("select2-hidden-accessible").appendTo(document.body),this.containerId="s2id_"+(i.element.attr("id")||"autogen"+R()),this.containerEventName=this.containerId.replace(/([.])/g,"_").replace(/([;&,\-\.\+\*\~':"\!\^#$%@\[\]\(\)=>\|])/g,"\\$1"),this.container.attr("id",this.containerId),this.container.attr("title",i.element.attr("title")),this.body=t("body"),y(this.container,this.opts.element,this.opts.adaptContainerCssClass),this.container.attr("style",i.element.attr("style")),this.container.css(k(i.containerCss,this.opts.element)),this.container.addClass(k(i.containerCssClass,this.opts.element)),this.elementTabIndex=this.opts.element.attr("tabindex"),this.opts.element.data("select2",this).attr("tabindex","-1").before(this.container).on("click.select2",g),this.container.data("select2",this),this.dropdown=this.container.find(".select2-drop"),y(this.dropdown,this.opts.element,this.opts.adaptDropdownCssClass),this.dropdown.addClass(k(i.dropdownCssClass,this.opts.element)),this.dropdown.data("select2",this),this.dropdown.on("click",g),this.results=n=this.container.find(o),this.search=r=this.container.find("input.select2-input"),this.queryCount=0,this.resultsPage=0,this.context=null,this.initContainer(),this.container.on("click",g),h(this.results),this.dropdown.on("mousemove-filtered",o,this.bind(this.highlightUnderEvent)),this.dropdown.on("touchstart touchmove touchend",o,this.bind(function(t){this._touchEvent=!0,this.highlightUnderEvent(t)})),this.dropdown.on("touchmove",o,this.bind(this.touchMoved)),this.dropdown.on("touchstart touchend",o,this.bind(this.clearTouchMoved)),this.dropdown.on("click",this.bind(function(t){this._touchEvent&&(this._touchEvent=!1,this.selectHighlighted())})),d(80,this.results),this.dropdown.on("scroll-debounced",o,this.bind(this.loadMoreIfNeeded)),t(this.container).on("change",".select2-input",function(t){t.stopPropagation()}),t(this.dropdown).on("change",".select2-input",function(t){t.stopPropagation()}),t.fn.mousewheel&&n.mousewheel(function(t,e,i,r){var s=n.scrollTop();r>0&&0>=s-r?(n.scrollTop(0),g(t)):0>r&&n.get(0).scrollHeight-n.scrollTop()+r<=n.height()&&(n.scrollTop(n.get(0).scrollHeight-n.height()),g(t))}),u(r),r.on("keyup-change input paste",this.bind(this.updateResults)),r.on("focus",function(){r.addClass("select2-focused")}),r.on("blur",function(){r.removeClass("select2-focused")}),this.dropdown.on("mouseup",o,this.bind(function(e){t(e.target).closest(".select2-result-selectable").length>0&&(this.highlightUnderEvent(e),this.selectHighlighted(e))})),this.dropdown.on("click mouseup mousedown touchstart touchend focusin",function(t){t.stopPropagation()}),this.nextSearchTerm=e,t.isFunction(this.opts.initSelection)&&(this.initSelection(),this.monitorSource()),null!==i.maximumInputLength&&this.search.attr("maxlength",i.maximumInputLength);var a=i.element.prop("disabled");a===e&&(a=!1),this.enable(!a);var l=i.element.prop("readonly");l===e&&(l=!1),this.readonly(l),H=H||s(),this.autofocus=i.element.prop("autofocus"),i.element.prop("autofocus",!1),this.autofocus&&this.focus(),this.search.attr("placeholder",i.searchInputPlaceholder)},destroy:function(){var t=this.opts.element,i=t.data("select2"),n=this;this.close(),t.length&&t[0].detachEvent&&t.each(function(){this.detachEvent("onpropertychange",n._sync)}),this.propertyObserver&&(this.propertyObserver.disconnect(),this.propertyObserver=null),this._sync=null,i!==e&&(i.container.remove(),i.liveRegion.remove(),i.dropdown.remove(),t.removeClass("select2-offscreen").removeData("select2").off(".select2").prop("autofocus",this.autofocus||!1),this.elementTabIndex?t.attr({tabindex:this.elementTabIndex}):t.removeAttr("tabindex"),t.show()),A.call(this,"container","liveRegion","dropdown","results","search")},optionToData:function(t){return t.is("option")?{id:t.prop("value"),text:t.text(),element:t.get(),css:t.attr("class"),disabled:t.prop("disabled"),locked:o(t.attr("locked"),"locked")||o(t.data("locked"),!0)}:t.is("optgroup")?{text:t.attr("label"),children:[],element:t.get(),css:t.attr("class")}:void 0},prepareOpts:function(i){var n,r,s,l,u=this;if(n=i.element,"select"===n.get(0).tagName.toLowerCase()&&(this.select=r=i.element),r&&t.each(["id","multiple","ajax","query","createSearchChoice","initSelection","data","tags"],function(){if(this in i)throw new Error("Option '"+this+"' is not allowed for Select2 when attached to a ","
                                            "," ","
                                              ","
                                            ","
                                            "].join(""));return e},enableInterface:function(){this.parent.enableInterface.apply(this,arguments)&&this.focusser.prop("disabled",!this.isInterfaceEnabled())},opening:function(){var i,n,r;this.opts.minimumResultsForSearch>=0&&this.showSearch(!0),this.parent.opening.apply(this,arguments),this.showSearchInput!==!1&&this.search.val(this.focusser.val()),this.opts.shouldFocusInput(this)&&(this.search.focus(),i=this.search.get(0),i.createTextRange?(n=i.createTextRange(),n.collapse(!1),n.select()):i.setSelectionRange&&(r=this.search.val().length,i.setSelectionRange(r,r))),""===this.search.val()&&this.nextSearchTerm!=e&&(this.search.val(this.nextSearchTerm),this.search.select()),this.focusser.prop("disabled",!0).val(""),this.updateResults(!0),this.opts.element.trigger(t.Event("select2-open"))},close:function(){this.opened()&&(this.parent.close.apply(this,arguments),this.focusser.prop("disabled",!1),this.opts.shouldFocusInput(this)&&this.focusser.focus())},focus:function(){this.opened()?this.close():(this.focusser.prop("disabled",!1),this.opts.shouldFocusInput(this)&&this.focusser.focus())},isFocused:function(){return this.container.hasClass("select2-container-active")},cancel:function(){this.parent.cancel.apply(this,arguments),this.focusser.prop("disabled",!1),this.opts.shouldFocusInput(this)&&this.focusser.focus()},destroy:function(){t("label[for='"+this.focusser.attr("id")+"']").attr("for",this.opts.element.attr("id")),this.parent.destroy.apply(this,arguments),A.call(this,"selection","focusser")},initContainer:function(){var e,n,r=this.container,s=this.dropdown,o=R();this.opts.minimumResultsForSearch<0?this.showSearch(!1):this.showSearch(!0),this.selection=e=r.find(".select2-choice"),this.focusser=r.find(".select2-focusser"),e.find(".select2-chosen").attr("id","select2-chosen-"+o),this.focusser.attr("aria-labelledby","select2-chosen-"+o),this.results.attr("id","select2-results-"+o),this.search.attr("aria-owns","select2-results-"+o),this.focusser.attr("id","s2id_autogen"+o),n=t("label[for='"+this.opts.element.attr("id")+"']"),this.focusser.prev().text(n.text()).attr("for",this.focusser.attr("id"));var a=this.opts.element.attr("title");this.opts.element.attr("title",a||n.text()),this.focusser.attr("tabindex",this.elementTabIndex),this.search.attr("id",this.focusser.attr("id")+"_search"),this.search.prev().text(t("label[for='"+this.focusser.attr("id")+"']").text()).attr("for",this.search.attr("id")),this.search.on("keydown",this.bind(function(t){if(this.isInterfaceEnabled()&&229!=t.keyCode){if(t.which===O.PAGE_UP||t.which===O.PAGE_DOWN)return void g(t);switch(t.which){case O.UP:case O.DOWN:return this.moveHighlight(t.which===O.UP?-1:1),void g(t);case O.ENTER:return this.selectHighlighted(),void g(t);case O.TAB:return void this.selectHighlighted({noFocus:!0});case O.ESC:return this.cancel(t),void g(t)}}})),this.search.on("blur",this.bind(function(t){document.activeElement===this.body.get(0)&&window.setTimeout(this.bind(function(){this.opened()&&this.search.focus()}),0)})),this.focusser.on("keydown",this.bind(function(t){if(this.isInterfaceEnabled()&&t.which!==O.TAB&&!O.isControl(t)&&!O.isFunctionKey(t)&&t.which!==O.ESC){if(this.opts.openOnEnter===!1&&t.which===O.ENTER)return void g(t);if(t.which==O.DOWN||t.which==O.UP||t.which==O.ENTER&&this.opts.openOnEnter){if(t.altKey||t.ctrlKey||t.shiftKey||t.metaKey)return;return this.open(),void g(t)}return t.which==O.DELETE||t.which==O.BACKSPACE?(this.opts.allowClear&&this.clear(),void g(t)):void 0}})),u(this.focusser),this.focusser.on("keyup-change input",this.bind(function(t){if(this.opts.minimumResultsForSearch>=0){if(t.stopPropagation(),this.opened())return;this.open()}})),e.on("mousedown touchstart","abbr",this.bind(function(t){this.isInterfaceEnabled()&&(this.clear(),m(t),this.close(),this.selection.focus())})),e.on("mousedown touchstart",this.bind(function(n){i(e),this.container.hasClass("select2-container-active")||this.opts.element.trigger(t.Event("select2-focus")),this.opened()?this.close():this.isInterfaceEnabled()&&this.open(),g(n)})),s.on("mousedown touchstart",this.bind(function(){this.opts.shouldFocusInput(this)&&this.search.focus()})),e.on("focus",this.bind(function(t){g(t)})),this.focusser.on("focus",this.bind(function(){this.container.hasClass("select2-container-active")||this.opts.element.trigger(t.Event("select2-focus")),this.container.addClass("select2-container-active")})).on("blur",this.bind(function(){this.opened()||(this.container.removeClass("select2-container-active"),this.opts.element.trigger(t.Event("select2-blur")))})),this.search.on("focus",this.bind(function(){this.container.hasClass("select2-container-active")||this.opts.element.trigger(t.Event("select2-focus")),this.container.addClass("select2-container-active")})),this.initContainerWidth(),this.opts.element.addClass("select2-offscreen"),this.setPlaceholder()},clear:function(e){var i=this.selection.data("select2-data");if(i){var n=t.Event("select2-clearing");if(this.opts.element.trigger(n),n.isDefaultPrevented())return;var r=this.getPlaceholderOption();this.opts.element.val(r?r.val():""),this.selection.find(".select2-chosen").empty(),this.selection.removeData("select2-data"),this.setPlaceholder(),e!==!1&&(this.opts.element.trigger({type:"select2-removed",val:this.id(i),choice:i}),this.triggerChange({removed:i}))}},initSelection:function(){if(this.isPlaceholderOptionSelected())this.updateSelection(null),this.close(),this.setPlaceholder();else{var t=this;this.opts.initSelection.call(null,this.opts.element,function(i){i!==e&&null!==i&&(t.updateSelection(i),t.close(),t.setPlaceholder(),t.nextSearchTerm=t.opts.nextSearchTerm(i,t.search.val()))})}},isPlaceholderOptionSelected:function(){var t;return this.getPlaceholder()===e?!1:(t=this.getPlaceholderOption())!==e&&t.prop("selected")||""===this.opts.element.val()||this.opts.element.val()===e||null===this.opts.element.val()},prepareOpts:function(){var e=this.parent.prepareOpts.apply(this,arguments),i=this;return"select"===e.element.get(0).tagName.toLowerCase()?e.initSelection=function(t,e){var n=t.find("option").filter(function(){return this.selected&&!this.disabled});e(i.optionToData(n))}:"data"in e&&(e.initSelection=e.initSelection||function(i,n){var r=i.val(),s=null;e.query({matcher:function(t,i,n){var a=o(r,e.id(n));return a&&(s=n),a},callback:t.isFunction(n)?function(){n(s)}:t.noop})}),e},getPlaceholder:function(){return this.select&&this.getPlaceholderOption()===e?e:this.parent.getPlaceholder.apply(this,arguments)},setPlaceholder:function(){var t=this.getPlaceholder();if(this.isPlaceholderOptionSelected()&&t!==e){if(this.select&&this.getPlaceholderOption()===e)return;this.selection.find(".select2-chosen").html(this.opts.escapeMarkup(t)),this.selection.addClass("select2-default"),this.container.removeClass("select2-allowclear")}},postprocessResults:function(t,e,i){var n=0,r=this;if(this.findHighlightableChoices().each2(function(t,e){return o(r.id(e.data("select2-data")),r.opts.element.val())?(n=t,!1):void 0}),i!==!1&&(e===!0&&n>=0?this.highlight(n):this.highlight(0)),e===!0){var s=this.opts.minimumResultsForSearch;s>=0&&this.showSearch(T(t.results)>=s)}},showSearch:function(e){this.showSearchInput!==e&&(this.showSearchInput=e,this.dropdown.find(".select2-search").toggleClass("select2-search-hidden",!e),this.dropdown.find(".select2-search").toggleClass("select2-offscreen",!e),t(this.dropdown,this.container).toggleClass("select2-with-searchbox",e))},onSelect:function(t,e){if(this.triggerSelect(t)){var i=this.opts.element.val(),n=this.data();this.opts.element.val(this.id(t)),this.updateSelection(t),this.opts.element.trigger({type:"select2-selected",val:this.id(t),choice:t}),this.nextSearchTerm=this.opts.nextSearchTerm(t,this.search.val()),this.close(),e&&e.noFocus||!this.opts.shouldFocusInput(this)||this.focusser.focus(),o(i,this.id(t))||this.triggerChange({added:t,removed:n})}},updateSelection:function(t){var i,n,r=this.selection.find(".select2-chosen");this.selection.data("select2-data",t),r.empty(),null!==t&&(i=this.opts.formatSelection(t,r,this.opts.escapeMarkup)),i!==e&&r.append(i),n=this.opts.formatSelectionCssClass(t,r),n!==e&&r.addClass(n),this.selection.removeClass("select2-default"),this.opts.allowClear&&this.getPlaceholder()!==e&&this.container.addClass("select2-allowclear")},val:function(){var t,i=!1,n=null,r=this,s=this.data();if(0===arguments.length)return this.opts.element.val();if(t=arguments[0],arguments.length>1&&(i=arguments[1]),this.select)this.select.val(t).find("option").filter(function(){return this.selected}).each2(function(t,e){return n=r.optionToData(e),!1}),this.updateSelection(n),this.setPlaceholder(),i&&this.triggerChange({added:n,removed:s});else{if(!t&&0!==t)return void this.clear(i);if(this.opts.initSelection===e)throw new Error("cannot call val() if initSelection() is not defined");this.opts.element.val(t),this.opts.initSelection(this.opts.element,function(t){r.opts.element.val(t?r.id(t):""),r.updateSelection(t),r.setPlaceholder(),i&&r.triggerChange({added:t,removed:s})})}},clearSearch:function(){this.search.val(""),this.focusser.val("")},data:function(t){var i,n=!1;return 0===arguments.length?(i=this.selection.data("select2-data"),i==e&&(i=null),i):(arguments.length>1&&(n=arguments[1]),void(t?(i=this.data(),this.opts.element.val(t?this.id(t):""),this.updateSelection(t),n&&this.triggerChange({added:t,removed:i})):this.clear(n)))}}),P=N(M,{createContainer:function(){var e=t(document.createElement("div")).attr({"class":"select2-container select2-container-multi"}).html(["
                                              ","
                                            • "," "," ","
                                            • ","
                                            ","
                                            ","
                                              ","
                                            ","
                                            "].join(""));return e},prepareOpts:function(){var e=this.parent.prepareOpts.apply(this,arguments),i=this;return"select"===e.element.get(0).tagName.toLowerCase()?e.initSelection=function(t,e){var n=[];t.find("option").filter(function(){return this.selected&&!this.disabled}).each2(function(t,e){n.push(i.optionToData(e))}),e(n)}:"data"in e&&(e.initSelection=e.initSelection||function(i,n){var r=a(i.val(),e.separator),s=[];e.query({matcher:function(i,n,a){var l=t.grep(r,function(t){return o(t,e.id(a))}).length;return l&&s.push(a),l},callback:t.isFunction(n)?function(){for(var t=[],i=0;i0||(this.selectChoice(null),this.clearPlaceholder(),this.container.hasClass("select2-container-active")||this.opts.element.trigger(t.Event("select2-focus")),this.open(),this.focusSearch(),e.preventDefault()))})),this.container.on("focus",i,this.bind(function(){this.isInterfaceEnabled()&&(this.container.hasClass("select2-container-active")||this.opts.element.trigger(t.Event("select2-focus")),this.container.addClass("select2-container-active"),this.dropdown.addClass("select2-drop-active"),this.clearPlaceholder())})),this.initContainerWidth(),this.opts.element.addClass("select2-offscreen"),this.clearSearch()},enableInterface:function(){this.parent.enableInterface.apply(this,arguments)&&this.search.prop("disabled",!this.isInterfaceEnabled())},initSelection:function(){if(""===this.opts.element.val()&&""===this.opts.element.text()&&(this.updateSelection([]),this.close(),this.clearSearch()),this.select||""!==this.opts.element.val()){var t=this;this.opts.initSelection.call(null,this.opts.element,function(i){i!==e&&null!==i&&(t.updateSelection(i),t.close(),t.clearSearch())})}},clearSearch:function(){var t=this.getPlaceholder(),i=this.getMaxSearchWidth();t!==e&&0===this.getVal().length&&this.search.hasClass("select2-focused")===!1?(this.search.val(t).addClass("select2-default"),this.search.width(i>0?i:this.container.css("width"))):this.search.val("").width(10)},clearPlaceholder:function(){this.search.hasClass("select2-default")&&this.search.val("").removeClass("select2-default")},opening:function(){this.clearPlaceholder(),this.resizeSearch(),this.parent.opening.apply(this,arguments),this.focusSearch(),""===this.search.val()&&this.nextSearchTerm!=e&&(this.search.val(this.nextSearchTerm),this.search.select()),this.updateResults(!0),this.opts.shouldFocusInput(this)&&this.search.focus(),this.opts.element.trigger(t.Event("select2-open"))},close:function(){this.opened()&&this.parent.close.apply(this,arguments)},focus:function(){this.close(),this.search.focus()},isFocused:function(){return this.search.hasClass("select2-focused")},updateSelection:function(e){var i=[],n=[],s=this;t(e).each(function(){r(s.id(this),i)<0&&(i.push(s.id(this)),n.push(this))}),e=n,this.selection.find(".select2-search-choice").remove(),t(e).each(function(){s.addSelectedChoice(this)}),s.postprocessResults()},tokenize:function(){var t=this.search.val();t=this.opts.tokenizer.call(this,t,this.data(),this.bind(this.onSelect),this.opts),null!=t&&t!=e&&(this.search.val(t),t.length>0&&this.open())},onSelect:function(t,i){this.triggerSelect(t)&&""!==t.text&&(this.addSelectedChoice(t),this.opts.element.trigger({type:"selected",val:this.id(t),choice:t}),this.nextSearchTerm=this.opts.nextSearchTerm(t,this.search.val()),this.moveHighlight(1),(this.select||!this.opts.closeOnSelect)&&this.postprocessResults(t,!1,this.opts.closeOnSelect===!0),this.opts.closeOnSelect?(this.clearSearch(),this.updateResults(),this.close(),this.search.width(10)):this.countSelectableResults()>0?(this.search.width(10),this.resizeSearch(),this.getMaximumSelectionSize()>0&&this.val().length>=this.getMaximumSelectionSize()?this.updateResults(!0):this.nextSearchTerm!=e&&(this.search.val(this.nextSearchTerm),this.updateResults(),this.search.select()),this.positionDropdown()):(this.close(),this.search.width(10)),this.triggerChange({added:t}),i&&i.noFocus||this.focusSearch())},cancel:function(){this.close(),this.focusSearch()},addSelectedChoice:function(i){var n,r,s=!i.locked,o=t("
                                          • "),a=t("
                                          • "),l=s?o:a,u=this.id(i),h=this.getVal();n=this.opts.formatSelection(i,l.find("div"),this.opts.escapeMarkup),n!=e&&l.find("div").replaceWith("
                                            "+n+"
                                            "),r=this.opts.formatSelectionCssClass(i,l.find("div")),r!=e&&l.addClass(r),s&&l.find(".select2-search-choice-close").on("mousedown",g).on("click dblclick",this.bind(function(e){this.isInterfaceEnabled()&&(this.unselect(t(e.target)),this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus"),g(e),this.close(),this.focusSearch())})).on("focus",this.bind(function(){this.isInterfaceEnabled()&&(this.container.addClass("select2-container-active"),this.dropdown.addClass("select2-drop-active"))})),l.data("select2-data",i),l.insertBefore(this.searchContainer),h.push(u),this.setVal(h)},unselect:function(e){var i,n,s=this.getVal();if(e=e.closest(".select2-search-choice"),0===e.length)throw"Invalid argument: "+e+". Must be .select2-search-choice"; -if(i=e.data("select2-data")){var o=t.Event("select2-removing");if(o.val=this.id(i),o.choice=i,this.opts.element.trigger(o),o.isDefaultPrevented())return!1;for(;(n=r(this.id(i),s))>=0;)s.splice(n,1),this.setVal(s),this.select&&this.postprocessResults();return e.remove(),this.opts.element.trigger({type:"select2-removed",val:this.id(i),choice:i}),this.triggerChange({removed:i}),!0}},postprocessResults:function(t,e,i){var n=this.getVal(),s=this.results.find(".select2-result"),o=this.results.find(".select2-result-with-children"),a=this;s.each2(function(t,e){var i=a.id(e.data("select2-data"));r(i,n)>=0&&(e.addClass("select2-selected"),e.find(".select2-result-selectable").addClass("select2-selected"))}),o.each2(function(t,e){e.is(".select2-result-selectable")||0!==e.find(".select2-result-selectable:not(.select2-selected)").length||e.addClass("select2-selected")}),-1==this.highlight()&&i!==!1&&a.highlight(0),!this.opts.createSearchChoice&&!s.filter(".select2-result:not(.select2-selected)").length>0&&(!t||t&&!t.more&&0===this.results.find(".select2-no-results").length)&&S(a.opts.formatNoMatches,"formatNoMatches")&&this.results.append("
                                          • "+k(a.opts.formatNoMatches,a.opts.element,a.search.val())+"
                                          • ")},getMaxSearchWidth:function(){return this.selection.width()-l(this.search)},resizeSearch:function(){var t,e,i,n,r,s=l(this.search);t=v(this.search)+10,e=this.search.offset().left,i=this.selection.width(),n=this.selection.offset().left,r=i-(e-n)-s,t>r&&(r=i-s),40>r&&(r=i-s),0>=r&&(r=t),this.search.width(Math.floor(r))},getVal:function(){var t;return this.select?(t=this.select.val(),null===t?[]:t):(t=this.opts.element.val(),a(t,this.opts.separator))},setVal:function(e){var i;this.select?this.select.val(e):(i=[],t(e).each(function(){r(this,i)<0&&i.push(this)}),this.opts.element.val(0===i.length?"":i.join(this.opts.separator)))},buildChangeDetails:function(t,e){for(var e=e.slice(0),t=t.slice(0),i=0;i0&&i--,t.splice(n,1),n--);return{added:e,removed:t}},val:function(i,n){var r,s=this;if(0===arguments.length)return this.getVal();if(r=this.data(),r.length||(r=[]),!i&&0!==i)return this.opts.element.val(""),this.updateSelection([]),this.clearSearch(),void(n&&this.triggerChange({added:this.data(),removed:r}));if(this.setVal(i),this.select)this.opts.initSelection(this.select,this.bind(this.updateSelection)),n&&this.triggerChange(this.buildChangeDetails(r,this.data()));else{if(this.opts.initSelection===e)throw new Error("val() cannot be called if initSelection() is not defined");this.opts.initSelection(this.opts.element,function(e){var i=t.map(e,s.id);s.setVal(i),s.updateSelection(e),s.clearSearch(),n&&s.triggerChange(s.buildChangeDetails(r,s.data()))})}this.clearSearch()},onSortStart:function(){if(this.select)throw new Error("Sorting of elements is not supported when attached to instead.");this.search.width(0),this.searchContainer.hide()},onSortEnd:function(){var e=[],i=this;this.searchContainer.show(),this.searchContainer.appendTo(this.searchContainer.parent()),this.resizeSearch(),this.selection.find(".select2-search-choice").each(function(){e.push(i.opts.id(t(this).data("select2-data")))}),this.setVal(e),this.triggerChange()},data:function(e,i){var n,r,s=this;return 0===arguments.length?this.selection.children(".select2-search-choice").map(function(){return t(this).data("select2-data")}).get():(r=this.data(),e||(e=[]),n=t.map(e,function(t){return s.opts.id(t)}),this.setVal(n),this.updateSelection(e),this.clearSearch(),i&&this.triggerChange(this.buildChangeDetails(r,this.data())),void 0)}}),t.fn.select2=function(){var i,n,s,o,a,l=Array.prototype.slice.call(arguments,0),u=["val","destroy","opened","open","close","focus","isFocused","container","dropdown","onSortStart","onSortEnd","enable","disable","readonly","positionDropdown","data","search"],h=["opened","isFocused","container","dropdown"],c=["val","data"],d={search:"externalSearch"};return this.each(function(){if(0===l.length||"object"==typeof l[0])i=0===l.length?{}:t.extend({},l[0]),i.element=t(this),"select"===i.element.get(0).tagName.toLowerCase()?a=i.element.prop("multiple"):(a=i.multiple||!1,"tags"in i&&(i.multiple=a=!0)),n=a?new window.Select2["class"].multi:new window.Select2["class"].single,n.init(i);else{if("string"!=typeof l[0])throw"Invalid arguments to select2 plugin: "+l;if(r(l[0],u)<0)throw"Unknown method: "+l[0];if(o=e,n=t(this).data("select2"),n===e)return;if(s=l[0],"container"===s?o=n.container:"dropdown"===s?o=n.dropdown:(d[s]&&(s=d[s]),o=n[s].apply(n,l.slice(1))),r(l[0],h)>=0||r(l[0],c)>=0&&1==l.length)return!1}}),o===e?this:o},t.fn.select2.defaults={width:"copy",loadMorePadding:0,closeOnSelect:!0,openOnEnter:!0,containerCss:{},dropdownCss:{},containerCssClass:"",dropdownCssClass:"",formatResult:function(t,e,i,n){var r=[];return b(t.text,i.term,r,n),r.join("")},formatSelection:function(t,i,n){return t?n(t.text):e},sortResults:function(t,e,i){return t},formatResultCssClass:function(t){return t.css},formatSelectionCssClass:function(t,i){return e},minimumResultsForSearch:0,minimumInputLength:0,maximumInputLength:null,maximumSelectionSize:0,id:function(t){return t==e?null:t.id},matcher:function(t,e){return n(""+e).toUpperCase().indexOf(n(""+t).toUpperCase())>=0},separator:",",tokenSeparators:[],tokenizer:E,escapeMarkup:w,blurOnChange:!1,selectOnBlur:!1,adaptContainerCssClass:function(t){return t},adaptDropdownCssClass:function(t){return null},nextSearchTerm:function(t,i){return e},searchInputPlaceholder:"",createSearchChoicePosition:"top",shouldFocusInput:function(t){var e="ontouchstart"in window||navigator.msMaxTouchPoints>0;return e&&t.opts.minimumResultsForSearch<0?!1:!0}},t.fn.select2.locales=[],t.fn.select2.locales.en={formatMatches:function(t){return 1===t?"One result is available, press enter to select it.":t+" results are available, use up and down arrow keys to navigate."},formatNoMatches:function(){return"No matches found"},formatAjaxError:function(t,e,i){return"Loading failed"},formatInputTooShort:function(t,e){var i=e-t.length;return"Please enter "+i+" or more character"+(1==i?"":"s")},formatInputTooLong:function(t,e){var i=t.length-e;return"Please delete "+i+" character"+(1==i?"":"s")},formatSelectionTooBig:function(t){return"You can only select "+t+" item"+(1==t?"":"s")},formatLoadMore:function(t){return"Loading more results…"},formatSearching:function(){return"Searching…"}},t.extend(t.fn.select2.defaults,t.fn.select2.locales.en),t.fn.select2.ajaxDefaults={transport:t.ajax,params:{type:"GET",cache:!1,dataType:"json"}},window.Select2={query:{ajax:_,local:x,tags:C},util:{debounce:c,markMatch:b,escapeMarkup:w,stripDiacritics:n},"class":{"abstract":M,single:D,multi:P}}}}(t)}).call(e,i(1))},,,,,,,,,,,,,,,,,function(t,e,i){var n,r;n=[i(2),i(3),i(6),i(5)],r=function(t,e,i,n){"use strict";var r="user",s=e.Model.extend(i.LoggableMixin).extend({_logNamespace:r,urlRoot:function(){return Galaxy.root+"api/users"},defaults:{id:null,username:"("+n("anonymous user")+")",email:"",total_disk_usage:0,nice_total_disk_usage:"",quota_percent:null,is_admin:!1},initialize:function(t){this.log("User.initialize:",t),this.on("loaded",function(t,e){this.log(this+" has loaded:",t,e)}),this.on("change",function(t,e){this.log(this+" has changed:",t,e.changes)})},isAnonymous:function(){return!this.get("email")},isAdmin:function(){return this.get("is_admin")},loadFromApi:function(t,i){t=t||s.CURRENT_ID_STR,i=i||{};var n=this,r=i.success;return i.success=function(t,e){n.trigger("loaded",t,e),r&&r(t,e)},t===s.CURRENT_ID_STR&&(i.url=this.urlRoot+"/"+s.CURRENT_ID_STR),e.Model.prototype.fetch.call(this,i)},clearSessionStorage:function(){for(var t in sessionStorage)0===t.indexOf("history:")?sessionStorage.removeItem(t):"history-panel"===t&&sessionStorage.removeItem(t)},toString:function(){var t=[this.get("username")];return this.get("id")&&(t.unshift(this.get("id")),t.push(this.get("email"))),"User("+t.join(":")+")"}});s.CURRENT_ID_STR="current",s.getCurrentUserFromApi=function(t){var e=new s;return e.loadFromApi(s.CURRENT_ID_STR,t),e};e.Collection.extend(i.LoggableMixin).extend({model:s,urlRoot:function(){return Galaxy.root+"api/users"}});return{User:s}}.apply(e,n),!(void 0!==r&&(t.exports=r))},function(t,e,i){var n,r,s;(function(o){!function(o){r=[i(1)],n=o,s="function"==typeof n?n.apply(e,r):n,!(void 0!==s&&(t.exports=s))}(function(t){"use_strict";function e(t,e){i(t).find(".tag-name").each(function(){i(this).click(function(){var t=i(this).text(),n=t.split(":");return e(n[0],n[1]),!0})})}var i=t;return t.fn.autocomplete_tagging=function(n){function r(t){i(t).mouseenter(function(){i(this).attr("src",l.delete_tag_img_rollover)}),i(t).mouseleave(function(){i(this).attr("src",l.delete_tag_img)}),i(t).click(function(){var e=i(this).parent(),n=e.find(".tag-name").eq(0),r=n.text(),s=r.split(":"),o=s[0],a=s[1],u=e.prev();e.remove(),delete l.tags[o];var d=l.get_toggle_link_text_fn(l.tags);return c.text(d),i.ajax({url:l.ajax_delete_tag_url,data:{tag_name:o},error:function(){l.tags[o]=a,u.hasClass("tag-button")?u.after(e):h.prepend(e),alert("Remove tag failed"),c.text(l.get_toggle_link_text_fn(l.tags)),t.mouseenter(function(){i(this).attr("src",l.delete_tag_img_rollover)}),t.mouseleave(function(){i(this).attr("src",l.delete_tag_img)})},success:function(){}}),!0})}function s(t){var e=i("").attr("src",l.delete_tag_img).addClass("delete-tag-img");r(e);var n=i("").text(t).addClass("tag-name");n.click(function(){var e=t.split(":");return l.tag_click_fn(e[0],e[1]),!0});var s=i("").addClass("tag-button");return s.append(n),l.editable&&s.append(e),s}var a={get_toggle_link_text_fn:function(t){var e="",i=o.size(t);return e=i>0?i+(i>1?" Tags":" Tag"):"Add tags"},tag_click_fn:function(t,e){},editable:!0,input_size:20,in_form:!1,tags:{},use_toggle_link:!0,item_id:"",add_tag_img:"",add_tag_img_rollover:"",delete_tag_img:"",ajax_autocomplete_tag_url:"",ajax_retag_url:"",ajax_delete_tag_url:"",ajax_add_tag_url:""},l=t.extend(a,n),u=i(this),h=u.find(".tag-area"),c=u.find(".toggle-link"),d=u.find(".tag-input"),p=u.find(".add-tag-button");c.click(function(){var t;return t=h.is(":hidden")?function(){var t=i(this).find(".tag-button").length;0===t&&h.click()}:function(){h.blur()},h.slideToggle("fast",t),i(this)}),l.editable&&d.hide(),d.keyup(function(t){if(27===t.keyCode)i(this).trigger("blur");else if(13===t.keyCode||188===t.keyCode||32===t.keyCode){var e=this.value;if(-1!==e.indexOf(": ",e.length-2))return this.value=e.substring(0,e.length-1),!1;if((188===t.keyCode||32===t.keyCode)&&(e=e.substring(0,e.length-1)),e=i.trim(e),e.length<2)return!1;this.value="";var n=s(e),r=h.children(".tag-button");if(0!==r.length){var o=r.slice(r.length-1);o.after(n)}else h.prepend(n);var a=e.split(":");l.tags[a[0]]=a[1];var u=l.get_toggle_link_text_fn(l.tags);c.text(u);var d=i(this);return i.ajax({url:l.ajax_add_tag_url,data:{new_tag:e},error:function(){n.remove(),delete l.tags[a[0]];var t=l.get_toggle_link_text_fn(l.tags);c.text(t),alert("Add tag failed")},success:function(){d.data("autocompleter").cacheFlush()}}),!1}});var f=function(t,e,i,n,r){var s=n.split(":");return 1===s.length?s[0]:s[1]},g={selectFirst:!1,formatItem:f,autoFill:!1,highlight:!1};d.autocomplete(l.ajax_autocomplete_tag_url,g),u.find(".delete-tag-img").each(function(){r(i(this))}),e(i(this),l.tag_click_fn),p.click(function(){return i(this).hide(),h.click(),!1}),l.editable&&(h.bind("blur",function(t){o.size(l.tags)>0&&(p.show(),d.hide(),h.removeClass("active-tag-area"))}),h.click(function(t){var e=i(this).hasClass("active-tag-area");if(i(t.target).hasClass("delete-tag-img")&&!e)return!1;if(i(t.target).hasClass("tag-name")&&!e)return!1;i(this).addClass("active-tag-area"),p.hide(),d.show(),d.focus();var n=function(t){var e=function(t,e){t.attr("id");e!==t&&(t.blur(),i(window).unbind("click.tagging_blur"),i(this).addClass("tooltip"))};e(h,i(t.target))};return i(window).bind("click.tagging_blur",n),!1})),l.use_toggle_link&&h.hide()},e})}).call(e,i(2))},,,function(t,e,i){var n,r;n=[],r=function(){function t(t,i){var n=void 0!==t.prototype?t.prototype:t;return void 0!==i&&(n._logNamespace=i),e.forEach(function(t){n[t]=function(){return this.logger?this.logger.emit?this.logger.emit(t,this._logNamespace,arguments):this.logger[t]?this.logger[t].apply(this.logger,arguments):void 0:void 0}}),t}var e=["log","debug","info","warn","error","metric"];return t}.apply(e,n),!(void 0!==r&&(t.exports=r))},,,,,function(t,e,i){var n,r;(function(s,o){n=[i(8)],r=function(t){var e=s.View.extend({initialize:function(){this.modal=null},makeModalIframe:function(i){var n=window.Galaxy.config.communication_server_host,r=window.Galaxy.config.communication_server_port,s=escape(window.Galaxy.user.attributes.username),a=escape(window.Galaxy.config.persistent_communication_rooms),l="?username="+s+"&persistent_communication_rooms="+a,u=n+":"+r+l,h=null,c=null,d='',p='',f=350,g=600,m="ui-modal chat-modal";return o(".chat-modal").length>0&&o(".chat-modal").remove(),e.modal=new t.View({body:d,height:f,width:g,closing_events:!0,title_separator:!1,cls:m}),e.modal.show(),h=o(".chat-modal .modal-header"),c=o(".chat-modal .modal-body"),h.addClass("modal-header-body"),c.addClass("modal-header-body"),h.find("h4").remove(),h.removeAttr("min-height padding border"),h.append(p),o(".close-modal").click(function(t){o(".chat-modal").css("display","none")}),o(".expand-compress-modal").click(function(t){o(".expand-compress-modal").hasClass("fa-expand")?(o(".chat-modal .modal-dialog").width("1000px"),o(".chat-modal .modal-body").height("575px"),o(".expand-compress-modal").removeClass("fa-expand").addClass("fa-compress"),o(".expand-compress-modal").attr("title","Minimize"),o(".expand-compress-modal").css("margin-left","96.2%")):(o(".chat-modal .modal-dialog").width(g+"px"),o(".chat-modal .modal-body").height(f+"px"),o(".expand-compress-modal").removeClass("fa-compress").addClass("fa-expand"),o(".expand-compress-modal").attr("title","Maximize"),o(".expand-compress-modal").css("margin-left","93.2%"))}),this},render:function(){var t=this,e={};return e={id:"show-chat-online",icon:"fa-comment-o",tooltip:"Chat online",visible:!1,onclick:t.makeModalIframe}}});return{GenericNavView:e}}.apply(e,n),!(void 0!==r&&(t.exports=r))}).call(e,i(3),i(1))},function(t,e,i){var n,r;(function(s,o){n=[i(4),i(93),i(94),i(124)],r=function(t,e,i,n){var r=s.View.extend({initialize:function(t){var r=this;this.options=t,this.setElement(this._template()),this.$navbarBrandLink=this.$(".navbar-brand-link"),this.$navbarBrandImage=this.$(".navbar-brand-image"),this.$navbarBrandTitle=this.$(".navbar-brand-title"),this.$navbarTabs=this.$(".navbar-tabs"),this.$quoteMeter=this.$(".quota-meter-container"),this.collection=new e.Collection,this.collection.on("add",function(t){r.$navbarTabs.append(new e.Tab({model:t}).render().$el)}).on("reset",function(){r.$navbarTabs.empty()}).on("dispatch",function(t){r.collection.each(function(e){t(e)})}).fetch(this.options),Galaxy.frame=this.frame=new i({collection:this.collection}),Galaxy.quotaMeter=this.quotaMeter=new n.UserQuotaMeter({model:Galaxy.user,el:this.$quoteMeter}),o(window).on("click",function(t){var e=o(t.target).closest("a[download]");1==e.length&&(0===o("iframe[id=download]").length&&o("body").append(o("',p='',f=350,g=600,m="ui-modal chat-modal";return o(".chat-modal").length>0&&o(".chat-modal").remove(),e.modal=new t.View({body:d,height:f,width:g,closing_events:!0,title_separator:!1,cls:m}),e.modal.show(),c=o(".chat-modal .modal-header"),u=o(".chat-modal .modal-body"),c.addClass("modal-header-body"),u.addClass("modal-header-body"),c.find("h4").remove(),c.removeAttr("min-height padding border"),c.append(p),o(".close-modal").click(function(t){o(".chat-modal").css("display","none")}),o(".expand-compress-modal").click(function(t){o(".expand-compress-modal").hasClass("fa-expand")?(o(".chat-modal .modal-dialog").width("1000px"),o(".chat-modal .modal-body").height("575px"),o(".expand-compress-modal").removeClass("fa-expand").addClass("fa-compress"),o(".expand-compress-modal").attr("title","Minimize"),o(".expand-compress-modal").css("margin-left","96.2%")):(o(".chat-modal .modal-dialog").width(g+"px"),o(".chat-modal .modal-body").height(f+"px"),o(".expand-compress-modal").removeClass("fa-compress").addClass("fa-expand"),o(".expand-compress-modal").attr("title","Maximize"),o(".expand-compress-modal").css("margin-left","93.2%"))}),this},render:function(){var t=this,e={};return e={id:"show-chat-online",icon:"fa-comment-o",tooltip:"Chat online",visible:!1,onclick:t.makeModalIframe}}});return{GenericNavView:e}}.apply(e,n),!(void 0!==r&&(t.exports=r))}).call(e,i(3),i(1))},function(t,e,i){var n,r;(function(s,o){n=[i(4),i(93),i(94),i(124)],r=function(t,e,i,n){var r=s.View.extend({initialize:function(t){var r=this;this.options=t,this.setElement(this._template()),this.$navbarBrandLink=this.$(".navbar-brand-link"),this.$navbarBrandImage=this.$(".navbar-brand-image"),this.$navbarBrandTitle=this.$(".navbar-brand-title"),this.$navbarTabs=this.$(".navbar-tabs"),this.$quoteMeter=this.$(".quota-meter-container"),this.collection=new e.Collection,this.collection.on("add",function(t){r.$navbarTabs.append(new e.Tab({model:t}).render().$el)}).on("reset",function(){r.$navbarTabs.empty()}).on("dispatch",function(t){r.collection.each(function(e){t(e)})}).fetch(this.options),Galaxy.frame=this.frame=new i({collection:this.collection}),Galaxy.quotaMeter=this.quotaMeter=new n.UserQuotaMeter({model:Galaxy.user,el:this.$quoteMeter}),o(window).on("click",function(t){var e=o(t.target).closest("a[download]");1==e.length&&(0===o("iframe[id=download]").length&&o("body").append(o("
                                            \"\n\t )\n\t });\n\t modal.show( { backdrop: true } );\n\t}\n\t\n\t\n\t// ============================================================================\n\t return {\n\t Modal : Modal,\n\t hide_modal : hide_modal,\n\t show_modal : show_modal,\n\t show_message : show_message,\n\t show_in_overlay : show_in_overlay,\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\n\n/***/ },\n/* 59 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, _, $) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(92),\n\t __webpack_require__(10),\n\t __webpack_require__(8),\n\t __webpack_require__(6)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( Masthead, Panel, Modal, BaseMVC ) {\n\t\n\t// ============================================================================\n\tvar PageLayoutView = Backbone.View.extend( BaseMVC.LoggableMixin ).extend({\n\t _logNamespace : 'layout',\n\t\n\t el : 'body',\n\t className : 'full-content',\n\t\n\t _panelIds : [\n\t 'left', 'center', 'right'\n\t ],\n\t\n\t defaultOptions : {\n\t message_box_visible : false,\n\t message_box_content : '',\n\t message_box_class : 'info',\n\t show_inactivity_warning : false,\n\t inactivity_box_content : ''\n\t },\n\t\n\t initialize : function( options ) {\n\t // TODO: remove globals\n\t this.log( this + '.initialize:', options );\n\t _.extend( this, _.pick( options, this._panelIds ) );\n\t this.options = _.defaults( _.omit( options.config, this._panelIds ), this.defaultOptions );\n\t Galaxy.modal = this.modal = new Modal.View();\n\t this.masthead = new Masthead.View( this.options );\n\t this.$el.attr( 'scroll', 'no' );\n\t this.$el.html( this._template() );\n\t this.$el.append( this.masthead.frame.$el );\n\t this.$( '#masthead' ).replaceWith( this.masthead.$el );\n\t this.$el.append( this.modal.$el );\n\t this.$messagebox = this.$( '#messagebox' );\n\t this.$inactivebox = this.$( '#inactivebox' );\n\t },\n\t\n\t render : function() {\n\t // TODO: Remove this line after select2 update\n\t $( '.select2-hidden-accessible' ).remove();\n\t this.log( this + '.render:' );\n\t this.masthead.render();\n\t this.renderMessageBox();\n\t this.renderInactivityBox();\n\t this.renderPanels();\n\t this._checkCommunicationServerOnline();\n\t return this;\n\t },\n\t\n\t /** Render message box */\n\t renderMessageBox : function() {\n\t if ( this.options.message_box_visible ){\n\t var content = this.options.message_box_content || '';\n\t var level = this.options.message_box_class || 'info';\n\t this.$el.addClass( 'has-message-box' );\n\t this.$messagebox\n\t .attr( 'class', 'panel-' + level + '-message' )\n\t .html( content )\n\t .toggle( !!content )\n\t .show();\n\t } else {\n\t this.$el.removeClass( 'has-message-box' );\n\t this.$messagebox.hide();\n\t }\n\t return this;\n\t },\n\t\n\t /** Render inactivity warning */\n\t renderInactivityBox : function() {\n\t if( this.options.show_inactivity_warning ){\n\t var content = this.options.inactivity_box_content || '';\n\t var verificationLink = $( '' ).attr( 'href', Galaxy.root + 'user/resend_verification' ).text( 'Resend verification' );\n\t this.$el.addClass( 'has-inactivity-box' );\n\t this.$inactivebox\n\t .html( content + ' ' )\n\t .append( verificationLink )\n\t .toggle( !!content )\n\t .show();\n\t } else {\n\t this.$el.removeClass( 'has-inactivity-box' );\n\t this.$inactivebox.hide();\n\t }\n\t return this;\n\t },\n\t\n\t /** Render panels */\n\t renderPanels : function() {\n\t var page = this;\n\t this._panelIds.forEach( function( panelId ){\n\t if( _.has( page, panelId ) ){\n\t page[ panelId ].setElement( '#' + panelId );\n\t page[ panelId ].render();\n\t }\n\t });\n\t if( !this.left ){\n\t this.center.$el.css( 'left', 0 );\n\t }\n\t if( !this.right ){\n\t this.center.$el.css( 'right', 0 );\n\t }\n\t return this;\n\t },\n\t\n\t /** body template */\n\t _template: function() {\n\t return [\n\t '
                                            ',\n\t '
                                            ',\n\t '
                                            ',\n\t '
                                            ',\n\t '
                                            ',\n\t this.left? '
                                            ' : '',\n\t this.center? '
                                            ' : '',\n\t this.right? '
                                            ' : '',\n\t '
                                            ',\n\t '
                                            ',\n\t ].join('');\n\t },\n\t\n\t /** hide both side panels if previously shown */\n\t hideSidePanels : function(){\n\t if( this.left ){\n\t this.left.hide();\n\t }\n\t if( this.right ){\n\t this.right.hide();\n\t }\n\t },\n\t\n\t toString : function() { return 'PageLayoutView'; },\n\t\n\t /** Check if the communication server is online and show the icon otherwise hide the icon */\n\t _checkCommunicationServerOnline: function(){\n\t var host = window.Galaxy.config.communication_server_host,\n\t port = window.Galaxy.config.communication_server_port,\n\t $chat_icon_element = $( \"#show-chat-online\" );\n\t /** Check if the user has deactivated the communication in it's personal settings */\n\t if (window.Galaxy.user.attributes.preferences !== undefined && window.Galaxy.user.attributes.preferences.communication_server === '1') {\n\t // See if the configured communication server is available\n\t $.ajax({\n\t url: host + \":\" + port,\n\t })\n\t .success( function( data ) { \n\t // enable communication only when a user is logged in\n\t if( window.Galaxy.user.id !== null ) {\n\t if( $chat_icon_element.css( \"visibility\") === \"hidden\" ) {\n\t $chat_icon_element.css( \"visibility\", \"visible\" ); \n\t }\n\t }\n\t })\n\t .error( function( data ) { \n\t // hide the communication icon if the communication server is not available\n\t $chat_icon_element.css( \"visibility\", \"hidden\" ); \n\t });\n\t } else {\n\t $chat_icon_element.css( \"visibility\", \"hidden\" ); \n\t }\n\t },\n\t});\n\t\n\t// ============================================================================\n\t return {\n\t PageLayoutView: PageLayoutView\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(2), __webpack_require__(1)))\n\n/***/ },\n/* 60 */\n/***/ function(module, exports, __webpack_require__) {\n\n\t/* WEBPACK VAR INJECTION */(function(jQuery) {/* ========================================================================\n\t * bootstrap-tour - v0.10.2\n\t * http://bootstraptour.com\n\t * ========================================================================\n\t * Copyright 2012-2015 Ulrich Sossou\n\t *\n\t * ========================================================================\n\t * Licensed under the MIT License (the \"License\");\n\t * you may not use this file except in compliance with the License.\n\t * You may obtain a copy of the License at\n\t *\n\t * https://opensource.org/licenses/MIT\n\t *\n\t * Unless required by applicable law or agreed to in writing, software\n\t * distributed under the License is distributed on an \"AS IS\" BASIS,\n\t * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n\t * See the License for the specific language governing permissions and\n\t * limitations under the License.\n\t * ========================================================================\n\t */\n\t\n\t(function($, window) {\n\t var Tour, document;\n\t document = window.document;\n\t Tour = (function() {\n\t function Tour(options) {\n\t var storage;\n\t try {\n\t storage = window.localStorage;\n\t } catch (_error) {\n\t storage = false;\n\t }\n\t this._options = $.extend({\n\t name: 'tour',\n\t steps: [],\n\t container: 'body',\n\t autoscroll: true,\n\t keyboard: true,\n\t storage: storage,\n\t debug: false,\n\t backdrop: false,\n\t backdropContainer: 'body',\n\t backdropPadding: 0,\n\t redirect: true,\n\t orphan: false,\n\t duration: false,\n\t delay: false,\n\t basePath: '',\n\t template: '

                                            ',\n\t afterSetState: function(key, value) {},\n\t afterGetState: function(key, value) {},\n\t afterRemoveState: function(key) {},\n\t onStart: function(tour) {},\n\t onEnd: function(tour) {},\n\t onShow: function(tour) {},\n\t onShown: function(tour) {},\n\t onHide: function(tour) {},\n\t onHidden: function(tour) {},\n\t onNext: function(tour) {},\n\t onPrev: function(tour) {},\n\t onPause: function(tour, duration) {},\n\t onResume: function(tour, duration) {},\n\t onRedirectError: function(tour) {}\n\t }, options);\n\t this._force = false;\n\t this._inited = false;\n\t this._current = null;\n\t this.backdrop = {\n\t overlay: null,\n\t $element: null,\n\t $background: null,\n\t backgroundShown: false,\n\t overlayElementShown: false\n\t };\n\t this;\n\t }\n\t\n\t Tour.prototype.addSteps = function(steps) {\n\t var step, _i, _len;\n\t for (_i = 0, _len = steps.length; _i < _len; _i++) {\n\t step = steps[_i];\n\t this.addStep(step);\n\t }\n\t return this;\n\t };\n\t\n\t Tour.prototype.addStep = function(step) {\n\t this._options.steps.push(step);\n\t return this;\n\t };\n\t\n\t Tour.prototype.getStep = function(i) {\n\t if (this._options.steps[i] != null) {\n\t return $.extend({\n\t id: \"step-\" + i,\n\t path: '',\n\t host: '',\n\t placement: 'right',\n\t title: '',\n\t content: '

                                            ',\n\t next: i === this._options.steps.length - 1 ? -1 : i + 1,\n\t prev: i - 1,\n\t animation: true,\n\t container: this._options.container,\n\t autoscroll: this._options.autoscroll,\n\t backdrop: this._options.backdrop,\n\t backdropContainer: this._options.backdropContainer,\n\t backdropPadding: this._options.backdropPadding,\n\t redirect: this._options.redirect,\n\t reflexElement: this._options.steps[i].element,\n\t orphan: this._options.orphan,\n\t duration: this._options.duration,\n\t delay: this._options.delay,\n\t template: this._options.template,\n\t onShow: this._options.onShow,\n\t onShown: this._options.onShown,\n\t onHide: this._options.onHide,\n\t onHidden: this._options.onHidden,\n\t onNext: this._options.onNext,\n\t onPrev: this._options.onPrev,\n\t onPause: this._options.onPause,\n\t onResume: this._options.onResume,\n\t onRedirectError: this._options.onRedirectError\n\t }, this._options.steps[i]);\n\t }\n\t };\n\t\n\t Tour.prototype.init = function(force) {\n\t this._force = force;\n\t if (this.ended()) {\n\t this._debug('Tour ended, init prevented.');\n\t return this;\n\t }\n\t this.setCurrentStep();\n\t this._initMouseNavigation();\n\t this._initKeyboardNavigation();\n\t this._onResize((function(_this) {\n\t return function() {\n\t return _this.showStep(_this._current);\n\t };\n\t })(this));\n\t if (this._current !== null) {\n\t this.showStep(this._current);\n\t }\n\t this._inited = true;\n\t return this;\n\t };\n\t\n\t Tour.prototype.start = function(force) {\n\t var promise;\n\t if (force == null) {\n\t force = false;\n\t }\n\t if (!this._inited) {\n\t this.init(force);\n\t }\n\t if (this._current === null) {\n\t promise = this._makePromise(this._options.onStart != null ? this._options.onStart(this) : void 0);\n\t this._callOnPromiseDone(promise, this.showStep, 0);\n\t }\n\t return this;\n\t };\n\t\n\t Tour.prototype.next = function() {\n\t var promise;\n\t promise = this.hideStep(this._current);\n\t return this._callOnPromiseDone(promise, this._showNextStep);\n\t };\n\t\n\t Tour.prototype.prev = function() {\n\t var promise;\n\t promise = this.hideStep(this._current);\n\t return this._callOnPromiseDone(promise, this._showPrevStep);\n\t };\n\t\n\t Tour.prototype.goTo = function(i) {\n\t var promise;\n\t promise = this.hideStep(this._current);\n\t return this._callOnPromiseDone(promise, this.showStep, i);\n\t };\n\t\n\t Tour.prototype.end = function() {\n\t var endHelper, promise;\n\t endHelper = (function(_this) {\n\t return function(e) {\n\t $(document).off(\"click.tour-\" + _this._options.name);\n\t $(document).off(\"keyup.tour-\" + _this._options.name);\n\t $(window).off(\"resize.tour-\" + _this._options.name);\n\t _this._setState('end', 'yes');\n\t _this._inited = false;\n\t _this._force = false;\n\t _this._clearTimer();\n\t if (_this._options.onEnd != null) {\n\t return _this._options.onEnd(_this);\n\t }\n\t };\n\t })(this);\n\t promise = this.hideStep(this._current);\n\t return this._callOnPromiseDone(promise, endHelper);\n\t };\n\t\n\t Tour.prototype.ended = function() {\n\t return !this._force && !!this._getState('end');\n\t };\n\t\n\t Tour.prototype.restart = function() {\n\t this._removeState('current_step');\n\t this._removeState('end');\n\t this._removeState('redirect_to');\n\t return this.start();\n\t };\n\t\n\t Tour.prototype.pause = function() {\n\t var step;\n\t step = this.getStep(this._current);\n\t if (!(step && step.duration)) {\n\t return this;\n\t }\n\t this._paused = true;\n\t this._duration -= new Date().getTime() - this._start;\n\t window.clearTimeout(this._timer);\n\t this._debug(\"Paused/Stopped step \" + (this._current + 1) + \" timer (\" + this._duration + \" remaining).\");\n\t if (step.onPause != null) {\n\t return step.onPause(this, this._duration);\n\t }\n\t };\n\t\n\t Tour.prototype.resume = function() {\n\t var step;\n\t step = this.getStep(this._current);\n\t if (!(step && step.duration)) {\n\t return this;\n\t }\n\t this._paused = false;\n\t this._start = new Date().getTime();\n\t this._duration = this._duration || step.duration;\n\t this._timer = window.setTimeout((function(_this) {\n\t return function() {\n\t if (_this._isLast()) {\n\t return _this.next();\n\t } else {\n\t return _this.end();\n\t }\n\t };\n\t })(this), this._duration);\n\t this._debug(\"Started step \" + (this._current + 1) + \" timer with duration \" + this._duration);\n\t if ((step.onResume != null) && this._duration !== step.duration) {\n\t return step.onResume(this, this._duration);\n\t }\n\t };\n\t\n\t Tour.prototype.hideStep = function(i) {\n\t var hideStepHelper, promise, step;\n\t step = this.getStep(i);\n\t if (!step) {\n\t return;\n\t }\n\t this._clearTimer();\n\t promise = this._makePromise(step.onHide != null ? step.onHide(this, i) : void 0);\n\t hideStepHelper = (function(_this) {\n\t return function(e) {\n\t var $element;\n\t $element = $(step.element);\n\t if (!($element.data('bs.popover') || $element.data('popover'))) {\n\t $element = $('body');\n\t }\n\t $element.popover('destroy').removeClass(\"tour-\" + _this._options.name + \"-element tour-\" + _this._options.name + \"-\" + i + \"-element\");\n\t $element.removeData('bs.popover');\n\t if (step.reflex) {\n\t $(step.reflexElement).removeClass('tour-step-element-reflex').off(\"\" + (_this._reflexEvent(step.reflex)) + \".tour-\" + _this._options.name);\n\t }\n\t if (step.backdrop) {\n\t _this._hideBackdrop();\n\t }\n\t if (step.onHidden != null) {\n\t return step.onHidden(_this);\n\t }\n\t };\n\t })(this);\n\t this._callOnPromiseDone(promise, hideStepHelper);\n\t return promise;\n\t };\n\t\n\t Tour.prototype.showStep = function(i) {\n\t var promise, showStepHelper, skipToPrevious, step;\n\t if (this.ended()) {\n\t this._debug('Tour ended, showStep prevented.');\n\t return this;\n\t }\n\t step = this.getStep(i);\n\t if (!step) {\n\t return;\n\t }\n\t skipToPrevious = i < this._current;\n\t promise = this._makePromise(step.onShow != null ? step.onShow(this, i) : void 0);\n\t showStepHelper = (function(_this) {\n\t return function(e) {\n\t var path, showPopoverAndOverlay;\n\t _this.setCurrentStep(i);\n\t path = (function() {\n\t switch ({}.toString.call(step.path)) {\n\t case '[object Function]':\n\t return step.path();\n\t case '[object String]':\n\t return this._options.basePath + step.path;\n\t default:\n\t return step.path;\n\t }\n\t }).call(_this);\n\t if (_this._isRedirect(step.host, path, document.location)) {\n\t _this._redirect(step, i, path);\n\t if (!_this._isJustPathHashDifferent(step.host, path, document.location)) {\n\t return;\n\t }\n\t }\n\t if (_this._isOrphan(step)) {\n\t if (step.orphan === false) {\n\t _this._debug(\"Skip the orphan step \" + (_this._current + 1) + \".\\nOrphan option is false and the element does not exist or is hidden.\");\n\t if (skipToPrevious) {\n\t _this._showPrevStep();\n\t } else {\n\t _this._showNextStep();\n\t }\n\t return;\n\t }\n\t _this._debug(\"Show the orphan step \" + (_this._current + 1) + \". Orphans option is true.\");\n\t }\n\t if (step.backdrop) {\n\t _this._showBackdrop(step);\n\t }\n\t showPopoverAndOverlay = function() {\n\t if (_this.getCurrentStep() !== i || _this.ended()) {\n\t return;\n\t }\n\t if ((step.element != null) && step.backdrop) {\n\t _this._showOverlayElement(step);\n\t }\n\t _this._showPopover(step, i);\n\t if (step.onShown != null) {\n\t step.onShown(_this);\n\t }\n\t return _this._debug(\"Step \" + (_this._current + 1) + \" of \" + _this._options.steps.length);\n\t };\n\t if (step.autoscroll) {\n\t _this._scrollIntoView(step.element, showPopoverAndOverlay);\n\t } else {\n\t showPopoverAndOverlay();\n\t }\n\t if (step.duration) {\n\t return _this.resume();\n\t }\n\t };\n\t })(this);\n\t if (step.delay) {\n\t this._debug(\"Wait \" + step.delay + \" milliseconds to show the step \" + (this._current + 1));\n\t window.setTimeout((function(_this) {\n\t return function() {\n\t return _this._callOnPromiseDone(promise, showStepHelper);\n\t };\n\t })(this), step.delay);\n\t } else {\n\t this._callOnPromiseDone(promise, showStepHelper);\n\t }\n\t return promise;\n\t };\n\t\n\t Tour.prototype.getCurrentStep = function() {\n\t return this._current;\n\t };\n\t\n\t Tour.prototype.setCurrentStep = function(value) {\n\t if (value != null) {\n\t this._current = value;\n\t this._setState('current_step', value);\n\t } else {\n\t this._current = this._getState('current_step');\n\t this._current = this._current === null ? null : parseInt(this._current, 10);\n\t }\n\t return this;\n\t };\n\t\n\t Tour.prototype.redraw = function() {\n\t return this._showOverlayElement(this.getStep(this.getCurrentStep()).element, true);\n\t };\n\t\n\t Tour.prototype._setState = function(key, value) {\n\t var e, keyName;\n\t if (this._options.storage) {\n\t keyName = \"\" + this._options.name + \"_\" + key;\n\t try {\n\t this._options.storage.setItem(keyName, value);\n\t } catch (_error) {\n\t e = _error;\n\t if (e.code === DOMException.QUOTA_EXCEEDED_ERR) {\n\t this._debug('LocalStorage quota exceeded. State storage failed.');\n\t }\n\t }\n\t return this._options.afterSetState(keyName, value);\n\t } else {\n\t if (this._state == null) {\n\t this._state = {};\n\t }\n\t return this._state[key] = value;\n\t }\n\t };\n\t\n\t Tour.prototype._removeState = function(key) {\n\t var keyName;\n\t if (this._options.storage) {\n\t keyName = \"\" + this._options.name + \"_\" + key;\n\t this._options.storage.removeItem(keyName);\n\t return this._options.afterRemoveState(keyName);\n\t } else {\n\t if (this._state != null) {\n\t return delete this._state[key];\n\t }\n\t }\n\t };\n\t\n\t Tour.prototype._getState = function(key) {\n\t var keyName, value;\n\t if (this._options.storage) {\n\t keyName = \"\" + this._options.name + \"_\" + key;\n\t value = this._options.storage.getItem(keyName);\n\t } else {\n\t if (this._state != null) {\n\t value = this._state[key];\n\t }\n\t }\n\t if (value === void 0 || value === 'null') {\n\t value = null;\n\t }\n\t this._options.afterGetState(key, value);\n\t return value;\n\t };\n\t\n\t Tour.prototype._showNextStep = function() {\n\t var promise, showNextStepHelper, step;\n\t step = this.getStep(this._current);\n\t showNextStepHelper = (function(_this) {\n\t return function(e) {\n\t return _this.showStep(step.next);\n\t };\n\t })(this);\n\t promise = this._makePromise(step.onNext != null ? step.onNext(this) : void 0);\n\t return this._callOnPromiseDone(promise, showNextStepHelper);\n\t };\n\t\n\t Tour.prototype._showPrevStep = function() {\n\t var promise, showPrevStepHelper, step;\n\t step = this.getStep(this._current);\n\t showPrevStepHelper = (function(_this) {\n\t return function(e) {\n\t return _this.showStep(step.prev);\n\t };\n\t })(this);\n\t promise = this._makePromise(step.onPrev != null ? step.onPrev(this) : void 0);\n\t return this._callOnPromiseDone(promise, showPrevStepHelper);\n\t };\n\t\n\t Tour.prototype._debug = function(text) {\n\t if (this._options.debug) {\n\t return window.console.log(\"Bootstrap Tour '\" + this._options.name + \"' | \" + text);\n\t }\n\t };\n\t\n\t Tour.prototype._isRedirect = function(host, path, location) {\n\t var currentPath;\n\t if (host !== '') {\n\t if (this._isHostDifferent(host, location.href)) {\n\t return true;\n\t }\n\t }\n\t currentPath = [location.pathname, location.search, location.hash].join('');\n\t return (path != null) && path !== '' && (({}.toString.call(path) === '[object RegExp]' && !path.test(currentPath)) || ({}.toString.call(path) === '[object String]' && this._isPathDifferent(path, currentPath)));\n\t };\n\t\n\t Tour.prototype._isHostDifferent = function(host, currentURL) {\n\t return this._getProtocol(host) !== this._getProtocol(currentURL) || this._getHost(host) !== this._getHost(currentURL);\n\t };\n\t\n\t Tour.prototype._isPathDifferent = function(path, currentPath) {\n\t return this._getPath(path) !== this._getPath(currentPath) || !this._equal(this._getQuery(path), this._getQuery(currentPath)) || !this._equal(this._getHash(path), this._getHash(currentPath));\n\t };\n\t\n\t Tour.prototype._isJustPathHashDifferent = function(host, path, location) {\n\t var currentPath;\n\t if (host !== '') {\n\t if (this._isHostDifferent(host, location.href)) {\n\t return false;\n\t }\n\t }\n\t currentPath = [location.pathname, location.search, location.hash].join('');\n\t if ({}.toString.call(path) === '[object String]') {\n\t return this._getPath(path) === this._getPath(currentPath) && this._equal(this._getQuery(path), this._getQuery(currentPath)) && !this._equal(this._getHash(path), this._getHash(currentPath));\n\t }\n\t return false;\n\t };\n\t\n\t Tour.prototype._redirect = function(step, i, path) {\n\t if ($.isFunction(step.redirect)) {\n\t return step.redirect.call(this, path);\n\t } else if (step.redirect === true) {\n\t this._debug(\"Redirect to \" + step.host + path);\n\t if (this._getState('redirect_to') === (\"\" + i)) {\n\t this._debug(\"Error redirection loop to \" + path);\n\t this._removeState('redirect_to');\n\t if (step.onRedirectError != null) {\n\t return step.onRedirectError(this);\n\t }\n\t } else {\n\t this._setState('redirect_to', \"\" + i);\n\t return document.location.href = \"\" + step.host + path;\n\t }\n\t }\n\t };\n\t\n\t Tour.prototype._isOrphan = function(step) {\n\t return (step.element == null) || !$(step.element).length || $(step.element).is(':hidden') && ($(step.element)[0].namespaceURI !== 'http://www.w3.org/2000/svg');\n\t };\n\t\n\t Tour.prototype._isLast = function() {\n\t return this._current < this._options.steps.length - 1;\n\t };\n\t\n\t Tour.prototype._showPopover = function(step, i) {\n\t var $element, $tip, isOrphan, options, shouldAddSmart;\n\t $(\".tour-\" + this._options.name).remove();\n\t options = $.extend({}, this._options);\n\t isOrphan = this._isOrphan(step);\n\t step.template = this._template(step, i);\n\t if (isOrphan) {\n\t step.element = 'body';\n\t step.placement = 'top';\n\t }\n\t $element = $(step.element);\n\t $element.addClass(\"tour-\" + this._options.name + \"-element tour-\" + this._options.name + \"-\" + i + \"-element\");\n\t if (step.options) {\n\t $.extend(options, step.options);\n\t }\n\t if (step.reflex && !isOrphan) {\n\t $(step.reflexElement).addClass('tour-step-element-reflex').off(\"\" + (this._reflexEvent(step.reflex)) + \".tour-\" + this._options.name).on(\"\" + (this._reflexEvent(step.reflex)) + \".tour-\" + this._options.name, (function(_this) {\n\t return function() {\n\t if (_this._isLast()) {\n\t return _this.next();\n\t } else {\n\t return _this.end();\n\t }\n\t };\n\t })(this));\n\t }\n\t shouldAddSmart = step.smartPlacement === true && step.placement.search(/auto/i) === -1;\n\t $element.popover({\n\t placement: shouldAddSmart ? \"auto \" + step.placement : step.placement,\n\t trigger: 'manual',\n\t title: step.title,\n\t content: step.content,\n\t html: true,\n\t animation: step.animation,\n\t container: step.container,\n\t template: step.template,\n\t selector: step.element\n\t }).popover('show');\n\t $tip = $element.data('bs.popover') ? $element.data('bs.popover').tip() : $element.data('popover').tip();\n\t $tip.attr('id', step.id);\n\t this._reposition($tip, step);\n\t if (isOrphan) {\n\t return this._center($tip);\n\t }\n\t };\n\t\n\t Tour.prototype._template = function(step, i) {\n\t var $navigation, $next, $prev, $resume, $template, template;\n\t template = step.template;\n\t if (this._isOrphan(step) && {}.toString.call(step.orphan) !== '[object Boolean]') {\n\t template = step.orphan;\n\t }\n\t $template = $.isFunction(template) ? $(template(i, step)) : $(template);\n\t $navigation = $template.find('.popover-navigation');\n\t $prev = $navigation.find('[data-role=\"prev\"]');\n\t $next = $navigation.find('[data-role=\"next\"]');\n\t $resume = $navigation.find('[data-role=\"pause-resume\"]');\n\t if (this._isOrphan(step)) {\n\t $template.addClass('orphan');\n\t }\n\t $template.addClass(\"tour-\" + this._options.name + \" tour-\" + this._options.name + \"-\" + i);\n\t if (step.reflex) {\n\t $template.addClass(\"tour-\" + this._options.name + \"-reflex\");\n\t }\n\t if (step.prev < 0) {\n\t $prev.addClass('disabled');\n\t $prev.prop('disabled', true);\n\t }\n\t if (step.next < 0) {\n\t $next.addClass('disabled');\n\t $next.prop('disabled', true);\n\t }\n\t if (!step.duration) {\n\t $resume.remove();\n\t }\n\t return $template.clone().wrap('
                                            ').parent().html();\n\t };\n\t\n\t Tour.prototype._reflexEvent = function(reflex) {\n\t if ({}.toString.call(reflex) === '[object Boolean]') {\n\t return 'click';\n\t } else {\n\t return reflex;\n\t }\n\t };\n\t\n\t Tour.prototype._reposition = function($tip, step) {\n\t var offsetBottom, offsetHeight, offsetRight, offsetWidth, originalLeft, originalTop, tipOffset;\n\t offsetWidth = $tip[0].offsetWidth;\n\t offsetHeight = $tip[0].offsetHeight;\n\t tipOffset = $tip.offset();\n\t originalLeft = tipOffset.left;\n\t originalTop = tipOffset.top;\n\t offsetBottom = $(document).outerHeight() - tipOffset.top - $tip.outerHeight();\n\t if (offsetBottom < 0) {\n\t tipOffset.top = tipOffset.top + offsetBottom;\n\t }\n\t offsetRight = $('html').outerWidth() - tipOffset.left - $tip.outerWidth();\n\t if (offsetRight < 0) {\n\t tipOffset.left = tipOffset.left + offsetRight;\n\t }\n\t if (tipOffset.top < 0) {\n\t tipOffset.top = 0;\n\t }\n\t if (tipOffset.left < 0) {\n\t tipOffset.left = 0;\n\t }\n\t $tip.offset(tipOffset);\n\t if (step.placement === 'bottom' || step.placement === 'top') {\n\t if (originalLeft !== tipOffset.left) {\n\t return this._replaceArrow($tip, (tipOffset.left - originalLeft) * 2, offsetWidth, 'left');\n\t }\n\t } else {\n\t if (originalTop !== tipOffset.top) {\n\t return this._replaceArrow($tip, (tipOffset.top - originalTop) * 2, offsetHeight, 'top');\n\t }\n\t }\n\t };\n\t\n\t Tour.prototype._center = function($tip) {\n\t return $tip.css('top', $(window).outerHeight() / 2 - $tip.outerHeight() / 2);\n\t };\n\t\n\t Tour.prototype._replaceArrow = function($tip, delta, dimension, position) {\n\t return $tip.find('.arrow').css(position, delta ? 50 * (1 - delta / dimension) + '%' : '');\n\t };\n\t\n\t Tour.prototype._scrollIntoView = function(element, callback) {\n\t var $element, $window, counter, offsetTop, scrollTop, windowHeight;\n\t $element = $(element);\n\t if (!$element.length) {\n\t return callback();\n\t }\n\t $window = $(window);\n\t offsetTop = $element.offset().top;\n\t windowHeight = $window.height();\n\t scrollTop = Math.max(0, offsetTop - (windowHeight / 2));\n\t this._debug(\"Scroll into view. ScrollTop: \" + scrollTop + \". Element offset: \" + offsetTop + \". Window height: \" + windowHeight + \".\");\n\t counter = 0;\n\t return $('body, html').stop(true, true).animate({\n\t scrollTop: Math.ceil(scrollTop)\n\t }, (function(_this) {\n\t return function() {\n\t if (++counter === 2) {\n\t callback();\n\t return _this._debug(\"Scroll into view.\\nAnimation end element offset: \" + ($element.offset().top) + \".\\nWindow height: \" + ($window.height()) + \".\");\n\t }\n\t };\n\t })(this));\n\t };\n\t\n\t Tour.prototype._onResize = function(callback, timeout) {\n\t return $(window).on(\"resize.tour-\" + this._options.name, function() {\n\t clearTimeout(timeout);\n\t return timeout = setTimeout(callback, 100);\n\t });\n\t };\n\t\n\t Tour.prototype._initMouseNavigation = function() {\n\t var _this;\n\t _this = this;\n\t return $(document).off(\"click.tour-\" + this._options.name, \".popover.tour-\" + this._options.name + \" *[data-role='prev']\").off(\"click.tour-\" + this._options.name, \".popover.tour-\" + this._options.name + \" *[data-role='next']\").off(\"click.tour-\" + this._options.name, \".popover.tour-\" + this._options.name + \" *[data-role='end']\").off(\"click.tour-\" + this._options.name, \".popover.tour-\" + this._options.name + \" *[data-role='pause-resume']\").on(\"click.tour-\" + this._options.name, \".popover.tour-\" + this._options.name + \" *[data-role='next']\", (function(_this) {\n\t return function(e) {\n\t e.preventDefault();\n\t return _this.next();\n\t };\n\t })(this)).on(\"click.tour-\" + this._options.name, \".popover.tour-\" + this._options.name + \" *[data-role='prev']\", (function(_this) {\n\t return function(e) {\n\t e.preventDefault();\n\t return _this.prev();\n\t };\n\t })(this)).on(\"click.tour-\" + this._options.name, \".popover.tour-\" + this._options.name + \" *[data-role='end']\", (function(_this) {\n\t return function(e) {\n\t e.preventDefault();\n\t return _this.end();\n\t };\n\t })(this)).on(\"click.tour-\" + this._options.name, \".popover.tour-\" + this._options.name + \" *[data-role='pause-resume']\", function(e) {\n\t var $this;\n\t e.preventDefault();\n\t $this = $(this);\n\t $this.text(_this._paused ? $this.data('pause-text') : $this.data('resume-text'));\n\t if (_this._paused) {\n\t return _this.resume();\n\t } else {\n\t return _this.pause();\n\t }\n\t });\n\t };\n\t\n\t Tour.prototype._initKeyboardNavigation = function() {\n\t if (!this._options.keyboard) {\n\t return;\n\t }\n\t return $(document).on(\"keyup.tour-\" + this._options.name, (function(_this) {\n\t return function(e) {\n\t if (!e.which) {\n\t return;\n\t }\n\t switch (e.which) {\n\t case 39:\n\t e.preventDefault();\n\t if (_this._isLast()) {\n\t return _this.next();\n\t } else {\n\t return _this.end();\n\t }\n\t break;\n\t case 37:\n\t e.preventDefault();\n\t if (_this._current > 0) {\n\t return _this.prev();\n\t }\n\t break;\n\t case 27:\n\t e.preventDefault();\n\t return _this.end();\n\t }\n\t };\n\t })(this));\n\t };\n\t\n\t Tour.prototype._makePromise = function(result) {\n\t if (result && $.isFunction(result.then)) {\n\t return result;\n\t } else {\n\t return null;\n\t }\n\t };\n\t\n\t Tour.prototype._callOnPromiseDone = function(promise, cb, arg) {\n\t if (promise) {\n\t return promise.then((function(_this) {\n\t return function(e) {\n\t return cb.call(_this, arg);\n\t };\n\t })(this));\n\t } else {\n\t return cb.call(this, arg);\n\t }\n\t };\n\t\n\t Tour.prototype._showBackdrop = function(step) {\n\t if (this.backdrop.backgroundShown) {\n\t return;\n\t }\n\t this.backdrop = $('
                                            ', {\n\t \"class\": 'tour-backdrop'\n\t });\n\t this.backdrop.backgroundShown = true;\n\t return $(step.backdropContainer).append(this.backdrop);\n\t };\n\t\n\t Tour.prototype._hideBackdrop = function() {\n\t this._hideOverlayElement();\n\t return this._hideBackground();\n\t };\n\t\n\t Tour.prototype._hideBackground = function() {\n\t if (this.backdrop) {\n\t this.backdrop.remove();\n\t this.backdrop.overlay = null;\n\t return this.backdrop.backgroundShown = false;\n\t }\n\t };\n\t\n\t Tour.prototype._showOverlayElement = function(step, force) {\n\t var $element, elementData;\n\t $element = $(step.element);\n\t if (!$element || $element.length === 0 || this.backdrop.overlayElementShown && !force) {\n\t return;\n\t }\n\t if (!this.backdrop.overlayElementShown) {\n\t this.backdrop.$element = $element.addClass('tour-step-backdrop');\n\t this.backdrop.$background = $('
                                            ', {\n\t \"class\": 'tour-step-background'\n\t });\n\t this.backdrop.$background.appendTo(step.backdropContainer);\n\t this.backdrop.overlayElementShown = true;\n\t }\n\t elementData = {\n\t width: $element.innerWidth(),\n\t height: $element.innerHeight(),\n\t offset: $element.offset()\n\t };\n\t if (step.backdropPadding) {\n\t elementData = this._applyBackdropPadding(step.backdropPadding, elementData);\n\t }\n\t return this.backdrop.$background.width(elementData.width).height(elementData.height).offset(elementData.offset);\n\t };\n\t\n\t Tour.prototype._hideOverlayElement = function() {\n\t if (!this.backdrop.overlayElementShown) {\n\t return;\n\t }\n\t this.backdrop.$element.removeClass('tour-step-backdrop');\n\t this.backdrop.$background.remove();\n\t this.backdrop.$element = null;\n\t this.backdrop.$background = null;\n\t return this.backdrop.overlayElementShown = false;\n\t };\n\t\n\t Tour.prototype._applyBackdropPadding = function(padding, data) {\n\t if (typeof padding === 'object') {\n\t if (padding.top == null) {\n\t padding.top = 0;\n\t }\n\t if (padding.right == null) {\n\t padding.right = 0;\n\t }\n\t if (padding.bottom == null) {\n\t padding.bottom = 0;\n\t }\n\t if (padding.left == null) {\n\t padding.left = 0;\n\t }\n\t data.offset.top = data.offset.top - padding.top;\n\t data.offset.left = data.offset.left - padding.left;\n\t data.width = data.width + padding.left + padding.right;\n\t data.height = data.height + padding.top + padding.bottom;\n\t } else {\n\t data.offset.top = data.offset.top - padding;\n\t data.offset.left = data.offset.left - padding;\n\t data.width = data.width + (padding * 2);\n\t data.height = data.height + (padding * 2);\n\t }\n\t return data;\n\t };\n\t\n\t Tour.prototype._clearTimer = function() {\n\t window.clearTimeout(this._timer);\n\t this._timer = null;\n\t return this._duration = null;\n\t };\n\t\n\t Tour.prototype._getProtocol = function(url) {\n\t url = url.split('://');\n\t if (url.length > 1) {\n\t return url[0];\n\t } else {\n\t return 'http';\n\t }\n\t };\n\t\n\t Tour.prototype._getHost = function(url) {\n\t url = url.split('//');\n\t url = url.length > 1 ? url[1] : url[0];\n\t return url.split('/')[0];\n\t };\n\t\n\t Tour.prototype._getPath = function(path) {\n\t return path.replace(/\\/?$/, '').split('?')[0].split('#')[0];\n\t };\n\t\n\t Tour.prototype._getQuery = function(path) {\n\t return this._getParams(path, '?');\n\t };\n\t\n\t Tour.prototype._getHash = function(path) {\n\t return this._getParams(path, '#');\n\t };\n\t\n\t Tour.prototype._getParams = function(path, start) {\n\t var param, params, paramsObject, _i, _len;\n\t params = path.split(start);\n\t if (params.length === 1) {\n\t return {};\n\t }\n\t params = params[1].split('&');\n\t paramsObject = {};\n\t for (_i = 0, _len = params.length; _i < _len; _i++) {\n\t param = params[_i];\n\t param = param.split('=');\n\t paramsObject[param[0]] = param[1] || '';\n\t }\n\t return paramsObject;\n\t };\n\t\n\t Tour.prototype._equal = function(obj1, obj2) {\n\t var k, v;\n\t if ({}.toString.call(obj1) === '[object Object]' && {}.toString.call(obj2) === '[object Object]') {\n\t for (k in obj1) {\n\t v = obj1[k];\n\t if (obj2[k] !== v) {\n\t return false;\n\t }\n\t }\n\t for (k in obj2) {\n\t v = obj2[k];\n\t if (obj1[k] !== v) {\n\t return false;\n\t }\n\t }\n\t return true;\n\t }\n\t return obj1 === obj2;\n\t };\n\t\n\t return Tour;\n\t\n\t })();\n\t return window.Tour = Tour;\n\t})(jQuery, window);\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(1)))\n\n/***/ },\n/* 61 */\n/***/ function(module, exports, __webpack_require__) {\n\n\t/* WEBPACK VAR INJECTION */(function(jQuery) {/*! jQuery UI - v1.9.1 - 2012-10-29\n\t* http://jqueryui.com\n\t* Includes: jquery.ui.core.js, jquery.ui.widget.js, jquery.ui.mouse.js, jquery.ui.position.js, jquery.ui.autocomplete.js, jquery.ui.button.js, jquery.ui.menu.js, jquery.ui.slider.js\n\t* Copyright (c) 2012 jQuery Foundation and other contributors Licensed MIT */\n\t\n\t(function(e,t){function i(t,n){var r,i,o,u=t.nodeName.toLowerCase();return\"area\"===u?(r=t.parentNode,i=r.name,!t.href||!i||r.nodeName.toLowerCase()!==\"map\"?!1:(o=e(\"img[usemap=#\"+i+\"]\")[0],!!o&&s(o))):(/input|select|textarea|button|object/.test(u)?!t.disabled:\"a\"===u?t.href||n:n)&&s(t)}function s(t){return e.expr.filters.visible(t)&&!e(t).parents().andSelf().filter(function(){return e.css(this,\"visibility\")===\"hidden\"}).length}var n=0,r=/^ui-id-\\d+$/;e.ui=e.ui||{};if(e.ui.version)return;e.extend(e.ui,{version:\"1.9.1\",keyCode:{BACKSPACE:8,COMMA:188,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SPACE:32,TAB:9,UP:38}}),e.fn.extend({_focus:e.fn.focus,focus:function(t,n){return typeof t==\"number\"?this.each(function(){var r=this;setTimeout(function(){e(r).focus(),n&&n.call(r)},t)}):this._focus.apply(this,arguments)},scrollParent:function(){var t;return e.ui.ie&&/(static|relative)/.test(this.css(\"position\"))||/absolute/.test(this.css(\"position\"))?t=this.parents().filter(function(){return/(relative|absolute|fixed)/.test(e.css(this,\"position\"))&&/(auto|scroll)/.test(e.css(this,\"overflow\")+e.css(this,\"overflow-y\")+e.css(this,\"overflow-x\"))}).eq(0):t=this.parents().filter(function(){return/(auto|scroll)/.test(e.css(this,\"overflow\")+e.css(this,\"overflow-y\")+e.css(this,\"overflow-x\"))}).eq(0),/fixed/.test(this.css(\"position\"))||!t.length?e(document):t},zIndex:function(n){if(n!==t)return this.css(\"zIndex\",n);if(this.length){var r=e(this[0]),i,s;while(r.length&&r[0]!==document){i=r.css(\"position\");if(i===\"absolute\"||i===\"relative\"||i===\"fixed\"){s=parseInt(r.css(\"zIndex\"),10);if(!isNaN(s)&&s!==0)return s}r=r.parent()}}return 0},uniqueId:function(){return this.each(function(){this.id||(this.id=\"ui-id-\"+ ++n)})},removeUniqueId:function(){return this.each(function(){r.test(this.id)&&e(this).removeAttr(\"id\")})}}),e(\"\").outerWidth(1).jquery||e.each([\"Width\",\"Height\"],function(n,r){function u(t,n,r,s){return e.each(i,function(){n-=parseFloat(e.css(t,\"padding\"+this))||0,r&&(n-=parseFloat(e.css(t,\"border\"+this+\"Width\"))||0),s&&(n-=parseFloat(e.css(t,\"margin\"+this))||0)}),n}var i=r===\"Width\"?[\"Left\",\"Right\"]:[\"Top\",\"Bottom\"],s=r.toLowerCase(),o={innerWidth:e.fn.innerWidth,innerHeight:e.fn.innerHeight,outerWidth:e.fn.outerWidth,outerHeight:e.fn.outerHeight};e.fn[\"inner\"+r]=function(n){return n===t?o[\"inner\"+r].call(this):this.each(function(){e(this).css(s,u(this,n)+\"px\")})},e.fn[\"outer\"+r]=function(t,n){return typeof t!=\"number\"?o[\"outer\"+r].call(this,t):this.each(function(){e(this).css(s,u(this,t,!0,n)+\"px\")})}}),e.extend(e.expr[\":\"],{data:e.expr.createPseudo?e.expr.createPseudo(function(t){return function(n){return!!e.data(n,t)}}):function(t,n,r){return!!e.data(t,r[3])},focusable:function(t){return i(t,!isNaN(e.attr(t,\"tabindex\")))},tabbable:function(t){var n=e.attr(t,\"tabindex\"),r=isNaN(n);return(r||n>=0)&&i(t,!r)}}),e(function(){var t=document.body,n=t.appendChild(n=document.createElement(\"div\"));n.offsetHeight,e.extend(n.style,{minHeight:\"100px\",height:\"auto\",padding:0,borderWidth:0}),e.support.minHeight=n.offsetHeight===100,e.support.selectstart=\"onselectstart\"in n,t.removeChild(n).style.display=\"none\"}),function(){var t=/msie ([\\w.]+)/.exec(navigator.userAgent.toLowerCase())||[];e.ui.ie=t.length?!0:!1,e.ui.ie6=parseFloat(t[1],10)===6}(),e.fn.extend({disableSelection:function(){return this.bind((e.support.selectstart?\"selectstart\":\"mousedown\")+\".ui-disableSelection\",function(e){e.preventDefault()})},enableSelection:function(){return this.unbind(\".ui-disableSelection\")}}),e.extend(e.ui,{plugin:{add:function(t,n,r){var i,s=e.ui[t].prototype;for(i in r)s.plugins[i]=s.plugins[i]||[],s.plugins[i].push([n,r[i]])},call:function(e,t,n){var r,i=e.plugins[t];if(!i||!e.element[0].parentNode||e.element[0].parentNode.nodeType===11)return;for(r=0;r0?!0:(t[r]=1,i=t[r]>0,t[r]=0,i)},isOverAxis:function(e,t,n){return e>t&&e\",options:{disabled:!1,create:null},_createWidget:function(t,r){r=e(r||this.defaultElement||this)[0],this.element=e(r),this.uuid=n++,this.eventNamespace=\".\"+this.widgetName+this.uuid,this.options=e.widget.extend({},this.options,this._getCreateOptions(),t),this.bindings=e(),this.hoverable=e(),this.focusable=e(),r!==this&&(e.data(r,this.widgetName,this),e.data(r,this.widgetFullName,this),this._on(this.element,{remove:function(e){e.target===r&&this.destroy()}}),this.document=e(r.style?r.ownerDocument:r.document||r),this.window=e(this.document[0].defaultView||this.document[0].parentWindow)),this._create(),this._trigger(\"create\",null,this._getCreateEventData()),this._init()},_getCreateOptions:e.noop,_getCreateEventData:e.noop,_create:e.noop,_init:e.noop,destroy:function(){this._destroy(),this.element.unbind(this.eventNamespace).removeData(this.widgetName).removeData(this.widgetFullName).removeData(e.camelCase(this.widgetFullName)),this.widget().unbind(this.eventNamespace).removeAttr(\"aria-disabled\").removeClass(this.widgetFullName+\"-disabled \"+\"ui-state-disabled\"),this.bindings.unbind(this.eventNamespace),this.hoverable.removeClass(\"ui-state-hover\"),this.focusable.removeClass(\"ui-state-focus\")},_destroy:e.noop,widget:function(){return this.element},option:function(n,r){var i=n,s,o,u;if(arguments.length===0)return e.widget.extend({},this.options);if(typeof n==\"string\"){i={},s=n.split(\".\"),n=s.shift();if(s.length){o=i[n]=e.widget.extend({},this.options[n]);for(u=0;u=9||!!t.button?this._mouseStarted?(this._mouseDrag(t),t.preventDefault()):(this._mouseDistanceMet(t)&&this._mouseDelayMet(t)&&(this._mouseStarted=this._mouseStart(this._mouseDownEvent,t)!==!1,this._mouseStarted?this._mouseDrag(t):this._mouseUp(t)),!this._mouseStarted):this._mouseUp(t)},_mouseUp:function(t){return e(document).unbind(\"mousemove.\"+this.widgetName,this._mouseMoveDelegate).unbind(\"mouseup.\"+this.widgetName,this._mouseUpDelegate),this._mouseStarted&&(this._mouseStarted=!1,t.target===this._mouseDownEvent.target&&e.data(t.target,this.widgetName+\".preventClickEvent\",!0),this._mouseStop(t)),!1},_mouseDistanceMet:function(e){return Math.max(Math.abs(this._mouseDownEvent.pageX-e.pageX),Math.abs(this._mouseDownEvent.pageY-e.pageY))>=this.options.distance},_mouseDelayMet:function(e){return this.mouseDelayMet},_mouseStart:function(e){},_mouseDrag:function(e){},_mouseStop:function(e){},_mouseCapture:function(e){return!0}})})(jQuery);(function(e,t){function h(e,t,n){return[parseInt(e[0],10)*(l.test(e[0])?t/100:1),parseInt(e[1],10)*(l.test(e[1])?n/100:1)]}function p(t,n){return parseInt(e.css(t,n),10)||0}e.ui=e.ui||{};var n,r=Math.max,i=Math.abs,s=Math.round,o=/left|center|right/,u=/top|center|bottom/,a=/[\\+\\-]\\d+%?/,f=/^\\w+/,l=/%$/,c=e.fn.position;e.position={scrollbarWidth:function(){if(n!==t)return n;var r,i,s=e(\"
                                            \"),o=s.children()[0];return e(\"body\").append(s),r=o.offsetWidth,s.css(\"overflow\",\"scroll\"),i=o.offsetWidth,r===i&&(i=s[0].clientWidth),s.remove(),n=r-i},getScrollInfo:function(t){var n=t.isWindow?\"\":t.element.css(\"overflow-x\"),r=t.isWindow?\"\":t.element.css(\"overflow-y\"),i=n===\"scroll\"||n===\"auto\"&&t.width0?\"right\":\"center\",vertical:u<0?\"top\":o>0?\"bottom\":\"middle\"};lr(i(o),i(u))?h.important=\"horizontal\":h.important=\"vertical\",t.using.call(this,e,h)}),a.offset(e.extend(C,{using:u}))})},e.ui.position={fit:{left:function(e,t){var n=t.within,i=n.isWindow?n.scrollLeft:n.offset.left,s=n.width,o=e.left-t.collisionPosition.marginLeft,u=i-o,a=o+t.collisionWidth-s-i,f;t.collisionWidth>s?u>0&&a<=0?(f=e.left+u+t.collisionWidth-s-i,e.left+=u-f):a>0&&u<=0?e.left=i:u>a?e.left=i+s-t.collisionWidth:e.left=i:u>0?e.left+=u:a>0?e.left-=a:e.left=r(e.left-o,e.left)},top:function(e,t){var n=t.within,i=n.isWindow?n.scrollTop:n.offset.top,s=t.within.height,o=e.top-t.collisionPosition.marginTop,u=i-o,a=o+t.collisionHeight-s-i,f;t.collisionHeight>s?u>0&&a<=0?(f=e.top+u+t.collisionHeight-s-i,e.top+=u-f):a>0&&u<=0?e.top=i:u>a?e.top=i+s-t.collisionHeight:e.top=i:u>0?e.top+=u:a>0?e.top-=a:e.top=r(e.top-o,e.top)}},flip:{left:function(e,t){var n=t.within,r=n.offset.left+n.scrollLeft,s=n.width,o=n.isWindow?n.scrollLeft:n.offset.left,u=e.left-t.collisionPosition.marginLeft,a=u-o,f=u+t.collisionWidth-s-o,l=t.my[0]===\"left\"?-t.elemWidth:t.my[0]===\"right\"?t.elemWidth:0,c=t.at[0]===\"left\"?t.targetWidth:t.at[0]===\"right\"?-t.targetWidth:0,h=-2*t.offset[0],p,d;if(a<0){p=e.left+l+c+h+t.collisionWidth-s-r;if(p<0||p0){d=e.left-t.collisionPosition.marginLeft+l+c+h-o;if(d>0||i(d)a&&(v<0||v0&&(d=e.top-t.collisionPosition.marginTop+c+h+p-o,e.top+c+h+p>f&&(d>0||i(d)10&&i<11,t.innerHTML=\"\",n.removeChild(t)}(),e.uiBackCompat!==!1&&function(e){var n=e.fn.position;e.fn.position=function(r){if(!r||!r.offset)return n.call(this,r);var i=r.offset.split(\" \"),s=r.at.split(\" \");return i.length===1&&(i[1]=i[0]),/^\\d/.test(i[0])&&(i[0]=\"+\"+i[0]),/^\\d/.test(i[1])&&(i[1]=\"+\"+i[1]),s.length===1&&(/left|center|right/.test(s[0])?s[1]=\"center\":(s[1]=s[0],s[0]=\"center\")),n.call(this,e.extend(r,{at:s[0]+i[0]+\" \"+s[1]+i[1],offset:t}))}}(jQuery)})(jQuery);(function(e,t){var n=0;e.widget(\"ui.autocomplete\",{version:\"1.9.1\",defaultElement:\"\",options:{appendTo:\"body\",autoFocus:!1,delay:300,minLength:1,position:{my:\"left top\",at:\"left bottom\",collision:\"none\"},source:null,change:null,close:null,focus:null,open:null,response:null,search:null,select:null},pending:0,_create:function(){var t,n,r;this.isMultiLine=this._isMultiLine(),this.valueMethod=this.element[this.element.is(\"input,textarea\")?\"val\":\"text\"],this.isNewMenu=!0,this.element.addClass(\"ui-autocomplete-input\").attr(\"autocomplete\",\"off\"),this._on(this.element,{keydown:function(i){if(this.element.prop(\"readOnly\")){t=!0,r=!0,n=!0;return}t=!1,r=!1,n=!1;var s=e.ui.keyCode;switch(i.keyCode){case s.PAGE_UP:t=!0,this._move(\"previousPage\",i);break;case s.PAGE_DOWN:t=!0,this._move(\"nextPage\",i);break;case s.UP:t=!0,this._keyEvent(\"previous\",i);break;case s.DOWN:t=!0,this._keyEvent(\"next\",i);break;case s.ENTER:case s.NUMPAD_ENTER:this.menu.active&&(t=!0,i.preventDefault(),this.menu.select(i));break;case s.TAB:this.menu.active&&this.menu.select(i);break;case s.ESCAPE:this.menu.element.is(\":visible\")&&(this._value(this.term),this.close(i),i.preventDefault());break;default:n=!0,this._searchTimeout(i)}},keypress:function(r){if(t){t=!1,r.preventDefault();return}if(n)return;var i=e.ui.keyCode;switch(r.keyCode){case i.PAGE_UP:this._move(\"previousPage\",r);break;case i.PAGE_DOWN:this._move(\"nextPage\",r);break;case i.UP:this._keyEvent(\"previous\",r);break;case i.DOWN:this._keyEvent(\"next\",r)}},input:function(e){if(r){r=!1,e.preventDefault();return}this._searchTimeout(e)},focus:function(){this.selectedItem=null,this.previous=this._value()},blur:function(e){if(this.cancelBlur){delete this.cancelBlur;return}clearTimeout(this.searching),this.close(e),this._change(e)}}),this._initSource(),this.menu=e(\"
                                              \").addClass(\"ui-autocomplete\").appendTo(this.document.find(this.options.appendTo||\"body\")[0]).menu({input:e(),role:null}).zIndex(this.element.zIndex()+1).hide().data(\"menu\"),this._on(this.menu.element,{mousedown:function(t){t.preventDefault(),this.cancelBlur=!0,this._delay(function(){delete this.cancelBlur});var n=this.menu.element[0];e(t.target).closest(\".ui-menu-item\").length||this._delay(function(){var t=this;this.document.one(\"mousedown\",function(r){r.target!==t.element[0]&&r.target!==n&&!e.contains(n,r.target)&&t.close()})})},menufocus:function(t,n){if(this.isNewMenu){this.isNewMenu=!1;if(t.originalEvent&&/^mouse/.test(t.originalEvent.type)){this.menu.blur(),this.document.one(\"mousemove\",function(){e(t.target).trigger(t.originalEvent)});return}}var r=n.item.data(\"ui-autocomplete-item\")||n.item.data(\"item.autocomplete\");!1!==this._trigger(\"focus\",t,{item:r})?t.originalEvent&&/^key/.test(t.originalEvent.type)&&this._value(r.value):this.liveRegion.text(r.value)},menuselect:function(e,t){var n=t.item.data(\"ui-autocomplete-item\")||t.item.data(\"item.autocomplete\"),r=this.previous;this.element[0]!==this.document[0].activeElement&&(this.element.focus(),this.previous=r,this._delay(function(){this.previous=r,this.selectedItem=n})),!1!==this._trigger(\"select\",e,{item:n})&&this._value(n.value),this.term=this._value(),this.close(e),this.selectedItem=n}}),this.liveRegion=e(\"\",{role:\"status\",\"aria-live\":\"polite\"}).addClass(\"ui-helper-hidden-accessible\").insertAfter(this.element),e.fn.bgiframe&&this.menu.element.bgiframe(),this._on(this.window,{beforeunload:function(){this.element.removeAttr(\"autocomplete\")}})},_destroy:function(){clearTimeout(this.searching),this.element.removeClass(\"ui-autocomplete-input\").removeAttr(\"autocomplete\"),this.menu.element.remove(),this.liveRegion.remove()},_setOption:function(e,t){this._super(e,t),e===\"source\"&&this._initSource(),e===\"appendTo\"&&this.menu.element.appendTo(this.document.find(t||\"body\")[0]),e===\"disabled\"&&t&&this.xhr&&this.xhr.abort()},_isMultiLine:function(){return this.element.is(\"textarea\")?!0:this.element.is(\"input\")?!1:this.element.prop(\"isContentEditable\")},_initSource:function(){var t,n,r=this;e.isArray(this.options.source)?(t=this.options.source,this.source=function(n,r){r(e.ui.autocomplete.filter(t,n.term))}):typeof this.options.source==\"string\"?(n=this.options.source,this.source=function(t,i){r.xhr&&r.xhr.abort(),r.xhr=e.ajax({url:n,data:t,dataType:\"json\",success:function(e){i(e)},error:function(){i([])}})}):this.source=this.options.source},_searchTimeout:function(e){clearTimeout(this.searching),this.searching=this._delay(function(){this.term!==this._value()&&(this.selectedItem=null,this.search(null,e))},this.options.delay)},search:function(e,t){e=e!=null?e:this._value(),this.term=this._value();if(e.length\").append(e(\"\").text(n.label)).appendTo(t)},_move:function(e,t){if(!this.menu.element.is(\":visible\")){this.search(null,t);return}if(this.menu.isFirstItem()&&/^previous/.test(e)||this.menu.isLastItem()&&/^next/.test(e)){this._value(this.term),this.menu.blur();return}this.menu[e](t)},widget:function(){return this.menu.element},_value:function(){return this.valueMethod.apply(this.element,arguments)},_keyEvent:function(e,t){if(!this.isMultiLine||this.menu.element.is(\":visible\"))this._move(e,t),t.preventDefault()}}),e.extend(e.ui.autocomplete,{escapeRegex:function(e){return e.replace(/[\\-\\[\\]{}()*+?.,\\\\\\^$|#\\s]/g,\"\\\\$&\")},filter:function(t,n){var r=new RegExp(e.ui.autocomplete.escapeRegex(n),\"i\");return e.grep(t,function(e){return r.test(e.label||e.value||e)})}}),e.widget(\"ui.autocomplete\",e.ui.autocomplete,{options:{messages:{noResults:\"No search results.\",results:function(e){return e+(e>1?\" results are\":\" result is\")+\" available, use up and down arrow keys to navigate.\"}}},__response:function(e){var t;this._superApply(arguments);if(this.options.disabled||this.cancelSearch)return;e&&e.length?t=this.options.messages.results(e.length):t=this.options.messages.noResults,this.liveRegion.text(t)}})})(jQuery);(function(e,t){var n,r,i,s,o=\"ui-button ui-widget ui-state-default ui-corner-all\",u=\"ui-state-hover ui-state-active \",a=\"ui-button-icons-only ui-button-icon-only ui-button-text-icons ui-button-text-icon-primary ui-button-text-icon-secondary ui-button-text-only\",f=function(){var t=e(this).find(\":ui-button\");setTimeout(function(){t.button(\"refresh\")},1)},l=function(t){var n=t.name,r=t.form,i=e([]);return n&&(r?i=e(r).find(\"[name='\"+n+\"']\"):i=e(\"[name='\"+n+\"']\",t.ownerDocument).filter(function(){return!this.form})),i};e.widget(\"ui.button\",{version:\"1.9.1\",defaultElement:\"
                                            \"\n )\n });\n modal.show( { backdrop: true } );\n}\n\n\n// ============================================================================\n return {\n Modal : Modal,\n hide_modal : hide_modal,\n show_modal : show_modal,\n show_message : show_message,\n show_in_overlay : show_in_overlay,\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/layout/modal.js\n ** module id = 58\n ** module chunks = 2\n **/","define([\n 'layout/masthead',\n 'layout/panel',\n 'mvc/ui/ui-modal',\n 'mvc/base-mvc'\n], function( Masthead, Panel, Modal, BaseMVC ) {\n\n// ============================================================================\nvar PageLayoutView = Backbone.View.extend( BaseMVC.LoggableMixin ).extend({\n _logNamespace : 'layout',\n\n el : 'body',\n className : 'full-content',\n\n _panelIds : [\n 'left', 'center', 'right'\n ],\n\n defaultOptions : {\n message_box_visible : false,\n message_box_content : '',\n message_box_class : 'info',\n show_inactivity_warning : false,\n inactivity_box_content : ''\n },\n\n initialize : function( options ) {\n // TODO: remove globals\n this.log( this + '.initialize:', options );\n _.extend( this, _.pick( options, this._panelIds ) );\n this.options = _.defaults( _.omit( options.config, this._panelIds ), this.defaultOptions );\n Galaxy.modal = this.modal = new Modal.View();\n this.masthead = new Masthead.View( this.options );\n this.$el.attr( 'scroll', 'no' );\n this.$el.html( this._template() );\n this.$el.append( this.masthead.frame.$el );\n this.$( '#masthead' ).replaceWith( this.masthead.$el );\n this.$el.append( this.modal.$el );\n this.$messagebox = this.$( '#messagebox' );\n this.$inactivebox = this.$( '#inactivebox' );\n },\n\n render : function() {\n // TODO: Remove this line after select2 update\n $( '.select2-hidden-accessible' ).remove();\n this.log( this + '.render:' );\n this.masthead.render();\n this.renderMessageBox();\n this.renderInactivityBox();\n this.renderPanels();\n this._checkCommunicationServerOnline();\n return this;\n },\n\n /** Render message box */\n renderMessageBox : function() {\n if ( this.options.message_box_visible ){\n var content = this.options.message_box_content || '';\n var level = this.options.message_box_class || 'info';\n this.$el.addClass( 'has-message-box' );\n this.$messagebox\n .attr( 'class', 'panel-' + level + '-message' )\n .html( content )\n .toggle( !!content )\n .show();\n } else {\n this.$el.removeClass( 'has-message-box' );\n this.$messagebox.hide();\n }\n return this;\n },\n\n /** Render inactivity warning */\n renderInactivityBox : function() {\n if( this.options.show_inactivity_warning ){\n var content = this.options.inactivity_box_content || '';\n var verificationLink = $( '
                                            ' ).attr( 'href', Galaxy.root + 'user/resend_verification' ).text( 'Resend verification' );\n this.$el.addClass( 'has-inactivity-box' );\n this.$inactivebox\n .html( content + ' ' )\n .append( verificationLink )\n .toggle( !!content )\n .show();\n } else {\n this.$el.removeClass( 'has-inactivity-box' );\n this.$inactivebox.hide();\n }\n return this;\n },\n\n /** Render panels */\n renderPanels : function() {\n var page = this;\n this._panelIds.forEach( function( panelId ){\n if( _.has( page, panelId ) ){\n page[ panelId ].setElement( '#' + panelId );\n page[ panelId ].render();\n }\n });\n if( !this.left ){\n this.center.$el.css( 'left', 0 );\n }\n if( !this.right ){\n this.center.$el.css( 'right', 0 );\n }\n return this;\n },\n\n /** body template */\n _template: function() {\n return [\n '
                                            ',\n '
                                            ',\n '
                                            ',\n '
                                            ',\n '
                                            ',\n this.left? '
                                            ' : '',\n this.center? '
                                            ' : '',\n this.right? '
                                            ' : '',\n '
                                            ',\n '
                                            ',\n ].join('');\n },\n\n /** hide both side panels if previously shown */\n hideSidePanels : function(){\n if( this.left ){\n this.left.hide();\n }\n if( this.right ){\n this.right.hide();\n }\n },\n\n toString : function() { return 'PageLayoutView'; },\n\n /** Check if the communication server is online and show the icon otherwise hide the icon */\n _checkCommunicationServerOnline: function(){\n var host = window.Galaxy.config.communication_server_host,\n port = window.Galaxy.config.communication_server_port,\n $chat_icon_element = $( \"#show-chat-online\" );\n /** Check if the user has deactivated the communication in it's personal settings */\n if (window.Galaxy.user.attributes.preferences !== undefined && window.Galaxy.user.attributes.preferences.communication_server === '1') {\n // See if the configured communication server is available\n $.ajax({\n url: host + \":\" + port,\n })\n .success( function( data ) { \n // enable communication only when a user is logged in\n if( window.Galaxy.user.id !== null ) {\n if( $chat_icon_element.css( \"visibility\") === \"hidden\" ) {\n $chat_icon_element.css( \"visibility\", \"visible\" ); \n }\n }\n })\n .error( function( data ) { \n // hide the communication icon if the communication server is not available\n $chat_icon_element.css( \"visibility\", \"hidden\" ); \n });\n } else {\n $chat_icon_element.css( \"visibility\", \"hidden\" ); \n }\n },\n});\n\n// ============================================================================\n return {\n PageLayoutView: PageLayoutView\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/layout/page.js\n ** module id = 59\n ** module chunks = 2\n **/","/* ========================================================================\n * bootstrap-tour - v0.10.2\n * http://bootstraptour.com\n * ========================================================================\n * Copyright 2012-2015 Ulrich Sossou\n *\n * ========================================================================\n * Licensed under the MIT License (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://opensource.org/licenses/MIT\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * ========================================================================\n */\n\n(function($, window) {\n var Tour, document;\n document = window.document;\n Tour = (function() {\n function Tour(options) {\n var storage;\n try {\n storage = window.localStorage;\n } catch (_error) {\n storage = false;\n }\n this._options = $.extend({\n name: 'tour',\n steps: [],\n container: 'body',\n autoscroll: true,\n keyboard: true,\n storage: storage,\n debug: false,\n backdrop: false,\n backdropContainer: 'body',\n backdropPadding: 0,\n redirect: true,\n orphan: false,\n duration: false,\n delay: false,\n basePath: '',\n template: '

                                            ',\n afterSetState: function(key, value) {},\n afterGetState: function(key, value) {},\n afterRemoveState: function(key) {},\n onStart: function(tour) {},\n onEnd: function(tour) {},\n onShow: function(tour) {},\n onShown: function(tour) {},\n onHide: function(tour) {},\n onHidden: function(tour) {},\n onNext: function(tour) {},\n onPrev: function(tour) {},\n onPause: function(tour, duration) {},\n onResume: function(tour, duration) {},\n onRedirectError: function(tour) {}\n }, options);\n this._force = false;\n this._inited = false;\n this._current = null;\n this.backdrop = {\n overlay: null,\n $element: null,\n $background: null,\n backgroundShown: false,\n overlayElementShown: false\n };\n this;\n }\n\n Tour.prototype.addSteps = function(steps) {\n var step, _i, _len;\n for (_i = 0, _len = steps.length; _i < _len; _i++) {\n step = steps[_i];\n this.addStep(step);\n }\n return this;\n };\n\n Tour.prototype.addStep = function(step) {\n this._options.steps.push(step);\n return this;\n };\n\n Tour.prototype.getStep = function(i) {\n if (this._options.steps[i] != null) {\n return $.extend({\n id: \"step-\" + i,\n path: '',\n host: '',\n placement: 'right',\n title: '',\n content: '

                                            ',\n next: i === this._options.steps.length - 1 ? -1 : i + 1,\n prev: i - 1,\n animation: true,\n container: this._options.container,\n autoscroll: this._options.autoscroll,\n backdrop: this._options.backdrop,\n backdropContainer: this._options.backdropContainer,\n backdropPadding: this._options.backdropPadding,\n redirect: this._options.redirect,\n reflexElement: this._options.steps[i].element,\n orphan: this._options.orphan,\n duration: this._options.duration,\n delay: this._options.delay,\n template: this._options.template,\n onShow: this._options.onShow,\n onShown: this._options.onShown,\n onHide: this._options.onHide,\n onHidden: this._options.onHidden,\n onNext: this._options.onNext,\n onPrev: this._options.onPrev,\n onPause: this._options.onPause,\n onResume: this._options.onResume,\n onRedirectError: this._options.onRedirectError\n }, this._options.steps[i]);\n }\n };\n\n Tour.prototype.init = function(force) {\n this._force = force;\n if (this.ended()) {\n this._debug('Tour ended, init prevented.');\n return this;\n }\n this.setCurrentStep();\n this._initMouseNavigation();\n this._initKeyboardNavigation();\n this._onResize((function(_this) {\n return function() {\n return _this.showStep(_this._current);\n };\n })(this));\n if (this._current !== null) {\n this.showStep(this._current);\n }\n this._inited = true;\n return this;\n };\n\n Tour.prototype.start = function(force) {\n var promise;\n if (force == null) {\n force = false;\n }\n if (!this._inited) {\n this.init(force);\n }\n if (this._current === null) {\n promise = this._makePromise(this._options.onStart != null ? this._options.onStart(this) : void 0);\n this._callOnPromiseDone(promise, this.showStep, 0);\n }\n return this;\n };\n\n Tour.prototype.next = function() {\n var promise;\n promise = this.hideStep(this._current);\n return this._callOnPromiseDone(promise, this._showNextStep);\n };\n\n Tour.prototype.prev = function() {\n var promise;\n promise = this.hideStep(this._current);\n return this._callOnPromiseDone(promise, this._showPrevStep);\n };\n\n Tour.prototype.goTo = function(i) {\n var promise;\n promise = this.hideStep(this._current);\n return this._callOnPromiseDone(promise, this.showStep, i);\n };\n\n Tour.prototype.end = function() {\n var endHelper, promise;\n endHelper = (function(_this) {\n return function(e) {\n $(document).off(\"click.tour-\" + _this._options.name);\n $(document).off(\"keyup.tour-\" + _this._options.name);\n $(window).off(\"resize.tour-\" + _this._options.name);\n _this._setState('end', 'yes');\n _this._inited = false;\n _this._force = false;\n _this._clearTimer();\n if (_this._options.onEnd != null) {\n return _this._options.onEnd(_this);\n }\n };\n })(this);\n promise = this.hideStep(this._current);\n return this._callOnPromiseDone(promise, endHelper);\n };\n\n Tour.prototype.ended = function() {\n return !this._force && !!this._getState('end');\n };\n\n Tour.prototype.restart = function() {\n this._removeState('current_step');\n this._removeState('end');\n this._removeState('redirect_to');\n return this.start();\n };\n\n Tour.prototype.pause = function() {\n var step;\n step = this.getStep(this._current);\n if (!(step && step.duration)) {\n return this;\n }\n this._paused = true;\n this._duration -= new Date().getTime() - this._start;\n window.clearTimeout(this._timer);\n this._debug(\"Paused/Stopped step \" + (this._current + 1) + \" timer (\" + this._duration + \" remaining).\");\n if (step.onPause != null) {\n return step.onPause(this, this._duration);\n }\n };\n\n Tour.prototype.resume = function() {\n var step;\n step = this.getStep(this._current);\n if (!(step && step.duration)) {\n return this;\n }\n this._paused = false;\n this._start = new Date().getTime();\n this._duration = this._duration || step.duration;\n this._timer = window.setTimeout((function(_this) {\n return function() {\n if (_this._isLast()) {\n return _this.next();\n } else {\n return _this.end();\n }\n };\n })(this), this._duration);\n this._debug(\"Started step \" + (this._current + 1) + \" timer with duration \" + this._duration);\n if ((step.onResume != null) && this._duration !== step.duration) {\n return step.onResume(this, this._duration);\n }\n };\n\n Tour.prototype.hideStep = function(i) {\n var hideStepHelper, promise, step;\n step = this.getStep(i);\n if (!step) {\n return;\n }\n this._clearTimer();\n promise = this._makePromise(step.onHide != null ? step.onHide(this, i) : void 0);\n hideStepHelper = (function(_this) {\n return function(e) {\n var $element;\n $element = $(step.element);\n if (!($element.data('bs.popover') || $element.data('popover'))) {\n $element = $('body');\n }\n $element.popover('destroy').removeClass(\"tour-\" + _this._options.name + \"-element tour-\" + _this._options.name + \"-\" + i + \"-element\");\n $element.removeData('bs.popover');\n if (step.reflex) {\n $(step.reflexElement).removeClass('tour-step-element-reflex').off(\"\" + (_this._reflexEvent(step.reflex)) + \".tour-\" + _this._options.name);\n }\n if (step.backdrop) {\n _this._hideBackdrop();\n }\n if (step.onHidden != null) {\n return step.onHidden(_this);\n }\n };\n })(this);\n this._callOnPromiseDone(promise, hideStepHelper);\n return promise;\n };\n\n Tour.prototype.showStep = function(i) {\n var promise, showStepHelper, skipToPrevious, step;\n if (this.ended()) {\n this._debug('Tour ended, showStep prevented.');\n return this;\n }\n step = this.getStep(i);\n if (!step) {\n return;\n }\n skipToPrevious = i < this._current;\n promise = this._makePromise(step.onShow != null ? step.onShow(this, i) : void 0);\n showStepHelper = (function(_this) {\n return function(e) {\n var path, showPopoverAndOverlay;\n _this.setCurrentStep(i);\n path = (function() {\n switch ({}.toString.call(step.path)) {\n case '[object Function]':\n return step.path();\n case '[object String]':\n return this._options.basePath + step.path;\n default:\n return step.path;\n }\n }).call(_this);\n if (_this._isRedirect(step.host, path, document.location)) {\n _this._redirect(step, i, path);\n if (!_this._isJustPathHashDifferent(step.host, path, document.location)) {\n return;\n }\n }\n if (_this._isOrphan(step)) {\n if (step.orphan === false) {\n _this._debug(\"Skip the orphan step \" + (_this._current + 1) + \".\\nOrphan option is false and the element does not exist or is hidden.\");\n if (skipToPrevious) {\n _this._showPrevStep();\n } else {\n _this._showNextStep();\n }\n return;\n }\n _this._debug(\"Show the orphan step \" + (_this._current + 1) + \". Orphans option is true.\");\n }\n if (step.backdrop) {\n _this._showBackdrop(step);\n }\n showPopoverAndOverlay = function() {\n if (_this.getCurrentStep() !== i || _this.ended()) {\n return;\n }\n if ((step.element != null) && step.backdrop) {\n _this._showOverlayElement(step);\n }\n _this._showPopover(step, i);\n if (step.onShown != null) {\n step.onShown(_this);\n }\n return _this._debug(\"Step \" + (_this._current + 1) + \" of \" + _this._options.steps.length);\n };\n if (step.autoscroll) {\n _this._scrollIntoView(step.element, showPopoverAndOverlay);\n } else {\n showPopoverAndOverlay();\n }\n if (step.duration) {\n return _this.resume();\n }\n };\n })(this);\n if (step.delay) {\n this._debug(\"Wait \" + step.delay + \" milliseconds to show the step \" + (this._current + 1));\n window.setTimeout((function(_this) {\n return function() {\n return _this._callOnPromiseDone(promise, showStepHelper);\n };\n })(this), step.delay);\n } else {\n this._callOnPromiseDone(promise, showStepHelper);\n }\n return promise;\n };\n\n Tour.prototype.getCurrentStep = function() {\n return this._current;\n };\n\n Tour.prototype.setCurrentStep = function(value) {\n if (value != null) {\n this._current = value;\n this._setState('current_step', value);\n } else {\n this._current = this._getState('current_step');\n this._current = this._current === null ? null : parseInt(this._current, 10);\n }\n return this;\n };\n\n Tour.prototype.redraw = function() {\n return this._showOverlayElement(this.getStep(this.getCurrentStep()).element, true);\n };\n\n Tour.prototype._setState = function(key, value) {\n var e, keyName;\n if (this._options.storage) {\n keyName = \"\" + this._options.name + \"_\" + key;\n try {\n this._options.storage.setItem(keyName, value);\n } catch (_error) {\n e = _error;\n if (e.code === DOMException.QUOTA_EXCEEDED_ERR) {\n this._debug('LocalStorage quota exceeded. State storage failed.');\n }\n }\n return this._options.afterSetState(keyName, value);\n } else {\n if (this._state == null) {\n this._state = {};\n }\n return this._state[key] = value;\n }\n };\n\n Tour.prototype._removeState = function(key) {\n var keyName;\n if (this._options.storage) {\n keyName = \"\" + this._options.name + \"_\" + key;\n this._options.storage.removeItem(keyName);\n return this._options.afterRemoveState(keyName);\n } else {\n if (this._state != null) {\n return delete this._state[key];\n }\n }\n };\n\n Tour.prototype._getState = function(key) {\n var keyName, value;\n if (this._options.storage) {\n keyName = \"\" + this._options.name + \"_\" + key;\n value = this._options.storage.getItem(keyName);\n } else {\n if (this._state != null) {\n value = this._state[key];\n }\n }\n if (value === void 0 || value === 'null') {\n value = null;\n }\n this._options.afterGetState(key, value);\n return value;\n };\n\n Tour.prototype._showNextStep = function() {\n var promise, showNextStepHelper, step;\n step = this.getStep(this._current);\n showNextStepHelper = (function(_this) {\n return function(e) {\n return _this.showStep(step.next);\n };\n })(this);\n promise = this._makePromise(step.onNext != null ? step.onNext(this) : void 0);\n return this._callOnPromiseDone(promise, showNextStepHelper);\n };\n\n Tour.prototype._showPrevStep = function() {\n var promise, showPrevStepHelper, step;\n step = this.getStep(this._current);\n showPrevStepHelper = (function(_this) {\n return function(e) {\n return _this.showStep(step.prev);\n };\n })(this);\n promise = this._makePromise(step.onPrev != null ? step.onPrev(this) : void 0);\n return this._callOnPromiseDone(promise, showPrevStepHelper);\n };\n\n Tour.prototype._debug = function(text) {\n if (this._options.debug) {\n return window.console.log(\"Bootstrap Tour '\" + this._options.name + \"' | \" + text);\n }\n };\n\n Tour.prototype._isRedirect = function(host, path, location) {\n var currentPath;\n if (host !== '') {\n if (this._isHostDifferent(host, location.href)) {\n return true;\n }\n }\n currentPath = [location.pathname, location.search, location.hash].join('');\n return (path != null) && path !== '' && (({}.toString.call(path) === '[object RegExp]' && !path.test(currentPath)) || ({}.toString.call(path) === '[object String]' && this._isPathDifferent(path, currentPath)));\n };\n\n Tour.prototype._isHostDifferent = function(host, currentURL) {\n return this._getProtocol(host) !== this._getProtocol(currentURL) || this._getHost(host) !== this._getHost(currentURL);\n };\n\n Tour.prototype._isPathDifferent = function(path, currentPath) {\n return this._getPath(path) !== this._getPath(currentPath) || !this._equal(this._getQuery(path), this._getQuery(currentPath)) || !this._equal(this._getHash(path), this._getHash(currentPath));\n };\n\n Tour.prototype._isJustPathHashDifferent = function(host, path, location) {\n var currentPath;\n if (host !== '') {\n if (this._isHostDifferent(host, location.href)) {\n return false;\n }\n }\n currentPath = [location.pathname, location.search, location.hash].join('');\n if ({}.toString.call(path) === '[object String]') {\n return this._getPath(path) === this._getPath(currentPath) && this._equal(this._getQuery(path), this._getQuery(currentPath)) && !this._equal(this._getHash(path), this._getHash(currentPath));\n }\n return false;\n };\n\n Tour.prototype._redirect = function(step, i, path) {\n if ($.isFunction(step.redirect)) {\n return step.redirect.call(this, path);\n } else if (step.redirect === true) {\n this._debug(\"Redirect to \" + step.host + path);\n if (this._getState('redirect_to') === (\"\" + i)) {\n this._debug(\"Error redirection loop to \" + path);\n this._removeState('redirect_to');\n if (step.onRedirectError != null) {\n return step.onRedirectError(this);\n }\n } else {\n this._setState('redirect_to', \"\" + i);\n return document.location.href = \"\" + step.host + path;\n }\n }\n };\n\n Tour.prototype._isOrphan = function(step) {\n return (step.element == null) || !$(step.element).length || $(step.element).is(':hidden') && ($(step.element)[0].namespaceURI !== 'http://www.w3.org/2000/svg');\n };\n\n Tour.prototype._isLast = function() {\n return this._current < this._options.steps.length - 1;\n };\n\n Tour.prototype._showPopover = function(step, i) {\n var $element, $tip, isOrphan, options, shouldAddSmart;\n $(\".tour-\" + this._options.name).remove();\n options = $.extend({}, this._options);\n isOrphan = this._isOrphan(step);\n step.template = this._template(step, i);\n if (isOrphan) {\n step.element = 'body';\n step.placement = 'top';\n }\n $element = $(step.element);\n $element.addClass(\"tour-\" + this._options.name + \"-element tour-\" + this._options.name + \"-\" + i + \"-element\");\n if (step.options) {\n $.extend(options, step.options);\n }\n if (step.reflex && !isOrphan) {\n $(step.reflexElement).addClass('tour-step-element-reflex').off(\"\" + (this._reflexEvent(step.reflex)) + \".tour-\" + this._options.name).on(\"\" + (this._reflexEvent(step.reflex)) + \".tour-\" + this._options.name, (function(_this) {\n return function() {\n if (_this._isLast()) {\n return _this.next();\n } else {\n return _this.end();\n }\n };\n })(this));\n }\n shouldAddSmart = step.smartPlacement === true && step.placement.search(/auto/i) === -1;\n $element.popover({\n placement: shouldAddSmart ? \"auto \" + step.placement : step.placement,\n trigger: 'manual',\n title: step.title,\n content: step.content,\n html: true,\n animation: step.animation,\n container: step.container,\n template: step.template,\n selector: step.element\n }).popover('show');\n $tip = $element.data('bs.popover') ? $element.data('bs.popover').tip() : $element.data('popover').tip();\n $tip.attr('id', step.id);\n this._reposition($tip, step);\n if (isOrphan) {\n return this._center($tip);\n }\n };\n\n Tour.prototype._template = function(step, i) {\n var $navigation, $next, $prev, $resume, $template, template;\n template = step.template;\n if (this._isOrphan(step) && {}.toString.call(step.orphan) !== '[object Boolean]') {\n template = step.orphan;\n }\n $template = $.isFunction(template) ? $(template(i, step)) : $(template);\n $navigation = $template.find('.popover-navigation');\n $prev = $navigation.find('[data-role=\"prev\"]');\n $next = $navigation.find('[data-role=\"next\"]');\n $resume = $navigation.find('[data-role=\"pause-resume\"]');\n if (this._isOrphan(step)) {\n $template.addClass('orphan');\n }\n $template.addClass(\"tour-\" + this._options.name + \" tour-\" + this._options.name + \"-\" + i);\n if (step.reflex) {\n $template.addClass(\"tour-\" + this._options.name + \"-reflex\");\n }\n if (step.prev < 0) {\n $prev.addClass('disabled');\n $prev.prop('disabled', true);\n }\n if (step.next < 0) {\n $next.addClass('disabled');\n $next.prop('disabled', true);\n }\n if (!step.duration) {\n $resume.remove();\n }\n return $template.clone().wrap('
                                            ').parent().html();\n };\n\n Tour.prototype._reflexEvent = function(reflex) {\n if ({}.toString.call(reflex) === '[object Boolean]') {\n return 'click';\n } else {\n return reflex;\n }\n };\n\n Tour.prototype._reposition = function($tip, step) {\n var offsetBottom, offsetHeight, offsetRight, offsetWidth, originalLeft, originalTop, tipOffset;\n offsetWidth = $tip[0].offsetWidth;\n offsetHeight = $tip[0].offsetHeight;\n tipOffset = $tip.offset();\n originalLeft = tipOffset.left;\n originalTop = tipOffset.top;\n offsetBottom = $(document).outerHeight() - tipOffset.top - $tip.outerHeight();\n if (offsetBottom < 0) {\n tipOffset.top = tipOffset.top + offsetBottom;\n }\n offsetRight = $('html').outerWidth() - tipOffset.left - $tip.outerWidth();\n if (offsetRight < 0) {\n tipOffset.left = tipOffset.left + offsetRight;\n }\n if (tipOffset.top < 0) {\n tipOffset.top = 0;\n }\n if (tipOffset.left < 0) {\n tipOffset.left = 0;\n }\n $tip.offset(tipOffset);\n if (step.placement === 'bottom' || step.placement === 'top') {\n if (originalLeft !== tipOffset.left) {\n return this._replaceArrow($tip, (tipOffset.left - originalLeft) * 2, offsetWidth, 'left');\n }\n } else {\n if (originalTop !== tipOffset.top) {\n return this._replaceArrow($tip, (tipOffset.top - originalTop) * 2, offsetHeight, 'top');\n }\n }\n };\n\n Tour.prototype._center = function($tip) {\n return $tip.css('top', $(window).outerHeight() / 2 - $tip.outerHeight() / 2);\n };\n\n Tour.prototype._replaceArrow = function($tip, delta, dimension, position) {\n return $tip.find('.arrow').css(position, delta ? 50 * (1 - delta / dimension) + '%' : '');\n };\n\n Tour.prototype._scrollIntoView = function(element, callback) {\n var $element, $window, counter, offsetTop, scrollTop, windowHeight;\n $element = $(element);\n if (!$element.length) {\n return callback();\n }\n $window = $(window);\n offsetTop = $element.offset().top;\n windowHeight = $window.height();\n scrollTop = Math.max(0, offsetTop - (windowHeight / 2));\n this._debug(\"Scroll into view. ScrollTop: \" + scrollTop + \". Element offset: \" + offsetTop + \". Window height: \" + windowHeight + \".\");\n counter = 0;\n return $('body, html').stop(true, true).animate({\n scrollTop: Math.ceil(scrollTop)\n }, (function(_this) {\n return function() {\n if (++counter === 2) {\n callback();\n return _this._debug(\"Scroll into view.\\nAnimation end element offset: \" + ($element.offset().top) + \".\\nWindow height: \" + ($window.height()) + \".\");\n }\n };\n })(this));\n };\n\n Tour.prototype._onResize = function(callback, timeout) {\n return $(window).on(\"resize.tour-\" + this._options.name, function() {\n clearTimeout(timeout);\n return timeout = setTimeout(callback, 100);\n });\n };\n\n Tour.prototype._initMouseNavigation = function() {\n var _this;\n _this = this;\n return $(document).off(\"click.tour-\" + this._options.name, \".popover.tour-\" + this._options.name + \" *[data-role='prev']\").off(\"click.tour-\" + this._options.name, \".popover.tour-\" + this._options.name + \" *[data-role='next']\").off(\"click.tour-\" + this._options.name, \".popover.tour-\" + this._options.name + \" *[data-role='end']\").off(\"click.tour-\" + this._options.name, \".popover.tour-\" + this._options.name + \" *[data-role='pause-resume']\").on(\"click.tour-\" + this._options.name, \".popover.tour-\" + this._options.name + \" *[data-role='next']\", (function(_this) {\n return function(e) {\n e.preventDefault();\n return _this.next();\n };\n })(this)).on(\"click.tour-\" + this._options.name, \".popover.tour-\" + this._options.name + \" *[data-role='prev']\", (function(_this) {\n return function(e) {\n e.preventDefault();\n return _this.prev();\n };\n })(this)).on(\"click.tour-\" + this._options.name, \".popover.tour-\" + this._options.name + \" *[data-role='end']\", (function(_this) {\n return function(e) {\n e.preventDefault();\n return _this.end();\n };\n })(this)).on(\"click.tour-\" + this._options.name, \".popover.tour-\" + this._options.name + \" *[data-role='pause-resume']\", function(e) {\n var $this;\n e.preventDefault();\n $this = $(this);\n $this.text(_this._paused ? $this.data('pause-text') : $this.data('resume-text'));\n if (_this._paused) {\n return _this.resume();\n } else {\n return _this.pause();\n }\n });\n };\n\n Tour.prototype._initKeyboardNavigation = function() {\n if (!this._options.keyboard) {\n return;\n }\n return $(document).on(\"keyup.tour-\" + this._options.name, (function(_this) {\n return function(e) {\n if (!e.which) {\n return;\n }\n switch (e.which) {\n case 39:\n e.preventDefault();\n if (_this._isLast()) {\n return _this.next();\n } else {\n return _this.end();\n }\n break;\n case 37:\n e.preventDefault();\n if (_this._current > 0) {\n return _this.prev();\n }\n break;\n case 27:\n e.preventDefault();\n return _this.end();\n }\n };\n })(this));\n };\n\n Tour.prototype._makePromise = function(result) {\n if (result && $.isFunction(result.then)) {\n return result;\n } else {\n return null;\n }\n };\n\n Tour.prototype._callOnPromiseDone = function(promise, cb, arg) {\n if (promise) {\n return promise.then((function(_this) {\n return function(e) {\n return cb.call(_this, arg);\n };\n })(this));\n } else {\n return cb.call(this, arg);\n }\n };\n\n Tour.prototype._showBackdrop = function(step) {\n if (this.backdrop.backgroundShown) {\n return;\n }\n this.backdrop = $('
                                            ', {\n \"class\": 'tour-backdrop'\n });\n this.backdrop.backgroundShown = true;\n return $(step.backdropContainer).append(this.backdrop);\n };\n\n Tour.prototype._hideBackdrop = function() {\n this._hideOverlayElement();\n return this._hideBackground();\n };\n\n Tour.prototype._hideBackground = function() {\n if (this.backdrop) {\n this.backdrop.remove();\n this.backdrop.overlay = null;\n return this.backdrop.backgroundShown = false;\n }\n };\n\n Tour.prototype._showOverlayElement = function(step, force) {\n var $element, elementData;\n $element = $(step.element);\n if (!$element || $element.length === 0 || this.backdrop.overlayElementShown && !force) {\n return;\n }\n if (!this.backdrop.overlayElementShown) {\n this.backdrop.$element = $element.addClass('tour-step-backdrop');\n this.backdrop.$background = $('
                                            ', {\n \"class\": 'tour-step-background'\n });\n this.backdrop.$background.appendTo(step.backdropContainer);\n this.backdrop.overlayElementShown = true;\n }\n elementData = {\n width: $element.innerWidth(),\n height: $element.innerHeight(),\n offset: $element.offset()\n };\n if (step.backdropPadding) {\n elementData = this._applyBackdropPadding(step.backdropPadding, elementData);\n }\n return this.backdrop.$background.width(elementData.width).height(elementData.height).offset(elementData.offset);\n };\n\n Tour.prototype._hideOverlayElement = function() {\n if (!this.backdrop.overlayElementShown) {\n return;\n }\n this.backdrop.$element.removeClass('tour-step-backdrop');\n this.backdrop.$background.remove();\n this.backdrop.$element = null;\n this.backdrop.$background = null;\n return this.backdrop.overlayElementShown = false;\n };\n\n Tour.prototype._applyBackdropPadding = function(padding, data) {\n if (typeof padding === 'object') {\n if (padding.top == null) {\n padding.top = 0;\n }\n if (padding.right == null) {\n padding.right = 0;\n }\n if (padding.bottom == null) {\n padding.bottom = 0;\n }\n if (padding.left == null) {\n padding.left = 0;\n }\n data.offset.top = data.offset.top - padding.top;\n data.offset.left = data.offset.left - padding.left;\n data.width = data.width + padding.left + padding.right;\n data.height = data.height + padding.top + padding.bottom;\n } else {\n data.offset.top = data.offset.top - padding;\n data.offset.left = data.offset.left - padding;\n data.width = data.width + (padding * 2);\n data.height = data.height + (padding * 2);\n }\n return data;\n };\n\n Tour.prototype._clearTimer = function() {\n window.clearTimeout(this._timer);\n this._timer = null;\n return this._duration = null;\n };\n\n Tour.prototype._getProtocol = function(url) {\n url = url.split('://');\n if (url.length > 1) {\n return url[0];\n } else {\n return 'http';\n }\n };\n\n Tour.prototype._getHost = function(url) {\n url = url.split('//');\n url = url.length > 1 ? url[1] : url[0];\n return url.split('/')[0];\n };\n\n Tour.prototype._getPath = function(path) {\n return path.replace(/\\/?$/, '').split('?')[0].split('#')[0];\n };\n\n Tour.prototype._getQuery = function(path) {\n return this._getParams(path, '?');\n };\n\n Tour.prototype._getHash = function(path) {\n return this._getParams(path, '#');\n };\n\n Tour.prototype._getParams = function(path, start) {\n var param, params, paramsObject, _i, _len;\n params = path.split(start);\n if (params.length === 1) {\n return {};\n }\n params = params[1].split('&');\n paramsObject = {};\n for (_i = 0, _len = params.length; _i < _len; _i++) {\n param = params[_i];\n param = param.split('=');\n paramsObject[param[0]] = param[1] || '';\n }\n return paramsObject;\n };\n\n Tour.prototype._equal = function(obj1, obj2) {\n var k, v;\n if ({}.toString.call(obj1) === '[object Object]' && {}.toString.call(obj2) === '[object Object]') {\n for (k in obj1) {\n v = obj1[k];\n if (obj2[k] !== v) {\n return false;\n }\n }\n for (k in obj2) {\n v = obj2[k];\n if (obj1[k] !== v) {\n return false;\n }\n }\n return true;\n }\n return obj1 === obj2;\n };\n\n return Tour;\n\n })();\n return window.Tour = Tour;\n})(jQuery, window);\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/libs/bootstrap-tour.js\n ** module id = 60\n ** module chunks = 2\n **/","/*! jQuery UI - v1.9.1 - 2012-10-29\n* http://jqueryui.com\n* Includes: jquery.ui.core.js, jquery.ui.widget.js, jquery.ui.mouse.js, jquery.ui.position.js, jquery.ui.autocomplete.js, jquery.ui.button.js, jquery.ui.menu.js, jquery.ui.slider.js\n* Copyright (c) 2012 jQuery Foundation and other contributors Licensed MIT */\n\n(function(e,t){function i(t,n){var r,i,o,u=t.nodeName.toLowerCase();return\"area\"===u?(r=t.parentNode,i=r.name,!t.href||!i||r.nodeName.toLowerCase()!==\"map\"?!1:(o=e(\"img[usemap=#\"+i+\"]\")[0],!!o&&s(o))):(/input|select|textarea|button|object/.test(u)?!t.disabled:\"a\"===u?t.href||n:n)&&s(t)}function s(t){return e.expr.filters.visible(t)&&!e(t).parents().andSelf().filter(function(){return e.css(this,\"visibility\")===\"hidden\"}).length}var n=0,r=/^ui-id-\\d+$/;e.ui=e.ui||{};if(e.ui.version)return;e.extend(e.ui,{version:\"1.9.1\",keyCode:{BACKSPACE:8,COMMA:188,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SPACE:32,TAB:9,UP:38}}),e.fn.extend({_focus:e.fn.focus,focus:function(t,n){return typeof t==\"number\"?this.each(function(){var r=this;setTimeout(function(){e(r).focus(),n&&n.call(r)},t)}):this._focus.apply(this,arguments)},scrollParent:function(){var t;return e.ui.ie&&/(static|relative)/.test(this.css(\"position\"))||/absolute/.test(this.css(\"position\"))?t=this.parents().filter(function(){return/(relative|absolute|fixed)/.test(e.css(this,\"position\"))&&/(auto|scroll)/.test(e.css(this,\"overflow\")+e.css(this,\"overflow-y\")+e.css(this,\"overflow-x\"))}).eq(0):t=this.parents().filter(function(){return/(auto|scroll)/.test(e.css(this,\"overflow\")+e.css(this,\"overflow-y\")+e.css(this,\"overflow-x\"))}).eq(0),/fixed/.test(this.css(\"position\"))||!t.length?e(document):t},zIndex:function(n){if(n!==t)return this.css(\"zIndex\",n);if(this.length){var r=e(this[0]),i,s;while(r.length&&r[0]!==document){i=r.css(\"position\");if(i===\"absolute\"||i===\"relative\"||i===\"fixed\"){s=parseInt(r.css(\"zIndex\"),10);if(!isNaN(s)&&s!==0)return s}r=r.parent()}}return 0},uniqueId:function(){return this.each(function(){this.id||(this.id=\"ui-id-\"+ ++n)})},removeUniqueId:function(){return this.each(function(){r.test(this.id)&&e(this).removeAttr(\"id\")})}}),e(\"\").outerWidth(1).jquery||e.each([\"Width\",\"Height\"],function(n,r){function u(t,n,r,s){return e.each(i,function(){n-=parseFloat(e.css(t,\"padding\"+this))||0,r&&(n-=parseFloat(e.css(t,\"border\"+this+\"Width\"))||0),s&&(n-=parseFloat(e.css(t,\"margin\"+this))||0)}),n}var i=r===\"Width\"?[\"Left\",\"Right\"]:[\"Top\",\"Bottom\"],s=r.toLowerCase(),o={innerWidth:e.fn.innerWidth,innerHeight:e.fn.innerHeight,outerWidth:e.fn.outerWidth,outerHeight:e.fn.outerHeight};e.fn[\"inner\"+r]=function(n){return n===t?o[\"inner\"+r].call(this):this.each(function(){e(this).css(s,u(this,n)+\"px\")})},e.fn[\"outer\"+r]=function(t,n){return typeof t!=\"number\"?o[\"outer\"+r].call(this,t):this.each(function(){e(this).css(s,u(this,t,!0,n)+\"px\")})}}),e.extend(e.expr[\":\"],{data:e.expr.createPseudo?e.expr.createPseudo(function(t){return function(n){return!!e.data(n,t)}}):function(t,n,r){return!!e.data(t,r[3])},focusable:function(t){return i(t,!isNaN(e.attr(t,\"tabindex\")))},tabbable:function(t){var n=e.attr(t,\"tabindex\"),r=isNaN(n);return(r||n>=0)&&i(t,!r)}}),e(function(){var t=document.body,n=t.appendChild(n=document.createElement(\"div\"));n.offsetHeight,e.extend(n.style,{minHeight:\"100px\",height:\"auto\",padding:0,borderWidth:0}),e.support.minHeight=n.offsetHeight===100,e.support.selectstart=\"onselectstart\"in n,t.removeChild(n).style.display=\"none\"}),function(){var t=/msie ([\\w.]+)/.exec(navigator.userAgent.toLowerCase())||[];e.ui.ie=t.length?!0:!1,e.ui.ie6=parseFloat(t[1],10)===6}(),e.fn.extend({disableSelection:function(){return this.bind((e.support.selectstart?\"selectstart\":\"mousedown\")+\".ui-disableSelection\",function(e){e.preventDefault()})},enableSelection:function(){return this.unbind(\".ui-disableSelection\")}}),e.extend(e.ui,{plugin:{add:function(t,n,r){var i,s=e.ui[t].prototype;for(i in r)s.plugins[i]=s.plugins[i]||[],s.plugins[i].push([n,r[i]])},call:function(e,t,n){var r,i=e.plugins[t];if(!i||!e.element[0].parentNode||e.element[0].parentNode.nodeType===11)return;for(r=0;r0?!0:(t[r]=1,i=t[r]>0,t[r]=0,i)},isOverAxis:function(e,t,n){return e>t&&e\",options:{disabled:!1,create:null},_createWidget:function(t,r){r=e(r||this.defaultElement||this)[0],this.element=e(r),this.uuid=n++,this.eventNamespace=\".\"+this.widgetName+this.uuid,this.options=e.widget.extend({},this.options,this._getCreateOptions(),t),this.bindings=e(),this.hoverable=e(),this.focusable=e(),r!==this&&(e.data(r,this.widgetName,this),e.data(r,this.widgetFullName,this),this._on(this.element,{remove:function(e){e.target===r&&this.destroy()}}),this.document=e(r.style?r.ownerDocument:r.document||r),this.window=e(this.document[0].defaultView||this.document[0].parentWindow)),this._create(),this._trigger(\"create\",null,this._getCreateEventData()),this._init()},_getCreateOptions:e.noop,_getCreateEventData:e.noop,_create:e.noop,_init:e.noop,destroy:function(){this._destroy(),this.element.unbind(this.eventNamespace).removeData(this.widgetName).removeData(this.widgetFullName).removeData(e.camelCase(this.widgetFullName)),this.widget().unbind(this.eventNamespace).removeAttr(\"aria-disabled\").removeClass(this.widgetFullName+\"-disabled \"+\"ui-state-disabled\"),this.bindings.unbind(this.eventNamespace),this.hoverable.removeClass(\"ui-state-hover\"),this.focusable.removeClass(\"ui-state-focus\")},_destroy:e.noop,widget:function(){return this.element},option:function(n,r){var i=n,s,o,u;if(arguments.length===0)return e.widget.extend({},this.options);if(typeof n==\"string\"){i={},s=n.split(\".\"),n=s.shift();if(s.length){o=i[n]=e.widget.extend({},this.options[n]);for(u=0;u=9||!!t.button?this._mouseStarted?(this._mouseDrag(t),t.preventDefault()):(this._mouseDistanceMet(t)&&this._mouseDelayMet(t)&&(this._mouseStarted=this._mouseStart(this._mouseDownEvent,t)!==!1,this._mouseStarted?this._mouseDrag(t):this._mouseUp(t)),!this._mouseStarted):this._mouseUp(t)},_mouseUp:function(t){return e(document).unbind(\"mousemove.\"+this.widgetName,this._mouseMoveDelegate).unbind(\"mouseup.\"+this.widgetName,this._mouseUpDelegate),this._mouseStarted&&(this._mouseStarted=!1,t.target===this._mouseDownEvent.target&&e.data(t.target,this.widgetName+\".preventClickEvent\",!0),this._mouseStop(t)),!1},_mouseDistanceMet:function(e){return Math.max(Math.abs(this._mouseDownEvent.pageX-e.pageX),Math.abs(this._mouseDownEvent.pageY-e.pageY))>=this.options.distance},_mouseDelayMet:function(e){return this.mouseDelayMet},_mouseStart:function(e){},_mouseDrag:function(e){},_mouseStop:function(e){},_mouseCapture:function(e){return!0}})})(jQuery);(function(e,t){function h(e,t,n){return[parseInt(e[0],10)*(l.test(e[0])?t/100:1),parseInt(e[1],10)*(l.test(e[1])?n/100:1)]}function p(t,n){return parseInt(e.css(t,n),10)||0}e.ui=e.ui||{};var n,r=Math.max,i=Math.abs,s=Math.round,o=/left|center|right/,u=/top|center|bottom/,a=/[\\+\\-]\\d+%?/,f=/^\\w+/,l=/%$/,c=e.fn.position;e.position={scrollbarWidth:function(){if(n!==t)return n;var r,i,s=e(\"
                                            \"),o=s.children()[0];return e(\"body\").append(s),r=o.offsetWidth,s.css(\"overflow\",\"scroll\"),i=o.offsetWidth,r===i&&(i=s[0].clientWidth),s.remove(),n=r-i},getScrollInfo:function(t){var n=t.isWindow?\"\":t.element.css(\"overflow-x\"),r=t.isWindow?\"\":t.element.css(\"overflow-y\"),i=n===\"scroll\"||n===\"auto\"&&t.width0?\"right\":\"center\",vertical:u<0?\"top\":o>0?\"bottom\":\"middle\"};lr(i(o),i(u))?h.important=\"horizontal\":h.important=\"vertical\",t.using.call(this,e,h)}),a.offset(e.extend(C,{using:u}))})},e.ui.position={fit:{left:function(e,t){var n=t.within,i=n.isWindow?n.scrollLeft:n.offset.left,s=n.width,o=e.left-t.collisionPosition.marginLeft,u=i-o,a=o+t.collisionWidth-s-i,f;t.collisionWidth>s?u>0&&a<=0?(f=e.left+u+t.collisionWidth-s-i,e.left+=u-f):a>0&&u<=0?e.left=i:u>a?e.left=i+s-t.collisionWidth:e.left=i:u>0?e.left+=u:a>0?e.left-=a:e.left=r(e.left-o,e.left)},top:function(e,t){var n=t.within,i=n.isWindow?n.scrollTop:n.offset.top,s=t.within.height,o=e.top-t.collisionPosition.marginTop,u=i-o,a=o+t.collisionHeight-s-i,f;t.collisionHeight>s?u>0&&a<=0?(f=e.top+u+t.collisionHeight-s-i,e.top+=u-f):a>0&&u<=0?e.top=i:u>a?e.top=i+s-t.collisionHeight:e.top=i:u>0?e.top+=u:a>0?e.top-=a:e.top=r(e.top-o,e.top)}},flip:{left:function(e,t){var n=t.within,r=n.offset.left+n.scrollLeft,s=n.width,o=n.isWindow?n.scrollLeft:n.offset.left,u=e.left-t.collisionPosition.marginLeft,a=u-o,f=u+t.collisionWidth-s-o,l=t.my[0]===\"left\"?-t.elemWidth:t.my[0]===\"right\"?t.elemWidth:0,c=t.at[0]===\"left\"?t.targetWidth:t.at[0]===\"right\"?-t.targetWidth:0,h=-2*t.offset[0],p,d;if(a<0){p=e.left+l+c+h+t.collisionWidth-s-r;if(p<0||p0){d=e.left-t.collisionPosition.marginLeft+l+c+h-o;if(d>0||i(d)a&&(v<0||v0&&(d=e.top-t.collisionPosition.marginTop+c+h+p-o,e.top+c+h+p>f&&(d>0||i(d)10&&i<11,t.innerHTML=\"\",n.removeChild(t)}(),e.uiBackCompat!==!1&&function(e){var n=e.fn.position;e.fn.position=function(r){if(!r||!r.offset)return n.call(this,r);var i=r.offset.split(\" \"),s=r.at.split(\" \");return i.length===1&&(i[1]=i[0]),/^\\d/.test(i[0])&&(i[0]=\"+\"+i[0]),/^\\d/.test(i[1])&&(i[1]=\"+\"+i[1]),s.length===1&&(/left|center|right/.test(s[0])?s[1]=\"center\":(s[1]=s[0],s[0]=\"center\")),n.call(this,e.extend(r,{at:s[0]+i[0]+\" \"+s[1]+i[1],offset:t}))}}(jQuery)})(jQuery);(function(e,t){var n=0;e.widget(\"ui.autocomplete\",{version:\"1.9.1\",defaultElement:\"\",options:{appendTo:\"body\",autoFocus:!1,delay:300,minLength:1,position:{my:\"left top\",at:\"left bottom\",collision:\"none\"},source:null,change:null,close:null,focus:null,open:null,response:null,search:null,select:null},pending:0,_create:function(){var t,n,r;this.isMultiLine=this._isMultiLine(),this.valueMethod=this.element[this.element.is(\"input,textarea\")?\"val\":\"text\"],this.isNewMenu=!0,this.element.addClass(\"ui-autocomplete-input\").attr(\"autocomplete\",\"off\"),this._on(this.element,{keydown:function(i){if(this.element.prop(\"readOnly\")){t=!0,r=!0,n=!0;return}t=!1,r=!1,n=!1;var s=e.ui.keyCode;switch(i.keyCode){case s.PAGE_UP:t=!0,this._move(\"previousPage\",i);break;case s.PAGE_DOWN:t=!0,this._move(\"nextPage\",i);break;case s.UP:t=!0,this._keyEvent(\"previous\",i);break;case s.DOWN:t=!0,this._keyEvent(\"next\",i);break;case s.ENTER:case s.NUMPAD_ENTER:this.menu.active&&(t=!0,i.preventDefault(),this.menu.select(i));break;case s.TAB:this.menu.active&&this.menu.select(i);break;case s.ESCAPE:this.menu.element.is(\":visible\")&&(this._value(this.term),this.close(i),i.preventDefault());break;default:n=!0,this._searchTimeout(i)}},keypress:function(r){if(t){t=!1,r.preventDefault();return}if(n)return;var i=e.ui.keyCode;switch(r.keyCode){case i.PAGE_UP:this._move(\"previousPage\",r);break;case i.PAGE_DOWN:this._move(\"nextPage\",r);break;case i.UP:this._keyEvent(\"previous\",r);break;case i.DOWN:this._keyEvent(\"next\",r)}},input:function(e){if(r){r=!1,e.preventDefault();return}this._searchTimeout(e)},focus:function(){this.selectedItem=null,this.previous=this._value()},blur:function(e){if(this.cancelBlur){delete this.cancelBlur;return}clearTimeout(this.searching),this.close(e),this._change(e)}}),this._initSource(),this.menu=e(\"
                                              \").addClass(\"ui-autocomplete\").appendTo(this.document.find(this.options.appendTo||\"body\")[0]).menu({input:e(),role:null}).zIndex(this.element.zIndex()+1).hide().data(\"menu\"),this._on(this.menu.element,{mousedown:function(t){t.preventDefault(),this.cancelBlur=!0,this._delay(function(){delete this.cancelBlur});var n=this.menu.element[0];e(t.target).closest(\".ui-menu-item\").length||this._delay(function(){var t=this;this.document.one(\"mousedown\",function(r){r.target!==t.element[0]&&r.target!==n&&!e.contains(n,r.target)&&t.close()})})},menufocus:function(t,n){if(this.isNewMenu){this.isNewMenu=!1;if(t.originalEvent&&/^mouse/.test(t.originalEvent.type)){this.menu.blur(),this.document.one(\"mousemove\",function(){e(t.target).trigger(t.originalEvent)});return}}var r=n.item.data(\"ui-autocomplete-item\")||n.item.data(\"item.autocomplete\");!1!==this._trigger(\"focus\",t,{item:r})?t.originalEvent&&/^key/.test(t.originalEvent.type)&&this._value(r.value):this.liveRegion.text(r.value)},menuselect:function(e,t){var n=t.item.data(\"ui-autocomplete-item\")||t.item.data(\"item.autocomplete\"),r=this.previous;this.element[0]!==this.document[0].activeElement&&(this.element.focus(),this.previous=r,this._delay(function(){this.previous=r,this.selectedItem=n})),!1!==this._trigger(\"select\",e,{item:n})&&this._value(n.value),this.term=this._value(),this.close(e),this.selectedItem=n}}),this.liveRegion=e(\"\",{role:\"status\",\"aria-live\":\"polite\"}).addClass(\"ui-helper-hidden-accessible\").insertAfter(this.element),e.fn.bgiframe&&this.menu.element.bgiframe(),this._on(this.window,{beforeunload:function(){this.element.removeAttr(\"autocomplete\")}})},_destroy:function(){clearTimeout(this.searching),this.element.removeClass(\"ui-autocomplete-input\").removeAttr(\"autocomplete\"),this.menu.element.remove(),this.liveRegion.remove()},_setOption:function(e,t){this._super(e,t),e===\"source\"&&this._initSource(),e===\"appendTo\"&&this.menu.element.appendTo(this.document.find(t||\"body\")[0]),e===\"disabled\"&&t&&this.xhr&&this.xhr.abort()},_isMultiLine:function(){return this.element.is(\"textarea\")?!0:this.element.is(\"input\")?!1:this.element.prop(\"isContentEditable\")},_initSource:function(){var t,n,r=this;e.isArray(this.options.source)?(t=this.options.source,this.source=function(n,r){r(e.ui.autocomplete.filter(t,n.term))}):typeof this.options.source==\"string\"?(n=this.options.source,this.source=function(t,i){r.xhr&&r.xhr.abort(),r.xhr=e.ajax({url:n,data:t,dataType:\"json\",success:function(e){i(e)},error:function(){i([])}})}):this.source=this.options.source},_searchTimeout:function(e){clearTimeout(this.searching),this.searching=this._delay(function(){this.term!==this._value()&&(this.selectedItem=null,this.search(null,e))},this.options.delay)},search:function(e,t){e=e!=null?e:this._value(),this.term=this._value();if(e.length\").append(e(\"\").text(n.label)).appendTo(t)},_move:function(e,t){if(!this.menu.element.is(\":visible\")){this.search(null,t);return}if(this.menu.isFirstItem()&&/^previous/.test(e)||this.menu.isLastItem()&&/^next/.test(e)){this._value(this.term),this.menu.blur();return}this.menu[e](t)},widget:function(){return this.menu.element},_value:function(){return this.valueMethod.apply(this.element,arguments)},_keyEvent:function(e,t){if(!this.isMultiLine||this.menu.element.is(\":visible\"))this._move(e,t),t.preventDefault()}}),e.extend(e.ui.autocomplete,{escapeRegex:function(e){return e.replace(/[\\-\\[\\]{}()*+?.,\\\\\\^$|#\\s]/g,\"\\\\$&\")},filter:function(t,n){var r=new RegExp(e.ui.autocomplete.escapeRegex(n),\"i\");return e.grep(t,function(e){return r.test(e.label||e.value||e)})}}),e.widget(\"ui.autocomplete\",e.ui.autocomplete,{options:{messages:{noResults:\"No search results.\",results:function(e){return e+(e>1?\" results are\":\" result is\")+\" available, use up and down arrow keys to navigate.\"}}},__response:function(e){var t;this._superApply(arguments);if(this.options.disabled||this.cancelSearch)return;e&&e.length?t=this.options.messages.results(e.length):t=this.options.messages.noResults,this.liveRegion.text(t)}})})(jQuery);(function(e,t){var n,r,i,s,o=\"ui-button ui-widget ui-state-default ui-corner-all\",u=\"ui-state-hover ui-state-active \",a=\"ui-button-icons-only ui-button-icon-only ui-button-text-icons ui-button-text-icon-primary ui-button-text-icon-secondary ui-button-text-only\",f=function(){var t=e(this).find(\":ui-button\");setTimeout(function(){t.button(\"refresh\")},1)},l=function(t){var n=t.name,r=t.form,i=e([]);return n&&(r?i=e(r).find(\"[name='\"+n+\"']\"):i=e(\"[name='\"+n+\"']\",t.ownerDocument).filter(function(){return!this.form})),i};e.widget(\"ui.button\",{version:\"1.9.1\",defaultElement:\"
                                            \"\n\t )\n\t });\n\t modal.show( { backdrop: true } );\n\t}\n\t\n\t\n\t// ============================================================================\n\t return {\n\t Modal : Modal,\n\t hide_modal : hide_modal,\n\t show_modal : show_modal,\n\t show_message : show_message,\n\t show_in_overlay : show_in_overlay,\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\n\n/***/ },\n/* 59 */\n/***/ function(module, exports, __webpack_require__) {\n\n\tvar __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(Backbone, _, $) {!(__WEBPACK_AMD_DEFINE_ARRAY__ = [\n\t __webpack_require__(92),\n\t __webpack_require__(10),\n\t __webpack_require__(8),\n\t __webpack_require__(6)\n\t], __WEBPACK_AMD_DEFINE_RESULT__ = function( Masthead, Panel, Modal, BaseMVC ) {\n\t\n\t// ============================================================================\n\tvar PageLayoutView = Backbone.View.extend( BaseMVC.LoggableMixin ).extend({\n\t _logNamespace : 'layout',\n\t\n\t el : 'body',\n\t className : 'full-content',\n\t\n\t _panelIds : [\n\t 'left', 'center', 'right'\n\t ],\n\t\n\t defaultOptions : {\n\t message_box_visible : false,\n\t message_box_content : '',\n\t message_box_class : 'info',\n\t show_inactivity_warning : false,\n\t inactivity_box_content : ''\n\t },\n\t\n\t initialize : function( options ) {\n\t // TODO: remove globals\n\t this.log( this + '.initialize:', options );\n\t _.extend( this, _.pick( options, this._panelIds ) );\n\t this.options = _.defaults( _.omit( options.config, this._panelIds ), this.defaultOptions );\n\t Galaxy.modal = this.modal = new Modal.View();\n\t this.masthead = new Masthead.View( this.options );\n\t this.$el.attr( 'scroll', 'no' );\n\t this.$el.html( this._template() );\n\t this.$el.append( this.masthead.frame.$el );\n\t this.$( '#masthead' ).replaceWith( this.masthead.$el );\n\t this.$el.append( this.modal.$el );\n\t this.$messagebox = this.$( '#messagebox' );\n\t this.$inactivebox = this.$( '#inactivebox' );\n\t },\n\t\n\t render : function() {\n\t // TODO: Remove this line after select2 update\n\t $( '.select2-hidden-accessible' ).remove();\n\t this.log( this + '.render:' );\n\t this.masthead.render();\n\t this.renderMessageBox();\n\t this.renderInactivityBox();\n\t this.renderPanels();\n\t this._checkCommunicationServerOnline();\n\t return this;\n\t },\n\t\n\t /** Render message box */\n\t renderMessageBox : function() {\n\t if ( this.options.message_box_visible ){\n\t var content = this.options.message_box_content || '';\n\t var level = this.options.message_box_class || 'info';\n\t this.$el.addClass( 'has-message-box' );\n\t this.$messagebox\n\t .attr( 'class', 'panel-' + level + '-message' )\n\t .html( content )\n\t .toggle( !!content )\n\t .show();\n\t } else {\n\t this.$el.removeClass( 'has-message-box' );\n\t this.$messagebox.hide();\n\t }\n\t return this;\n\t },\n\t\n\t /** Render inactivity warning */\n\t renderInactivityBox : function() {\n\t if( this.options.show_inactivity_warning ){\n\t var content = this.options.inactivity_box_content || '';\n\t var verificationLink = $( '
                                            ' ).attr( 'href', Galaxy.root + 'user/resend_verification' ).text( 'Resend verification' );\n\t this.$el.addClass( 'has-inactivity-box' );\n\t this.$inactivebox\n\t .html( content + ' ' )\n\t .append( verificationLink )\n\t .toggle( !!content )\n\t .show();\n\t } else {\n\t this.$el.removeClass( 'has-inactivity-box' );\n\t this.$inactivebox.hide();\n\t }\n\t return this;\n\t },\n\t\n\t /** Render panels */\n\t renderPanels : function() {\n\t var page = this;\n\t this._panelIds.forEach( function( panelId ){\n\t if( _.has( page, panelId ) ){\n\t page[ panelId ].setElement( '#' + panelId );\n\t page[ panelId ].render();\n\t }\n\t });\n\t if( !this.left ){\n\t this.center.$el.css( 'left', 0 );\n\t }\n\t if( !this.right ){\n\t this.center.$el.css( 'right', 0 );\n\t }\n\t return this;\n\t },\n\t\n\t /** body template */\n\t _template: function() {\n\t return [\n\t '
                                            ',\n\t '
                                            ',\n\t '
                                            ',\n\t '
                                            ',\n\t '
                                            ',\n\t this.left? '
                                            ' : '',\n\t this.center? '
                                            ' : '',\n\t this.right? '
                                            ' : '',\n\t '
                                            ',\n\t '
                                            ',\n\t ].join('');\n\t },\n\t\n\t /** hide both side panels if previously shown */\n\t hideSidePanels : function(){\n\t if( this.left ){\n\t this.left.hide();\n\t }\n\t if( this.right ){\n\t this.right.hide();\n\t }\n\t },\n\t\n\t toString : function() { return 'PageLayoutView'; },\n\t\n\t /** Check if the communication server is online and show the icon otherwise hide the icon */\n\t _checkCommunicationServerOnline: function(){\n\t var host = window.Galaxy.config.communication_server_host,\n\t port = window.Galaxy.config.communication_server_port,\n\t $chat_icon_element = $( \"#show-chat-online\" );\n\t /** Check if the user has deactivated the communication in it's personal settings */\n\t if (window.Galaxy.user.attributes.preferences !== undefined && window.Galaxy.user.attributes.preferences.communication_server === '1') {\n\t // See if the configured communication server is available\n\t $.ajax({\n\t url: host + \":\" + port,\n\t })\n\t .success( function( data ) { \n\t // enable communication only when a user is logged in\n\t if( window.Galaxy.user.id !== null ) {\n\t if( $chat_icon_element.css( \"visibility\") === \"hidden\" ) {\n\t $chat_icon_element.css( \"visibility\", \"visible\" ); \n\t }\n\t }\n\t })\n\t .error( function( data ) { \n\t // hide the communication icon if the communication server is not available\n\t $chat_icon_element.css( \"visibility\", \"hidden\" ); \n\t });\n\t } else {\n\t $chat_icon_element.css( \"visibility\", \"hidden\" ); \n\t }\n\t },\n\t});\n\t\n\t// ============================================================================\n\t return {\n\t PageLayoutView: PageLayoutView\n\t };\n\t}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(3), __webpack_require__(2), __webpack_require__(1)))\n\n/***/ },\n/* 60 */\n/***/ function(module, exports, __webpack_require__) {\n\n\t/* WEBPACK VAR INJECTION */(function(jQuery) {/* ========================================================================\n\t * bootstrap-tour - v0.10.2\n\t * http://bootstraptour.com\n\t * ========================================================================\n\t * Copyright 2012-2015 Ulrich Sossou\n\t *\n\t * ========================================================================\n\t * Licensed under the MIT License (the \"License\");\n\t * you may not use this file except in compliance with the License.\n\t * You may obtain a copy of the License at\n\t *\n\t * https://opensource.org/licenses/MIT\n\t *\n\t * Unless required by applicable law or agreed to in writing, software\n\t * distributed under the License is distributed on an \"AS IS\" BASIS,\n\t * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n\t * See the License for the specific language governing permissions and\n\t * limitations under the License.\n\t * ========================================================================\n\t */\n\t\n\t(function($, window) {\n\t var Tour, document;\n\t document = window.document;\n\t Tour = (function() {\n\t function Tour(options) {\n\t var storage;\n\t try {\n\t storage = window.localStorage;\n\t } catch (_error) {\n\t storage = false;\n\t }\n\t this._options = $.extend({\n\t name: 'tour',\n\t steps: [],\n\t container: 'body',\n\t autoscroll: true,\n\t keyboard: true,\n\t storage: storage,\n\t debug: false,\n\t backdrop: false,\n\t backdropContainer: 'body',\n\t backdropPadding: 0,\n\t redirect: true,\n\t orphan: false,\n\t duration: false,\n\t delay: false,\n\t basePath: '',\n\t template: '

                                            ',\n\t afterSetState: function(key, value) {},\n\t afterGetState: function(key, value) {},\n\t afterRemoveState: function(key) {},\n\t onStart: function(tour) {},\n\t onEnd: function(tour) {},\n\t onShow: function(tour) {},\n\t onShown: function(tour) {},\n\t onHide: function(tour) {},\n\t onHidden: function(tour) {},\n\t onNext: function(tour) {},\n\t onPrev: function(tour) {},\n\t onPause: function(tour, duration) {},\n\t onResume: function(tour, duration) {},\n\t onRedirectError: function(tour) {}\n\t }, options);\n\t this._force = false;\n\t this._inited = false;\n\t this._current = null;\n\t this.backdrop = {\n\t overlay: null,\n\t $element: null,\n\t $background: null,\n\t backgroundShown: false,\n\t overlayElementShown: false\n\t };\n\t this;\n\t }\n\t\n\t Tour.prototype.addSteps = function(steps) {\n\t var step, _i, _len;\n\t for (_i = 0, _len = steps.length; _i < _len; _i++) {\n\t step = steps[_i];\n\t this.addStep(step);\n\t }\n\t return this;\n\t };\n\t\n\t Tour.prototype.addStep = function(step) {\n\t this._options.steps.push(step);\n\t return this;\n\t };\n\t\n\t Tour.prototype.getStep = function(i) {\n\t if (this._options.steps[i] != null) {\n\t return $.extend({\n\t id: \"step-\" + i,\n\t path: '',\n\t host: '',\n\t placement: 'right',\n\t title: '',\n\t content: '

                                            ',\n\t next: i === this._options.steps.length - 1 ? -1 : i + 1,\n\t prev: i - 1,\n\t animation: true,\n\t container: this._options.container,\n\t autoscroll: this._options.autoscroll,\n\t backdrop: this._options.backdrop,\n\t backdropContainer: this._options.backdropContainer,\n\t backdropPadding: this._options.backdropPadding,\n\t redirect: this._options.redirect,\n\t reflexElement: this._options.steps[i].element,\n\t orphan: this._options.orphan,\n\t duration: this._options.duration,\n\t delay: this._options.delay,\n\t template: this._options.template,\n\t onShow: this._options.onShow,\n\t onShown: this._options.onShown,\n\t onHide: this._options.onHide,\n\t onHidden: this._options.onHidden,\n\t onNext: this._options.onNext,\n\t onPrev: this._options.onPrev,\n\t onPause: this._options.onPause,\n\t onResume: this._options.onResume,\n\t onRedirectError: this._options.onRedirectError\n\t }, this._options.steps[i]);\n\t }\n\t };\n\t\n\t Tour.prototype.init = function(force) {\n\t this._force = force;\n\t if (this.ended()) {\n\t this._debug('Tour ended, init prevented.');\n\t return this;\n\t }\n\t this.setCurrentStep();\n\t this._initMouseNavigation();\n\t this._initKeyboardNavigation();\n\t this._onResize((function(_this) {\n\t return function() {\n\t return _this.showStep(_this._current);\n\t };\n\t })(this));\n\t if (this._current !== null) {\n\t this.showStep(this._current);\n\t }\n\t this._inited = true;\n\t return this;\n\t };\n\t\n\t Tour.prototype.start = function(force) {\n\t var promise;\n\t if (force == null) {\n\t force = false;\n\t }\n\t if (!this._inited) {\n\t this.init(force);\n\t }\n\t if (this._current === null) {\n\t promise = this._makePromise(this._options.onStart != null ? this._options.onStart(this) : void 0);\n\t this._callOnPromiseDone(promise, this.showStep, 0);\n\t }\n\t return this;\n\t };\n\t\n\t Tour.prototype.next = function() {\n\t var promise;\n\t promise = this.hideStep(this._current);\n\t return this._callOnPromiseDone(promise, this._showNextStep);\n\t };\n\t\n\t Tour.prototype.prev = function() {\n\t var promise;\n\t promise = this.hideStep(this._current);\n\t return this._callOnPromiseDone(promise, this._showPrevStep);\n\t };\n\t\n\t Tour.prototype.goTo = function(i) {\n\t var promise;\n\t promise = this.hideStep(this._current);\n\t return this._callOnPromiseDone(promise, this.showStep, i);\n\t };\n\t\n\t Tour.prototype.end = function() {\n\t var endHelper, promise;\n\t endHelper = (function(_this) {\n\t return function(e) {\n\t $(document).off(\"click.tour-\" + _this._options.name);\n\t $(document).off(\"keyup.tour-\" + _this._options.name);\n\t $(window).off(\"resize.tour-\" + _this._options.name);\n\t _this._setState('end', 'yes');\n\t _this._inited = false;\n\t _this._force = false;\n\t _this._clearTimer();\n\t if (_this._options.onEnd != null) {\n\t return _this._options.onEnd(_this);\n\t }\n\t };\n\t })(this);\n\t promise = this.hideStep(this._current);\n\t return this._callOnPromiseDone(promise, endHelper);\n\t };\n\t\n\t Tour.prototype.ended = function() {\n\t return !this._force && !!this._getState('end');\n\t };\n\t\n\t Tour.prototype.restart = function() {\n\t this._removeState('current_step');\n\t this._removeState('end');\n\t this._removeState('redirect_to');\n\t return this.start();\n\t };\n\t\n\t Tour.prototype.pause = function() {\n\t var step;\n\t step = this.getStep(this._current);\n\t if (!(step && step.duration)) {\n\t return this;\n\t }\n\t this._paused = true;\n\t this._duration -= new Date().getTime() - this._start;\n\t window.clearTimeout(this._timer);\n\t this._debug(\"Paused/Stopped step \" + (this._current + 1) + \" timer (\" + this._duration + \" remaining).\");\n\t if (step.onPause != null) {\n\t return step.onPause(this, this._duration);\n\t }\n\t };\n\t\n\t Tour.prototype.resume = function() {\n\t var step;\n\t step = this.getStep(this._current);\n\t if (!(step && step.duration)) {\n\t return this;\n\t }\n\t this._paused = false;\n\t this._start = new Date().getTime();\n\t this._duration = this._duration || step.duration;\n\t this._timer = window.setTimeout((function(_this) {\n\t return function() {\n\t if (_this._isLast()) {\n\t return _this.next();\n\t } else {\n\t return _this.end();\n\t }\n\t };\n\t })(this), this._duration);\n\t this._debug(\"Started step \" + (this._current + 1) + \" timer with duration \" + this._duration);\n\t if ((step.onResume != null) && this._duration !== step.duration) {\n\t return step.onResume(this, this._duration);\n\t }\n\t };\n\t\n\t Tour.prototype.hideStep = function(i) {\n\t var hideStepHelper, promise, step;\n\t step = this.getStep(i);\n\t if (!step) {\n\t return;\n\t }\n\t this._clearTimer();\n\t promise = this._makePromise(step.onHide != null ? step.onHide(this, i) : void 0);\n\t hideStepHelper = (function(_this) {\n\t return function(e) {\n\t var $element;\n\t $element = $(step.element);\n\t if (!($element.data('bs.popover') || $element.data('popover'))) {\n\t $element = $('body');\n\t }\n\t $element.popover('destroy').removeClass(\"tour-\" + _this._options.name + \"-element tour-\" + _this._options.name + \"-\" + i + \"-element\");\n\t $element.removeData('bs.popover');\n\t if (step.reflex) {\n\t $(step.reflexElement).removeClass('tour-step-element-reflex').off(\"\" + (_this._reflexEvent(step.reflex)) + \".tour-\" + _this._options.name);\n\t }\n\t if (step.backdrop) {\n\t _this._hideBackdrop();\n\t }\n\t if (step.onHidden != null) {\n\t return step.onHidden(_this);\n\t }\n\t };\n\t })(this);\n\t this._callOnPromiseDone(promise, hideStepHelper);\n\t return promise;\n\t };\n\t\n\t Tour.prototype.showStep = function(i) {\n\t var promise, showStepHelper, skipToPrevious, step;\n\t if (this.ended()) {\n\t this._debug('Tour ended, showStep prevented.');\n\t return this;\n\t }\n\t step = this.getStep(i);\n\t if (!step) {\n\t return;\n\t }\n\t skipToPrevious = i < this._current;\n\t promise = this._makePromise(step.onShow != null ? step.onShow(this, i) : void 0);\n\t showStepHelper = (function(_this) {\n\t return function(e) {\n\t var path, showPopoverAndOverlay;\n\t _this.setCurrentStep(i);\n\t path = (function() {\n\t switch ({}.toString.call(step.path)) {\n\t case '[object Function]':\n\t return step.path();\n\t case '[object String]':\n\t return this._options.basePath + step.path;\n\t default:\n\t return step.path;\n\t }\n\t }).call(_this);\n\t if (_this._isRedirect(step.host, path, document.location)) {\n\t _this._redirect(step, i, path);\n\t if (!_this._isJustPathHashDifferent(step.host, path, document.location)) {\n\t return;\n\t }\n\t }\n\t if (_this._isOrphan(step)) {\n\t if (step.orphan === false) {\n\t _this._debug(\"Skip the orphan step \" + (_this._current + 1) + \".\\nOrphan option is false and the element does not exist or is hidden.\");\n\t if (skipToPrevious) {\n\t _this._showPrevStep();\n\t } else {\n\t _this._showNextStep();\n\t }\n\t return;\n\t }\n\t _this._debug(\"Show the orphan step \" + (_this._current + 1) + \". Orphans option is true.\");\n\t }\n\t if (step.backdrop) {\n\t _this._showBackdrop(step);\n\t }\n\t showPopoverAndOverlay = function() {\n\t if (_this.getCurrentStep() !== i || _this.ended()) {\n\t return;\n\t }\n\t if ((step.element != null) && step.backdrop) {\n\t _this._showOverlayElement(step);\n\t }\n\t _this._showPopover(step, i);\n\t if (step.onShown != null) {\n\t step.onShown(_this);\n\t }\n\t return _this._debug(\"Step \" + (_this._current + 1) + \" of \" + _this._options.steps.length);\n\t };\n\t if (step.autoscroll) {\n\t _this._scrollIntoView(step.element, showPopoverAndOverlay);\n\t } else {\n\t showPopoverAndOverlay();\n\t }\n\t if (step.duration) {\n\t return _this.resume();\n\t }\n\t };\n\t })(this);\n\t if (step.delay) {\n\t this._debug(\"Wait \" + step.delay + \" milliseconds to show the step \" + (this._current + 1));\n\t window.setTimeout((function(_this) {\n\t return function() {\n\t return _this._callOnPromiseDone(promise, showStepHelper);\n\t };\n\t })(this), step.delay);\n\t } else {\n\t this._callOnPromiseDone(promise, showStepHelper);\n\t }\n\t return promise;\n\t };\n\t\n\t Tour.prototype.getCurrentStep = function() {\n\t return this._current;\n\t };\n\t\n\t Tour.prototype.setCurrentStep = function(value) {\n\t if (value != null) {\n\t this._current = value;\n\t this._setState('current_step', value);\n\t } else {\n\t this._current = this._getState('current_step');\n\t this._current = this._current === null ? null : parseInt(this._current, 10);\n\t }\n\t return this;\n\t };\n\t\n\t Tour.prototype.redraw = function() {\n\t return this._showOverlayElement(this.getStep(this.getCurrentStep()).element, true);\n\t };\n\t\n\t Tour.prototype._setState = function(key, value) {\n\t var e, keyName;\n\t if (this._options.storage) {\n\t keyName = \"\" + this._options.name + \"_\" + key;\n\t try {\n\t this._options.storage.setItem(keyName, value);\n\t } catch (_error) {\n\t e = _error;\n\t if (e.code === DOMException.QUOTA_EXCEEDED_ERR) {\n\t this._debug('LocalStorage quota exceeded. State storage failed.');\n\t }\n\t }\n\t return this._options.afterSetState(keyName, value);\n\t } else {\n\t if (this._state == null) {\n\t this._state = {};\n\t }\n\t return this._state[key] = value;\n\t }\n\t };\n\t\n\t Tour.prototype._removeState = function(key) {\n\t var keyName;\n\t if (this._options.storage) {\n\t keyName = \"\" + this._options.name + \"_\" + key;\n\t this._options.storage.removeItem(keyName);\n\t return this._options.afterRemoveState(keyName);\n\t } else {\n\t if (this._state != null) {\n\t return delete this._state[key];\n\t }\n\t }\n\t };\n\t\n\t Tour.prototype._getState = function(key) {\n\t var keyName, value;\n\t if (this._options.storage) {\n\t keyName = \"\" + this._options.name + \"_\" + key;\n\t value = this._options.storage.getItem(keyName);\n\t } else {\n\t if (this._state != null) {\n\t value = this._state[key];\n\t }\n\t }\n\t if (value === void 0 || value === 'null') {\n\t value = null;\n\t }\n\t this._options.afterGetState(key, value);\n\t return value;\n\t };\n\t\n\t Tour.prototype._showNextStep = function() {\n\t var promise, showNextStepHelper, step;\n\t step = this.getStep(this._current);\n\t showNextStepHelper = (function(_this) {\n\t return function(e) {\n\t return _this.showStep(step.next);\n\t };\n\t })(this);\n\t promise = this._makePromise(step.onNext != null ? step.onNext(this) : void 0);\n\t return this._callOnPromiseDone(promise, showNextStepHelper);\n\t };\n\t\n\t Tour.prototype._showPrevStep = function() {\n\t var promise, showPrevStepHelper, step;\n\t step = this.getStep(this._current);\n\t showPrevStepHelper = (function(_this) {\n\t return function(e) {\n\t return _this.showStep(step.prev);\n\t };\n\t })(this);\n\t promise = this._makePromise(step.onPrev != null ? step.onPrev(this) : void 0);\n\t return this._callOnPromiseDone(promise, showPrevStepHelper);\n\t };\n\t\n\t Tour.prototype._debug = function(text) {\n\t if (this._options.debug) {\n\t return window.console.log(\"Bootstrap Tour '\" + this._options.name + \"' | \" + text);\n\t }\n\t };\n\t\n\t Tour.prototype._isRedirect = function(host, path, location) {\n\t var currentPath;\n\t if (host !== '') {\n\t if (this._isHostDifferent(host, location.href)) {\n\t return true;\n\t }\n\t }\n\t currentPath = [location.pathname, location.search, location.hash].join('');\n\t return (path != null) && path !== '' && (({}.toString.call(path) === '[object RegExp]' && !path.test(currentPath)) || ({}.toString.call(path) === '[object String]' && this._isPathDifferent(path, currentPath)));\n\t };\n\t\n\t Tour.prototype._isHostDifferent = function(host, currentURL) {\n\t return this._getProtocol(host) !== this._getProtocol(currentURL) || this._getHost(host) !== this._getHost(currentURL);\n\t };\n\t\n\t Tour.prototype._isPathDifferent = function(path, currentPath) {\n\t return this._getPath(path) !== this._getPath(currentPath) || !this._equal(this._getQuery(path), this._getQuery(currentPath)) || !this._equal(this._getHash(path), this._getHash(currentPath));\n\t };\n\t\n\t Tour.prototype._isJustPathHashDifferent = function(host, path, location) {\n\t var currentPath;\n\t if (host !== '') {\n\t if (this._isHostDifferent(host, location.href)) {\n\t return false;\n\t }\n\t }\n\t currentPath = [location.pathname, location.search, location.hash].join('');\n\t if ({}.toString.call(path) === '[object String]') {\n\t return this._getPath(path) === this._getPath(currentPath) && this._equal(this._getQuery(path), this._getQuery(currentPath)) && !this._equal(this._getHash(path), this._getHash(currentPath));\n\t }\n\t return false;\n\t };\n\t\n\t Tour.prototype._redirect = function(step, i, path) {\n\t if ($.isFunction(step.redirect)) {\n\t return step.redirect.call(this, path);\n\t } else if (step.redirect === true) {\n\t this._debug(\"Redirect to \" + step.host + path);\n\t if (this._getState('redirect_to') === (\"\" + i)) {\n\t this._debug(\"Error redirection loop to \" + path);\n\t this._removeState('redirect_to');\n\t if (step.onRedirectError != null) {\n\t return step.onRedirectError(this);\n\t }\n\t } else {\n\t this._setState('redirect_to', \"\" + i);\n\t return document.location.href = \"\" + step.host + path;\n\t }\n\t }\n\t };\n\t\n\t Tour.prototype._isOrphan = function(step) {\n\t return (step.element == null) || !$(step.element).length || $(step.element).is(':hidden') && ($(step.element)[0].namespaceURI !== 'http://www.w3.org/2000/svg');\n\t };\n\t\n\t Tour.prototype._isLast = function() {\n\t return this._current < this._options.steps.length - 1;\n\t };\n\t\n\t Tour.prototype._showPopover = function(step, i) {\n\t var $element, $tip, isOrphan, options, shouldAddSmart;\n\t $(\".tour-\" + this._options.name).remove();\n\t options = $.extend({}, this._options);\n\t isOrphan = this._isOrphan(step);\n\t step.template = this._template(step, i);\n\t if (isOrphan) {\n\t step.element = 'body';\n\t step.placement = 'top';\n\t }\n\t $element = $(step.element);\n\t $element.addClass(\"tour-\" + this._options.name + \"-element tour-\" + this._options.name + \"-\" + i + \"-element\");\n\t if (step.options) {\n\t $.extend(options, step.options);\n\t }\n\t if (step.reflex && !isOrphan) {\n\t $(step.reflexElement).addClass('tour-step-element-reflex').off(\"\" + (this._reflexEvent(step.reflex)) + \".tour-\" + this._options.name).on(\"\" + (this._reflexEvent(step.reflex)) + \".tour-\" + this._options.name, (function(_this) {\n\t return function() {\n\t if (_this._isLast()) {\n\t return _this.next();\n\t } else {\n\t return _this.end();\n\t }\n\t };\n\t })(this));\n\t }\n\t shouldAddSmart = step.smartPlacement === true && step.placement.search(/auto/i) === -1;\n\t $element.popover({\n\t placement: shouldAddSmart ? \"auto \" + step.placement : step.placement,\n\t trigger: 'manual',\n\t title: step.title,\n\t content: step.content,\n\t html: true,\n\t animation: step.animation,\n\t container: step.container,\n\t template: step.template,\n\t selector: step.element\n\t }).popover('show');\n\t $tip = $element.data('bs.popover') ? $element.data('bs.popover').tip() : $element.data('popover').tip();\n\t $tip.attr('id', step.id);\n\t this._reposition($tip, step);\n\t if (isOrphan) {\n\t return this._center($tip);\n\t }\n\t };\n\t\n\t Tour.prototype._template = function(step, i) {\n\t var $navigation, $next, $prev, $resume, $template, template;\n\t template = step.template;\n\t if (this._isOrphan(step) && {}.toString.call(step.orphan) !== '[object Boolean]') {\n\t template = step.orphan;\n\t }\n\t $template = $.isFunction(template) ? $(template(i, step)) : $(template);\n\t $navigation = $template.find('.popover-navigation');\n\t $prev = $navigation.find('[data-role=\"prev\"]');\n\t $next = $navigation.find('[data-role=\"next\"]');\n\t $resume = $navigation.find('[data-role=\"pause-resume\"]');\n\t if (this._isOrphan(step)) {\n\t $template.addClass('orphan');\n\t }\n\t $template.addClass(\"tour-\" + this._options.name + \" tour-\" + this._options.name + \"-\" + i);\n\t if (step.reflex) {\n\t $template.addClass(\"tour-\" + this._options.name + \"-reflex\");\n\t }\n\t if (step.prev < 0) {\n\t $prev.addClass('disabled');\n\t $prev.prop('disabled', true);\n\t }\n\t if (step.next < 0) {\n\t $next.addClass('disabled');\n\t $next.prop('disabled', true);\n\t }\n\t if (!step.duration) {\n\t $resume.remove();\n\t }\n\t return $template.clone().wrap('
                                            ').parent().html();\n\t };\n\t\n\t Tour.prototype._reflexEvent = function(reflex) {\n\t if ({}.toString.call(reflex) === '[object Boolean]') {\n\t return 'click';\n\t } else {\n\t return reflex;\n\t }\n\t };\n\t\n\t Tour.prototype._reposition = function($tip, step) {\n\t var offsetBottom, offsetHeight, offsetRight, offsetWidth, originalLeft, originalTop, tipOffset;\n\t offsetWidth = $tip[0].offsetWidth;\n\t offsetHeight = $tip[0].offsetHeight;\n\t tipOffset = $tip.offset();\n\t originalLeft = tipOffset.left;\n\t originalTop = tipOffset.top;\n\t offsetBottom = $(document).outerHeight() - tipOffset.top - $tip.outerHeight();\n\t if (offsetBottom < 0) {\n\t tipOffset.top = tipOffset.top + offsetBottom;\n\t }\n\t offsetRight = $('html').outerWidth() - tipOffset.left - $tip.outerWidth();\n\t if (offsetRight < 0) {\n\t tipOffset.left = tipOffset.left + offsetRight;\n\t }\n\t if (tipOffset.top < 0) {\n\t tipOffset.top = 0;\n\t }\n\t if (tipOffset.left < 0) {\n\t tipOffset.left = 0;\n\t }\n\t $tip.offset(tipOffset);\n\t if (step.placement === 'bottom' || step.placement === 'top') {\n\t if (originalLeft !== tipOffset.left) {\n\t return this._replaceArrow($tip, (tipOffset.left - originalLeft) * 2, offsetWidth, 'left');\n\t }\n\t } else {\n\t if (originalTop !== tipOffset.top) {\n\t return this._replaceArrow($tip, (tipOffset.top - originalTop) * 2, offsetHeight, 'top');\n\t }\n\t }\n\t };\n\t\n\t Tour.prototype._center = function($tip) {\n\t return $tip.css('top', $(window).outerHeight() / 2 - $tip.outerHeight() / 2);\n\t };\n\t\n\t Tour.prototype._replaceArrow = function($tip, delta, dimension, position) {\n\t return $tip.find('.arrow').css(position, delta ? 50 * (1 - delta / dimension) + '%' : '');\n\t };\n\t\n\t Tour.prototype._scrollIntoView = function(element, callback) {\n\t var $element, $window, counter, offsetTop, scrollTop, windowHeight;\n\t $element = $(element);\n\t if (!$element.length) {\n\t return callback();\n\t }\n\t $window = $(window);\n\t offsetTop = $element.offset().top;\n\t windowHeight = $window.height();\n\t scrollTop = Math.max(0, offsetTop - (windowHeight / 2));\n\t this._debug(\"Scroll into view. ScrollTop: \" + scrollTop + \". Element offset: \" + offsetTop + \". Window height: \" + windowHeight + \".\");\n\t counter = 0;\n\t return $('body, html').stop(true, true).animate({\n\t scrollTop: Math.ceil(scrollTop)\n\t }, (function(_this) {\n\t return function() {\n\t if (++counter === 2) {\n\t callback();\n\t return _this._debug(\"Scroll into view.\\nAnimation end element offset: \" + ($element.offset().top) + \".\\nWindow height: \" + ($window.height()) + \".\");\n\t }\n\t };\n\t })(this));\n\t };\n\t\n\t Tour.prototype._onResize = function(callback, timeout) {\n\t return $(window).on(\"resize.tour-\" + this._options.name, function() {\n\t clearTimeout(timeout);\n\t return timeout = setTimeout(callback, 100);\n\t });\n\t };\n\t\n\t Tour.prototype._initMouseNavigation = function() {\n\t var _this;\n\t _this = this;\n\t return $(document).off(\"click.tour-\" + this._options.name, \".popover.tour-\" + this._options.name + \" *[data-role='prev']\").off(\"click.tour-\" + this._options.name, \".popover.tour-\" + this._options.name + \" *[data-role='next']\").off(\"click.tour-\" + this._options.name, \".popover.tour-\" + this._options.name + \" *[data-role='end']\").off(\"click.tour-\" + this._options.name, \".popover.tour-\" + this._options.name + \" *[data-role='pause-resume']\").on(\"click.tour-\" + this._options.name, \".popover.tour-\" + this._options.name + \" *[data-role='next']\", (function(_this) {\n\t return function(e) {\n\t e.preventDefault();\n\t return _this.next();\n\t };\n\t })(this)).on(\"click.tour-\" + this._options.name, \".popover.tour-\" + this._options.name + \" *[data-role='prev']\", (function(_this) {\n\t return function(e) {\n\t e.preventDefault();\n\t return _this.prev();\n\t };\n\t })(this)).on(\"click.tour-\" + this._options.name, \".popover.tour-\" + this._options.name + \" *[data-role='end']\", (function(_this) {\n\t return function(e) {\n\t e.preventDefault();\n\t return _this.end();\n\t };\n\t })(this)).on(\"click.tour-\" + this._options.name, \".popover.tour-\" + this._options.name + \" *[data-role='pause-resume']\", function(e) {\n\t var $this;\n\t e.preventDefault();\n\t $this = $(this);\n\t $this.text(_this._paused ? $this.data('pause-text') : $this.data('resume-text'));\n\t if (_this._paused) {\n\t return _this.resume();\n\t } else {\n\t return _this.pause();\n\t }\n\t });\n\t };\n\t\n\t Tour.prototype._initKeyboardNavigation = function() {\n\t if (!this._options.keyboard) {\n\t return;\n\t }\n\t return $(document).on(\"keyup.tour-\" + this._options.name, (function(_this) {\n\t return function(e) {\n\t if (!e.which) {\n\t return;\n\t }\n\t switch (e.which) {\n\t case 39:\n\t e.preventDefault();\n\t if (_this._isLast()) {\n\t return _this.next();\n\t } else {\n\t return _this.end();\n\t }\n\t break;\n\t case 37:\n\t e.preventDefault();\n\t if (_this._current > 0) {\n\t return _this.prev();\n\t }\n\t break;\n\t case 27:\n\t e.preventDefault();\n\t return _this.end();\n\t }\n\t };\n\t })(this));\n\t };\n\t\n\t Tour.prototype._makePromise = function(result) {\n\t if (result && $.isFunction(result.then)) {\n\t return result;\n\t } else {\n\t return null;\n\t }\n\t };\n\t\n\t Tour.prototype._callOnPromiseDone = function(promise, cb, arg) {\n\t if (promise) {\n\t return promise.then((function(_this) {\n\t return function(e) {\n\t return cb.call(_this, arg);\n\t };\n\t })(this));\n\t } else {\n\t return cb.call(this, arg);\n\t }\n\t };\n\t\n\t Tour.prototype._showBackdrop = function(step) {\n\t if (this.backdrop.backgroundShown) {\n\t return;\n\t }\n\t this.backdrop = $('
                                            ', {\n\t \"class\": 'tour-backdrop'\n\t });\n\t this.backdrop.backgroundShown = true;\n\t return $(step.backdropContainer).append(this.backdrop);\n\t };\n\t\n\t Tour.prototype._hideBackdrop = function() {\n\t this._hideOverlayElement();\n\t return this._hideBackground();\n\t };\n\t\n\t Tour.prototype._hideBackground = function() {\n\t if (this.backdrop) {\n\t this.backdrop.remove();\n\t this.backdrop.overlay = null;\n\t return this.backdrop.backgroundShown = false;\n\t }\n\t };\n\t\n\t Tour.prototype._showOverlayElement = function(step, force) {\n\t var $element, elementData;\n\t $element = $(step.element);\n\t if (!$element || $element.length === 0 || this.backdrop.overlayElementShown && !force) {\n\t return;\n\t }\n\t if (!this.backdrop.overlayElementShown) {\n\t this.backdrop.$element = $element.addClass('tour-step-backdrop');\n\t this.backdrop.$background = $('
                                            ', {\n\t \"class\": 'tour-step-background'\n\t });\n\t this.backdrop.$background.appendTo(step.backdropContainer);\n\t this.backdrop.overlayElementShown = true;\n\t }\n\t elementData = {\n\t width: $element.innerWidth(),\n\t height: $element.innerHeight(),\n\t offset: $element.offset()\n\t };\n\t if (step.backdropPadding) {\n\t elementData = this._applyBackdropPadding(step.backdropPadding, elementData);\n\t }\n\t return this.backdrop.$background.width(elementData.width).height(elementData.height).offset(elementData.offset);\n\t };\n\t\n\t Tour.prototype._hideOverlayElement = function() {\n\t if (!this.backdrop.overlayElementShown) {\n\t return;\n\t }\n\t this.backdrop.$element.removeClass('tour-step-backdrop');\n\t this.backdrop.$background.remove();\n\t this.backdrop.$element = null;\n\t this.backdrop.$background = null;\n\t return this.backdrop.overlayElementShown = false;\n\t };\n\t\n\t Tour.prototype._applyBackdropPadding = function(padding, data) {\n\t if (typeof padding === 'object') {\n\t if (padding.top == null) {\n\t padding.top = 0;\n\t }\n\t if (padding.right == null) {\n\t padding.right = 0;\n\t }\n\t if (padding.bottom == null) {\n\t padding.bottom = 0;\n\t }\n\t if (padding.left == null) {\n\t padding.left = 0;\n\t }\n\t data.offset.top = data.offset.top - padding.top;\n\t data.offset.left = data.offset.left - padding.left;\n\t data.width = data.width + padding.left + padding.right;\n\t data.height = data.height + padding.top + padding.bottom;\n\t } else {\n\t data.offset.top = data.offset.top - padding;\n\t data.offset.left = data.offset.left - padding;\n\t data.width = data.width + (padding * 2);\n\t data.height = data.height + (padding * 2);\n\t }\n\t return data;\n\t };\n\t\n\t Tour.prototype._clearTimer = function() {\n\t window.clearTimeout(this._timer);\n\t this._timer = null;\n\t return this._duration = null;\n\t };\n\t\n\t Tour.prototype._getProtocol = function(url) {\n\t url = url.split('://');\n\t if (url.length > 1) {\n\t return url[0];\n\t } else {\n\t return 'http';\n\t }\n\t };\n\t\n\t Tour.prototype._getHost = function(url) {\n\t url = url.split('//');\n\t url = url.length > 1 ? url[1] : url[0];\n\t return url.split('/')[0];\n\t };\n\t\n\t Tour.prototype._getPath = function(path) {\n\t return path.replace(/\\/?$/, '').split('?')[0].split('#')[0];\n\t };\n\t\n\t Tour.prototype._getQuery = function(path) {\n\t return this._getParams(path, '?');\n\t };\n\t\n\t Tour.prototype._getHash = function(path) {\n\t return this._getParams(path, '#');\n\t };\n\t\n\t Tour.prototype._getParams = function(path, start) {\n\t var param, params, paramsObject, _i, _len;\n\t params = path.split(start);\n\t if (params.length === 1) {\n\t return {};\n\t }\n\t params = params[1].split('&');\n\t paramsObject = {};\n\t for (_i = 0, _len = params.length; _i < _len; _i++) {\n\t param = params[_i];\n\t param = param.split('=');\n\t paramsObject[param[0]] = param[1] || '';\n\t }\n\t return paramsObject;\n\t };\n\t\n\t Tour.prototype._equal = function(obj1, obj2) {\n\t var k, v;\n\t if ({}.toString.call(obj1) === '[object Object]' && {}.toString.call(obj2) === '[object Object]') {\n\t for (k in obj1) {\n\t v = obj1[k];\n\t if (obj2[k] !== v) {\n\t return false;\n\t }\n\t }\n\t for (k in obj2) {\n\t v = obj2[k];\n\t if (obj1[k] !== v) {\n\t return false;\n\t }\n\t }\n\t return true;\n\t }\n\t return obj1 === obj2;\n\t };\n\t\n\t return Tour;\n\t\n\t })();\n\t return window.Tour = Tour;\n\t})(jQuery, window);\n\t\n\t/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(1)))\n\n/***/ },\n/* 61 */\n/***/ function(module, exports, __webpack_require__) {\n\n\t/* WEBPACK VAR INJECTION */(function(jQuery) {/*! jQuery UI - v1.9.1 - 2012-10-29\n\t* http://jqueryui.com\n\t* Includes: jquery.ui.core.js, jquery.ui.widget.js, jquery.ui.mouse.js, jquery.ui.position.js, jquery.ui.autocomplete.js, jquery.ui.button.js, jquery.ui.menu.js, jquery.ui.slider.js\n\t* Copyright (c) 2012 jQuery Foundation and other contributors Licensed MIT */\n\t\n\t(function(e,t){function i(t,n){var r,i,o,u=t.nodeName.toLowerCase();return\"area\"===u?(r=t.parentNode,i=r.name,!t.href||!i||r.nodeName.toLowerCase()!==\"map\"?!1:(o=e(\"img[usemap=#\"+i+\"]\")[0],!!o&&s(o))):(/input|select|textarea|button|object/.test(u)?!t.disabled:\"a\"===u?t.href||n:n)&&s(t)}function s(t){return e.expr.filters.visible(t)&&!e(t).parents().andSelf().filter(function(){return e.css(this,\"visibility\")===\"hidden\"}).length}var n=0,r=/^ui-id-\\d+$/;e.ui=e.ui||{};if(e.ui.version)return;e.extend(e.ui,{version:\"1.9.1\",keyCode:{BACKSPACE:8,COMMA:188,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SPACE:32,TAB:9,UP:38}}),e.fn.extend({_focus:e.fn.focus,focus:function(t,n){return typeof t==\"number\"?this.each(function(){var r=this;setTimeout(function(){e(r).focus(),n&&n.call(r)},t)}):this._focus.apply(this,arguments)},scrollParent:function(){var t;return e.ui.ie&&/(static|relative)/.test(this.css(\"position\"))||/absolute/.test(this.css(\"position\"))?t=this.parents().filter(function(){return/(relative|absolute|fixed)/.test(e.css(this,\"position\"))&&/(auto|scroll)/.test(e.css(this,\"overflow\")+e.css(this,\"overflow-y\")+e.css(this,\"overflow-x\"))}).eq(0):t=this.parents().filter(function(){return/(auto|scroll)/.test(e.css(this,\"overflow\")+e.css(this,\"overflow-y\")+e.css(this,\"overflow-x\"))}).eq(0),/fixed/.test(this.css(\"position\"))||!t.length?e(document):t},zIndex:function(n){if(n!==t)return this.css(\"zIndex\",n);if(this.length){var r=e(this[0]),i,s;while(r.length&&r[0]!==document){i=r.css(\"position\");if(i===\"absolute\"||i===\"relative\"||i===\"fixed\"){s=parseInt(r.css(\"zIndex\"),10);if(!isNaN(s)&&s!==0)return s}r=r.parent()}}return 0},uniqueId:function(){return this.each(function(){this.id||(this.id=\"ui-id-\"+ ++n)})},removeUniqueId:function(){return this.each(function(){r.test(this.id)&&e(this).removeAttr(\"id\")})}}),e(\"\").outerWidth(1).jquery||e.each([\"Width\",\"Height\"],function(n,r){function u(t,n,r,s){return e.each(i,function(){n-=parseFloat(e.css(t,\"padding\"+this))||0,r&&(n-=parseFloat(e.css(t,\"border\"+this+\"Width\"))||0),s&&(n-=parseFloat(e.css(t,\"margin\"+this))||0)}),n}var i=r===\"Width\"?[\"Left\",\"Right\"]:[\"Top\",\"Bottom\"],s=r.toLowerCase(),o={innerWidth:e.fn.innerWidth,innerHeight:e.fn.innerHeight,outerWidth:e.fn.outerWidth,outerHeight:e.fn.outerHeight};e.fn[\"inner\"+r]=function(n){return n===t?o[\"inner\"+r].call(this):this.each(function(){e(this).css(s,u(this,n)+\"px\")})},e.fn[\"outer\"+r]=function(t,n){return typeof t!=\"number\"?o[\"outer\"+r].call(this,t):this.each(function(){e(this).css(s,u(this,t,!0,n)+\"px\")})}}),e.extend(e.expr[\":\"],{data:e.expr.createPseudo?e.expr.createPseudo(function(t){return function(n){return!!e.data(n,t)}}):function(t,n,r){return!!e.data(t,r[3])},focusable:function(t){return i(t,!isNaN(e.attr(t,\"tabindex\")))},tabbable:function(t){var n=e.attr(t,\"tabindex\"),r=isNaN(n);return(r||n>=0)&&i(t,!r)}}),e(function(){var t=document.body,n=t.appendChild(n=document.createElement(\"div\"));n.offsetHeight,e.extend(n.style,{minHeight:\"100px\",height:\"auto\",padding:0,borderWidth:0}),e.support.minHeight=n.offsetHeight===100,e.support.selectstart=\"onselectstart\"in n,t.removeChild(n).style.display=\"none\"}),function(){var t=/msie ([\\w.]+)/.exec(navigator.userAgent.toLowerCase())||[];e.ui.ie=t.length?!0:!1,e.ui.ie6=parseFloat(t[1],10)===6}(),e.fn.extend({disableSelection:function(){return this.bind((e.support.selectstart?\"selectstart\":\"mousedown\")+\".ui-disableSelection\",function(e){e.preventDefault()})},enableSelection:function(){return this.unbind(\".ui-disableSelection\")}}),e.extend(e.ui,{plugin:{add:function(t,n,r){var i,s=e.ui[t].prototype;for(i in r)s.plugins[i]=s.plugins[i]||[],s.plugins[i].push([n,r[i]])},call:function(e,t,n){var r,i=e.plugins[t];if(!i||!e.element[0].parentNode||e.element[0].parentNode.nodeType===11)return;for(r=0;r0?!0:(t[r]=1,i=t[r]>0,t[r]=0,i)},isOverAxis:function(e,t,n){return e>t&&e\",options:{disabled:!1,create:null},_createWidget:function(t,r){r=e(r||this.defaultElement||this)[0],this.element=e(r),this.uuid=n++,this.eventNamespace=\".\"+this.widgetName+this.uuid,this.options=e.widget.extend({},this.options,this._getCreateOptions(),t),this.bindings=e(),this.hoverable=e(),this.focusable=e(),r!==this&&(e.data(r,this.widgetName,this),e.data(r,this.widgetFullName,this),this._on(this.element,{remove:function(e){e.target===r&&this.destroy()}}),this.document=e(r.style?r.ownerDocument:r.document||r),this.window=e(this.document[0].defaultView||this.document[0].parentWindow)),this._create(),this._trigger(\"create\",null,this._getCreateEventData()),this._init()},_getCreateOptions:e.noop,_getCreateEventData:e.noop,_create:e.noop,_init:e.noop,destroy:function(){this._destroy(),this.element.unbind(this.eventNamespace).removeData(this.widgetName).removeData(this.widgetFullName).removeData(e.camelCase(this.widgetFullName)),this.widget().unbind(this.eventNamespace).removeAttr(\"aria-disabled\").removeClass(this.widgetFullName+\"-disabled \"+\"ui-state-disabled\"),this.bindings.unbind(this.eventNamespace),this.hoverable.removeClass(\"ui-state-hover\"),this.focusable.removeClass(\"ui-state-focus\")},_destroy:e.noop,widget:function(){return this.element},option:function(n,r){var i=n,s,o,u;if(arguments.length===0)return e.widget.extend({},this.options);if(typeof n==\"string\"){i={},s=n.split(\".\"),n=s.shift();if(s.length){o=i[n]=e.widget.extend({},this.options[n]);for(u=0;u=9||!!t.button?this._mouseStarted?(this._mouseDrag(t),t.preventDefault()):(this._mouseDistanceMet(t)&&this._mouseDelayMet(t)&&(this._mouseStarted=this._mouseStart(this._mouseDownEvent,t)!==!1,this._mouseStarted?this._mouseDrag(t):this._mouseUp(t)),!this._mouseStarted):this._mouseUp(t)},_mouseUp:function(t){return e(document).unbind(\"mousemove.\"+this.widgetName,this._mouseMoveDelegate).unbind(\"mouseup.\"+this.widgetName,this._mouseUpDelegate),this._mouseStarted&&(this._mouseStarted=!1,t.target===this._mouseDownEvent.target&&e.data(t.target,this.widgetName+\".preventClickEvent\",!0),this._mouseStop(t)),!1},_mouseDistanceMet:function(e){return Math.max(Math.abs(this._mouseDownEvent.pageX-e.pageX),Math.abs(this._mouseDownEvent.pageY-e.pageY))>=this.options.distance},_mouseDelayMet:function(e){return this.mouseDelayMet},_mouseStart:function(e){},_mouseDrag:function(e){},_mouseStop:function(e){},_mouseCapture:function(e){return!0}})})(jQuery);(function(e,t){function h(e,t,n){return[parseInt(e[0],10)*(l.test(e[0])?t/100:1),parseInt(e[1],10)*(l.test(e[1])?n/100:1)]}function p(t,n){return parseInt(e.css(t,n),10)||0}e.ui=e.ui||{};var n,r=Math.max,i=Math.abs,s=Math.round,o=/left|center|right/,u=/top|center|bottom/,a=/[\\+\\-]\\d+%?/,f=/^\\w+/,l=/%$/,c=e.fn.position;e.position={scrollbarWidth:function(){if(n!==t)return n;var r,i,s=e(\"
                                            \"),o=s.children()[0];return e(\"body\").append(s),r=o.offsetWidth,s.css(\"overflow\",\"scroll\"),i=o.offsetWidth,r===i&&(i=s[0].clientWidth),s.remove(),n=r-i},getScrollInfo:function(t){var n=t.isWindow?\"\":t.element.css(\"overflow-x\"),r=t.isWindow?\"\":t.element.css(\"overflow-y\"),i=n===\"scroll\"||n===\"auto\"&&t.width0?\"right\":\"center\",vertical:u<0?\"top\":o>0?\"bottom\":\"middle\"};lr(i(o),i(u))?h.important=\"horizontal\":h.important=\"vertical\",t.using.call(this,e,h)}),a.offset(e.extend(C,{using:u}))})},e.ui.position={fit:{left:function(e,t){var n=t.within,i=n.isWindow?n.scrollLeft:n.offset.left,s=n.width,o=e.left-t.collisionPosition.marginLeft,u=i-o,a=o+t.collisionWidth-s-i,f;t.collisionWidth>s?u>0&&a<=0?(f=e.left+u+t.collisionWidth-s-i,e.left+=u-f):a>0&&u<=0?e.left=i:u>a?e.left=i+s-t.collisionWidth:e.left=i:u>0?e.left+=u:a>0?e.left-=a:e.left=r(e.left-o,e.left)},top:function(e,t){var n=t.within,i=n.isWindow?n.scrollTop:n.offset.top,s=t.within.height,o=e.top-t.collisionPosition.marginTop,u=i-o,a=o+t.collisionHeight-s-i,f;t.collisionHeight>s?u>0&&a<=0?(f=e.top+u+t.collisionHeight-s-i,e.top+=u-f):a>0&&u<=0?e.top=i:u>a?e.top=i+s-t.collisionHeight:e.top=i:u>0?e.top+=u:a>0?e.top-=a:e.top=r(e.top-o,e.top)}},flip:{left:function(e,t){var n=t.within,r=n.offset.left+n.scrollLeft,s=n.width,o=n.isWindow?n.scrollLeft:n.offset.left,u=e.left-t.collisionPosition.marginLeft,a=u-o,f=u+t.collisionWidth-s-o,l=t.my[0]===\"left\"?-t.elemWidth:t.my[0]===\"right\"?t.elemWidth:0,c=t.at[0]===\"left\"?t.targetWidth:t.at[0]===\"right\"?-t.targetWidth:0,h=-2*t.offset[0],p,d;if(a<0){p=e.left+l+c+h+t.collisionWidth-s-r;if(p<0||p0){d=e.left-t.collisionPosition.marginLeft+l+c+h-o;if(d>0||i(d)a&&(v<0||v0&&(d=e.top-t.collisionPosition.marginTop+c+h+p-o,e.top+c+h+p>f&&(d>0||i(d)10&&i<11,t.innerHTML=\"\",n.removeChild(t)}(),e.uiBackCompat!==!1&&function(e){var n=e.fn.position;e.fn.position=function(r){if(!r||!r.offset)return n.call(this,r);var i=r.offset.split(\" \"),s=r.at.split(\" \");return i.length===1&&(i[1]=i[0]),/^\\d/.test(i[0])&&(i[0]=\"+\"+i[0]),/^\\d/.test(i[1])&&(i[1]=\"+\"+i[1]),s.length===1&&(/left|center|right/.test(s[0])?s[1]=\"center\":(s[1]=s[0],s[0]=\"center\")),n.call(this,e.extend(r,{at:s[0]+i[0]+\" \"+s[1]+i[1],offset:t}))}}(jQuery)})(jQuery);(function(e,t){var n=0;e.widget(\"ui.autocomplete\",{version:\"1.9.1\",defaultElement:\"\",options:{appendTo:\"body\",autoFocus:!1,delay:300,minLength:1,position:{my:\"left top\",at:\"left bottom\",collision:\"none\"},source:null,change:null,close:null,focus:null,open:null,response:null,search:null,select:null},pending:0,_create:function(){var t,n,r;this.isMultiLine=this._isMultiLine(),this.valueMethod=this.element[this.element.is(\"input,textarea\")?\"val\":\"text\"],this.isNewMenu=!0,this.element.addClass(\"ui-autocomplete-input\").attr(\"autocomplete\",\"off\"),this._on(this.element,{keydown:function(i){if(this.element.prop(\"readOnly\")){t=!0,r=!0,n=!0;return}t=!1,r=!1,n=!1;var s=e.ui.keyCode;switch(i.keyCode){case s.PAGE_UP:t=!0,this._move(\"previousPage\",i);break;case s.PAGE_DOWN:t=!0,this._move(\"nextPage\",i);break;case s.UP:t=!0,this._keyEvent(\"previous\",i);break;case s.DOWN:t=!0,this._keyEvent(\"next\",i);break;case s.ENTER:case s.NUMPAD_ENTER:this.menu.active&&(t=!0,i.preventDefault(),this.menu.select(i));break;case s.TAB:this.menu.active&&this.menu.select(i);break;case s.ESCAPE:this.menu.element.is(\":visible\")&&(this._value(this.term),this.close(i),i.preventDefault());break;default:n=!0,this._searchTimeout(i)}},keypress:function(r){if(t){t=!1,r.preventDefault();return}if(n)return;var i=e.ui.keyCode;switch(r.keyCode){case i.PAGE_UP:this._move(\"previousPage\",r);break;case i.PAGE_DOWN:this._move(\"nextPage\",r);break;case i.UP:this._keyEvent(\"previous\",r);break;case i.DOWN:this._keyEvent(\"next\",r)}},input:function(e){if(r){r=!1,e.preventDefault();return}this._searchTimeout(e)},focus:function(){this.selectedItem=null,this.previous=this._value()},blur:function(e){if(this.cancelBlur){delete this.cancelBlur;return}clearTimeout(this.searching),this.close(e),this._change(e)}}),this._initSource(),this.menu=e(\"
                                              \").addClass(\"ui-autocomplete\").appendTo(this.document.find(this.options.appendTo||\"body\")[0]).menu({input:e(),role:null}).zIndex(this.element.zIndex()+1).hide().data(\"menu\"),this._on(this.menu.element,{mousedown:function(t){t.preventDefault(),this.cancelBlur=!0,this._delay(function(){delete this.cancelBlur});var n=this.menu.element[0];e(t.target).closest(\".ui-menu-item\").length||this._delay(function(){var t=this;this.document.one(\"mousedown\",function(r){r.target!==t.element[0]&&r.target!==n&&!e.contains(n,r.target)&&t.close()})})},menufocus:function(t,n){if(this.isNewMenu){this.isNewMenu=!1;if(t.originalEvent&&/^mouse/.test(t.originalEvent.type)){this.menu.blur(),this.document.one(\"mousemove\",function(){e(t.target).trigger(t.originalEvent)});return}}var r=n.item.data(\"ui-autocomplete-item\")||n.item.data(\"item.autocomplete\");!1!==this._trigger(\"focus\",t,{item:r})?t.originalEvent&&/^key/.test(t.originalEvent.type)&&this._value(r.value):this.liveRegion.text(r.value)},menuselect:function(e,t){var n=t.item.data(\"ui-autocomplete-item\")||t.item.data(\"item.autocomplete\"),r=this.previous;this.element[0]!==this.document[0].activeElement&&(this.element.focus(),this.previous=r,this._delay(function(){this.previous=r,this.selectedItem=n})),!1!==this._trigger(\"select\",e,{item:n})&&this._value(n.value),this.term=this._value(),this.close(e),this.selectedItem=n}}),this.liveRegion=e(\"\",{role:\"status\",\"aria-live\":\"polite\"}).addClass(\"ui-helper-hidden-accessible\").insertAfter(this.element),e.fn.bgiframe&&this.menu.element.bgiframe(),this._on(this.window,{beforeunload:function(){this.element.removeAttr(\"autocomplete\")}})},_destroy:function(){clearTimeout(this.searching),this.element.removeClass(\"ui-autocomplete-input\").removeAttr(\"autocomplete\"),this.menu.element.remove(),this.liveRegion.remove()},_setOption:function(e,t){this._super(e,t),e===\"source\"&&this._initSource(),e===\"appendTo\"&&this.menu.element.appendTo(this.document.find(t||\"body\")[0]),e===\"disabled\"&&t&&this.xhr&&this.xhr.abort()},_isMultiLine:function(){return this.element.is(\"textarea\")?!0:this.element.is(\"input\")?!1:this.element.prop(\"isContentEditable\")},_initSource:function(){var t,n,r=this;e.isArray(this.options.source)?(t=this.options.source,this.source=function(n,r){r(e.ui.autocomplete.filter(t,n.term))}):typeof this.options.source==\"string\"?(n=this.options.source,this.source=function(t,i){r.xhr&&r.xhr.abort(),r.xhr=e.ajax({url:n,data:t,dataType:\"json\",success:function(e){i(e)},error:function(){i([])}})}):this.source=this.options.source},_searchTimeout:function(e){clearTimeout(this.searching),this.searching=this._delay(function(){this.term!==this._value()&&(this.selectedItem=null,this.search(null,e))},this.options.delay)},search:function(e,t){e=e!=null?e:this._value(),this.term=this._value();if(e.length\").append(e(\"\").text(n.label)).appendTo(t)},_move:function(e,t){if(!this.menu.element.is(\":visible\")){this.search(null,t);return}if(this.menu.isFirstItem()&&/^previous/.test(e)||this.menu.isLastItem()&&/^next/.test(e)){this._value(this.term),this.menu.blur();return}this.menu[e](t)},widget:function(){return this.menu.element},_value:function(){return this.valueMethod.apply(this.element,arguments)},_keyEvent:function(e,t){if(!this.isMultiLine||this.menu.element.is(\":visible\"))this._move(e,t),t.preventDefault()}}),e.extend(e.ui.autocomplete,{escapeRegex:function(e){return e.replace(/[\\-\\[\\]{}()*+?.,\\\\\\^$|#\\s]/g,\"\\\\$&\")},filter:function(t,n){var r=new RegExp(e.ui.autocomplete.escapeRegex(n),\"i\");return e.grep(t,function(e){return r.test(e.label||e.value||e)})}}),e.widget(\"ui.autocomplete\",e.ui.autocomplete,{options:{messages:{noResults:\"No search results.\",results:function(e){return e+(e>1?\" results are\":\" result is\")+\" available, use up and down arrow keys to navigate.\"}}},__response:function(e){var t;this._superApply(arguments);if(this.options.disabled||this.cancelSearch)return;e&&e.length?t=this.options.messages.results(e.length):t=this.options.messages.noResults,this.liveRegion.text(t)}})})(jQuery);(function(e,t){var n,r,i,s,o=\"ui-button ui-widget ui-state-default ui-corner-all\",u=\"ui-state-hover ui-state-active \",a=\"ui-button-icons-only ui-button-icon-only ui-button-text-icons ui-button-text-icon-primary ui-button-text-icon-secondary ui-button-text-only\",f=function(){var t=e(this).find(\":ui-button\");setTimeout(function(){t.button(\"refresh\")},1)},l=function(t){var n=t.name,r=t.form,i=e([]);return n&&(r?i=e(r).find(\"[name='\"+n+\"']\"):i=e(\"[name='\"+n+\"']\",t.ownerDocument).filter(function(){return!this.form})),i};e.widget(\"ui.button\",{version:\"1.9.1\",defaultElement:\"
                                            \"\n )\n });\n modal.show( { backdrop: true } );\n}\n\n\n// ============================================================================\n return {\n Modal : Modal,\n hide_modal : hide_modal,\n show_modal : show_modal,\n show_message : show_message,\n show_in_overlay : show_in_overlay,\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/layout/modal.js\n ** module id = 58\n ** module chunks = 2\n **/","define([\n 'layout/masthead',\n 'layout/panel',\n 'mvc/ui/ui-modal',\n 'mvc/base-mvc'\n], function( Masthead, Panel, Modal, BaseMVC ) {\n\n// ============================================================================\nvar PageLayoutView = Backbone.View.extend( BaseMVC.LoggableMixin ).extend({\n _logNamespace : 'layout',\n\n el : 'body',\n className : 'full-content',\n\n _panelIds : [\n 'left', 'center', 'right'\n ],\n\n defaultOptions : {\n message_box_visible : false,\n message_box_content : '',\n message_box_class : 'info',\n show_inactivity_warning : false,\n inactivity_box_content : ''\n },\n\n initialize : function( options ) {\n // TODO: remove globals\n this.log( this + '.initialize:', options );\n _.extend( this, _.pick( options, this._panelIds ) );\n this.options = _.defaults( _.omit( options.config, this._panelIds ), this.defaultOptions );\n Galaxy.modal = this.modal = new Modal.View();\n this.masthead = new Masthead.View( this.options );\n this.$el.attr( 'scroll', 'no' );\n this.$el.html( this._template() );\n this.$el.append( this.masthead.frame.$el );\n this.$( '#masthead' ).replaceWith( this.masthead.$el );\n this.$el.append( this.modal.$el );\n this.$messagebox = this.$( '#messagebox' );\n this.$inactivebox = this.$( '#inactivebox' );\n },\n\n render : function() {\n // TODO: Remove this line after select2 update\n $( '.select2-hidden-accessible' ).remove();\n this.log( this + '.render:' );\n this.masthead.render();\n this.renderMessageBox();\n this.renderInactivityBox();\n this.renderPanels();\n this._checkCommunicationServerOnline();\n return this;\n },\n\n /** Render message box */\n renderMessageBox : function() {\n if ( this.options.message_box_visible ){\n var content = this.options.message_box_content || '';\n var level = this.options.message_box_class || 'info';\n this.$el.addClass( 'has-message-box' );\n this.$messagebox\n .attr( 'class', 'panel-' + level + '-message' )\n .html( content )\n .toggle( !!content )\n .show();\n } else {\n this.$el.removeClass( 'has-message-box' );\n this.$messagebox.hide();\n }\n return this;\n },\n\n /** Render inactivity warning */\n renderInactivityBox : function() {\n if( this.options.show_inactivity_warning ){\n var content = this.options.inactivity_box_content || '';\n var verificationLink = $( '
                                            ' ).attr( 'href', Galaxy.root + 'user/resend_verification' ).text( 'Resend verification' );\n this.$el.addClass( 'has-inactivity-box' );\n this.$inactivebox\n .html( content + ' ' )\n .append( verificationLink )\n .toggle( !!content )\n .show();\n } else {\n this.$el.removeClass( 'has-inactivity-box' );\n this.$inactivebox.hide();\n }\n return this;\n },\n\n /** Render panels */\n renderPanels : function() {\n var page = this;\n this._panelIds.forEach( function( panelId ){\n if( _.has( page, panelId ) ){\n page[ panelId ].setElement( '#' + panelId );\n page[ panelId ].render();\n }\n });\n if( !this.left ){\n this.center.$el.css( 'left', 0 );\n }\n if( !this.right ){\n this.center.$el.css( 'right', 0 );\n }\n return this;\n },\n\n /** body template */\n _template: function() {\n return [\n '
                                            ',\n '
                                            ',\n '
                                            ',\n '
                                            ',\n '
                                            ',\n this.left? '
                                            ' : '',\n this.center? '
                                            ' : '',\n this.right? '
                                            ' : '',\n '
                                            ',\n '
                                            ',\n ].join('');\n },\n\n /** hide both side panels if previously shown */\n hideSidePanels : function(){\n if( this.left ){\n this.left.hide();\n }\n if( this.right ){\n this.right.hide();\n }\n },\n\n toString : function() { return 'PageLayoutView'; },\n\n /** Check if the communication server is online and show the icon otherwise hide the icon */\n _checkCommunicationServerOnline: function(){\n var host = window.Galaxy.config.communication_server_host,\n port = window.Galaxy.config.communication_server_port,\n $chat_icon_element = $( \"#show-chat-online\" );\n /** Check if the user has deactivated the communication in it's personal settings */\n if (window.Galaxy.user.attributes.preferences !== undefined && window.Galaxy.user.attributes.preferences.communication_server === '1') {\n // See if the configured communication server is available\n $.ajax({\n url: host + \":\" + port,\n })\n .success( function( data ) { \n // enable communication only when a user is logged in\n if( window.Galaxy.user.id !== null ) {\n if( $chat_icon_element.css( \"visibility\") === \"hidden\" ) {\n $chat_icon_element.css( \"visibility\", \"visible\" ); \n }\n }\n })\n .error( function( data ) { \n // hide the communication icon if the communication server is not available\n $chat_icon_element.css( \"visibility\", \"hidden\" ); \n });\n } else {\n $chat_icon_element.css( \"visibility\", \"hidden\" ); \n }\n },\n});\n\n// ============================================================================\n return {\n PageLayoutView: PageLayoutView\n };\n});\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/layout/page.js\n ** module id = 59\n ** module chunks = 2\n **/","/* ========================================================================\n * bootstrap-tour - v0.10.2\n * http://bootstraptour.com\n * ========================================================================\n * Copyright 2012-2015 Ulrich Sossou\n *\n * ========================================================================\n * Licensed under the MIT License (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://opensource.org/licenses/MIT\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * ========================================================================\n */\n\n(function($, window) {\n var Tour, document;\n document = window.document;\n Tour = (function() {\n function Tour(options) {\n var storage;\n try {\n storage = window.localStorage;\n } catch (_error) {\n storage = false;\n }\n this._options = $.extend({\n name: 'tour',\n steps: [],\n container: 'body',\n autoscroll: true,\n keyboard: true,\n storage: storage,\n debug: false,\n backdrop: false,\n backdropContainer: 'body',\n backdropPadding: 0,\n redirect: true,\n orphan: false,\n duration: false,\n delay: false,\n basePath: '',\n template: '

                                            ',\n afterSetState: function(key, value) {},\n afterGetState: function(key, value) {},\n afterRemoveState: function(key) {},\n onStart: function(tour) {},\n onEnd: function(tour) {},\n onShow: function(tour) {},\n onShown: function(tour) {},\n onHide: function(tour) {},\n onHidden: function(tour) {},\n onNext: function(tour) {},\n onPrev: function(tour) {},\n onPause: function(tour, duration) {},\n onResume: function(tour, duration) {},\n onRedirectError: function(tour) {}\n }, options);\n this._force = false;\n this._inited = false;\n this._current = null;\n this.backdrop = {\n overlay: null,\n $element: null,\n $background: null,\n backgroundShown: false,\n overlayElementShown: false\n };\n this;\n }\n\n Tour.prototype.addSteps = function(steps) {\n var step, _i, _len;\n for (_i = 0, _len = steps.length; _i < _len; _i++) {\n step = steps[_i];\n this.addStep(step);\n }\n return this;\n };\n\n Tour.prototype.addStep = function(step) {\n this._options.steps.push(step);\n return this;\n };\n\n Tour.prototype.getStep = function(i) {\n if (this._options.steps[i] != null) {\n return $.extend({\n id: \"step-\" + i,\n path: '',\n host: '',\n placement: 'right',\n title: '',\n content: '

                                            ',\n next: i === this._options.steps.length - 1 ? -1 : i + 1,\n prev: i - 1,\n animation: true,\n container: this._options.container,\n autoscroll: this._options.autoscroll,\n backdrop: this._options.backdrop,\n backdropContainer: this._options.backdropContainer,\n backdropPadding: this._options.backdropPadding,\n redirect: this._options.redirect,\n reflexElement: this._options.steps[i].element,\n orphan: this._options.orphan,\n duration: this._options.duration,\n delay: this._options.delay,\n template: this._options.template,\n onShow: this._options.onShow,\n onShown: this._options.onShown,\n onHide: this._options.onHide,\n onHidden: this._options.onHidden,\n onNext: this._options.onNext,\n onPrev: this._options.onPrev,\n onPause: this._options.onPause,\n onResume: this._options.onResume,\n onRedirectError: this._options.onRedirectError\n }, this._options.steps[i]);\n }\n };\n\n Tour.prototype.init = function(force) {\n this._force = force;\n if (this.ended()) {\n this._debug('Tour ended, init prevented.');\n return this;\n }\n this.setCurrentStep();\n this._initMouseNavigation();\n this._initKeyboardNavigation();\n this._onResize((function(_this) {\n return function() {\n return _this.showStep(_this._current);\n };\n })(this));\n if (this._current !== null) {\n this.showStep(this._current);\n }\n this._inited = true;\n return this;\n };\n\n Tour.prototype.start = function(force) {\n var promise;\n if (force == null) {\n force = false;\n }\n if (!this._inited) {\n this.init(force);\n }\n if (this._current === null) {\n promise = this._makePromise(this._options.onStart != null ? this._options.onStart(this) : void 0);\n this._callOnPromiseDone(promise, this.showStep, 0);\n }\n return this;\n };\n\n Tour.prototype.next = function() {\n var promise;\n promise = this.hideStep(this._current);\n return this._callOnPromiseDone(promise, this._showNextStep);\n };\n\n Tour.prototype.prev = function() {\n var promise;\n promise = this.hideStep(this._current);\n return this._callOnPromiseDone(promise, this._showPrevStep);\n };\n\n Tour.prototype.goTo = function(i) {\n var promise;\n promise = this.hideStep(this._current);\n return this._callOnPromiseDone(promise, this.showStep, i);\n };\n\n Tour.prototype.end = function() {\n var endHelper, promise;\n endHelper = (function(_this) {\n return function(e) {\n $(document).off(\"click.tour-\" + _this._options.name);\n $(document).off(\"keyup.tour-\" + _this._options.name);\n $(window).off(\"resize.tour-\" + _this._options.name);\n _this._setState('end', 'yes');\n _this._inited = false;\n _this._force = false;\n _this._clearTimer();\n if (_this._options.onEnd != null) {\n return _this._options.onEnd(_this);\n }\n };\n })(this);\n promise = this.hideStep(this._current);\n return this._callOnPromiseDone(promise, endHelper);\n };\n\n Tour.prototype.ended = function() {\n return !this._force && !!this._getState('end');\n };\n\n Tour.prototype.restart = function() {\n this._removeState('current_step');\n this._removeState('end');\n this._removeState('redirect_to');\n return this.start();\n };\n\n Tour.prototype.pause = function() {\n var step;\n step = this.getStep(this._current);\n if (!(step && step.duration)) {\n return this;\n }\n this._paused = true;\n this._duration -= new Date().getTime() - this._start;\n window.clearTimeout(this._timer);\n this._debug(\"Paused/Stopped step \" + (this._current + 1) + \" timer (\" + this._duration + \" remaining).\");\n if (step.onPause != null) {\n return step.onPause(this, this._duration);\n }\n };\n\n Tour.prototype.resume = function() {\n var step;\n step = this.getStep(this._current);\n if (!(step && step.duration)) {\n return this;\n }\n this._paused = false;\n this._start = new Date().getTime();\n this._duration = this._duration || step.duration;\n this._timer = window.setTimeout((function(_this) {\n return function() {\n if (_this._isLast()) {\n return _this.next();\n } else {\n return _this.end();\n }\n };\n })(this), this._duration);\n this._debug(\"Started step \" + (this._current + 1) + \" timer with duration \" + this._duration);\n if ((step.onResume != null) && this._duration !== step.duration) {\n return step.onResume(this, this._duration);\n }\n };\n\n Tour.prototype.hideStep = function(i) {\n var hideStepHelper, promise, step;\n step = this.getStep(i);\n if (!step) {\n return;\n }\n this._clearTimer();\n promise = this._makePromise(step.onHide != null ? step.onHide(this, i) : void 0);\n hideStepHelper = (function(_this) {\n return function(e) {\n var $element;\n $element = $(step.element);\n if (!($element.data('bs.popover') || $element.data('popover'))) {\n $element = $('body');\n }\n $element.popover('destroy').removeClass(\"tour-\" + _this._options.name + \"-element tour-\" + _this._options.name + \"-\" + i + \"-element\");\n $element.removeData('bs.popover');\n if (step.reflex) {\n $(step.reflexElement).removeClass('tour-step-element-reflex').off(\"\" + (_this._reflexEvent(step.reflex)) + \".tour-\" + _this._options.name);\n }\n if (step.backdrop) {\n _this._hideBackdrop();\n }\n if (step.onHidden != null) {\n return step.onHidden(_this);\n }\n };\n })(this);\n this._callOnPromiseDone(promise, hideStepHelper);\n return promise;\n };\n\n Tour.prototype.showStep = function(i) {\n var promise, showStepHelper, skipToPrevious, step;\n if (this.ended()) {\n this._debug('Tour ended, showStep prevented.');\n return this;\n }\n step = this.getStep(i);\n if (!step) {\n return;\n }\n skipToPrevious = i < this._current;\n promise = this._makePromise(step.onShow != null ? step.onShow(this, i) : void 0);\n showStepHelper = (function(_this) {\n return function(e) {\n var path, showPopoverAndOverlay;\n _this.setCurrentStep(i);\n path = (function() {\n switch ({}.toString.call(step.path)) {\n case '[object Function]':\n return step.path();\n case '[object String]':\n return this._options.basePath + step.path;\n default:\n return step.path;\n }\n }).call(_this);\n if (_this._isRedirect(step.host, path, document.location)) {\n _this._redirect(step, i, path);\n if (!_this._isJustPathHashDifferent(step.host, path, document.location)) {\n return;\n }\n }\n if (_this._isOrphan(step)) {\n if (step.orphan === false) {\n _this._debug(\"Skip the orphan step \" + (_this._current + 1) + \".\\nOrphan option is false and the element does not exist or is hidden.\");\n if (skipToPrevious) {\n _this._showPrevStep();\n } else {\n _this._showNextStep();\n }\n return;\n }\n _this._debug(\"Show the orphan step \" + (_this._current + 1) + \". Orphans option is true.\");\n }\n if (step.backdrop) {\n _this._showBackdrop(step);\n }\n showPopoverAndOverlay = function() {\n if (_this.getCurrentStep() !== i || _this.ended()) {\n return;\n }\n if ((step.element != null) && step.backdrop) {\n _this._showOverlayElement(step);\n }\n _this._showPopover(step, i);\n if (step.onShown != null) {\n step.onShown(_this);\n }\n return _this._debug(\"Step \" + (_this._current + 1) + \" of \" + _this._options.steps.length);\n };\n if (step.autoscroll) {\n _this._scrollIntoView(step.element, showPopoverAndOverlay);\n } else {\n showPopoverAndOverlay();\n }\n if (step.duration) {\n return _this.resume();\n }\n };\n })(this);\n if (step.delay) {\n this._debug(\"Wait \" + step.delay + \" milliseconds to show the step \" + (this._current + 1));\n window.setTimeout((function(_this) {\n return function() {\n return _this._callOnPromiseDone(promise, showStepHelper);\n };\n })(this), step.delay);\n } else {\n this._callOnPromiseDone(promise, showStepHelper);\n }\n return promise;\n };\n\n Tour.prototype.getCurrentStep = function() {\n return this._current;\n };\n\n Tour.prototype.setCurrentStep = function(value) {\n if (value != null) {\n this._current = value;\n this._setState('current_step', value);\n } else {\n this._current = this._getState('current_step');\n this._current = this._current === null ? null : parseInt(this._current, 10);\n }\n return this;\n };\n\n Tour.prototype.redraw = function() {\n return this._showOverlayElement(this.getStep(this.getCurrentStep()).element, true);\n };\n\n Tour.prototype._setState = function(key, value) {\n var e, keyName;\n if (this._options.storage) {\n keyName = \"\" + this._options.name + \"_\" + key;\n try {\n this._options.storage.setItem(keyName, value);\n } catch (_error) {\n e = _error;\n if (e.code === DOMException.QUOTA_EXCEEDED_ERR) {\n this._debug('LocalStorage quota exceeded. State storage failed.');\n }\n }\n return this._options.afterSetState(keyName, value);\n } else {\n if (this._state == null) {\n this._state = {};\n }\n return this._state[key] = value;\n }\n };\n\n Tour.prototype._removeState = function(key) {\n var keyName;\n if (this._options.storage) {\n keyName = \"\" + this._options.name + \"_\" + key;\n this._options.storage.removeItem(keyName);\n return this._options.afterRemoveState(keyName);\n } else {\n if (this._state != null) {\n return delete this._state[key];\n }\n }\n };\n\n Tour.prototype._getState = function(key) {\n var keyName, value;\n if (this._options.storage) {\n keyName = \"\" + this._options.name + \"_\" + key;\n value = this._options.storage.getItem(keyName);\n } else {\n if (this._state != null) {\n value = this._state[key];\n }\n }\n if (value === void 0 || value === 'null') {\n value = null;\n }\n this._options.afterGetState(key, value);\n return value;\n };\n\n Tour.prototype._showNextStep = function() {\n var promise, showNextStepHelper, step;\n step = this.getStep(this._current);\n showNextStepHelper = (function(_this) {\n return function(e) {\n return _this.showStep(step.next);\n };\n })(this);\n promise = this._makePromise(step.onNext != null ? step.onNext(this) : void 0);\n return this._callOnPromiseDone(promise, showNextStepHelper);\n };\n\n Tour.prototype._showPrevStep = function() {\n var promise, showPrevStepHelper, step;\n step = this.getStep(this._current);\n showPrevStepHelper = (function(_this) {\n return function(e) {\n return _this.showStep(step.prev);\n };\n })(this);\n promise = this._makePromise(step.onPrev != null ? step.onPrev(this) : void 0);\n return this._callOnPromiseDone(promise, showPrevStepHelper);\n };\n\n Tour.prototype._debug = function(text) {\n if (this._options.debug) {\n return window.console.log(\"Bootstrap Tour '\" + this._options.name + \"' | \" + text);\n }\n };\n\n Tour.prototype._isRedirect = function(host, path, location) {\n var currentPath;\n if (host !== '') {\n if (this._isHostDifferent(host, location.href)) {\n return true;\n }\n }\n currentPath = [location.pathname, location.search, location.hash].join('');\n return (path != null) && path !== '' && (({}.toString.call(path) === '[object RegExp]' && !path.test(currentPath)) || ({}.toString.call(path) === '[object String]' && this._isPathDifferent(path, currentPath)));\n };\n\n Tour.prototype._isHostDifferent = function(host, currentURL) {\n return this._getProtocol(host) !== this._getProtocol(currentURL) || this._getHost(host) !== this._getHost(currentURL);\n };\n\n Tour.prototype._isPathDifferent = function(path, currentPath) {\n return this._getPath(path) !== this._getPath(currentPath) || !this._equal(this._getQuery(path), this._getQuery(currentPath)) || !this._equal(this._getHash(path), this._getHash(currentPath));\n };\n\n Tour.prototype._isJustPathHashDifferent = function(host, path, location) {\n var currentPath;\n if (host !== '') {\n if (this._isHostDifferent(host, location.href)) {\n return false;\n }\n }\n currentPath = [location.pathname, location.search, location.hash].join('');\n if ({}.toString.call(path) === '[object String]') {\n return this._getPath(path) === this._getPath(currentPath) && this._equal(this._getQuery(path), this._getQuery(currentPath)) && !this._equal(this._getHash(path), this._getHash(currentPath));\n }\n return false;\n };\n\n Tour.prototype._redirect = function(step, i, path) {\n if ($.isFunction(step.redirect)) {\n return step.redirect.call(this, path);\n } else if (step.redirect === true) {\n this._debug(\"Redirect to \" + step.host + path);\n if (this._getState('redirect_to') === (\"\" + i)) {\n this._debug(\"Error redirection loop to \" + path);\n this._removeState('redirect_to');\n if (step.onRedirectError != null) {\n return step.onRedirectError(this);\n }\n } else {\n this._setState('redirect_to', \"\" + i);\n return document.location.href = \"\" + step.host + path;\n }\n }\n };\n\n Tour.prototype._isOrphan = function(step) {\n return (step.element == null) || !$(step.element).length || $(step.element).is(':hidden') && ($(step.element)[0].namespaceURI !== 'http://www.w3.org/2000/svg');\n };\n\n Tour.prototype._isLast = function() {\n return this._current < this._options.steps.length - 1;\n };\n\n Tour.prototype._showPopover = function(step, i) {\n var $element, $tip, isOrphan, options, shouldAddSmart;\n $(\".tour-\" + this._options.name).remove();\n options = $.extend({}, this._options);\n isOrphan = this._isOrphan(step);\n step.template = this._template(step, i);\n if (isOrphan) {\n step.element = 'body';\n step.placement = 'top';\n }\n $element = $(step.element);\n $element.addClass(\"tour-\" + this._options.name + \"-element tour-\" + this._options.name + \"-\" + i + \"-element\");\n if (step.options) {\n $.extend(options, step.options);\n }\n if (step.reflex && !isOrphan) {\n $(step.reflexElement).addClass('tour-step-element-reflex').off(\"\" + (this._reflexEvent(step.reflex)) + \".tour-\" + this._options.name).on(\"\" + (this._reflexEvent(step.reflex)) + \".tour-\" + this._options.name, (function(_this) {\n return function() {\n if (_this._isLast()) {\n return _this.next();\n } else {\n return _this.end();\n }\n };\n })(this));\n }\n shouldAddSmart = step.smartPlacement === true && step.placement.search(/auto/i) === -1;\n $element.popover({\n placement: shouldAddSmart ? \"auto \" + step.placement : step.placement,\n trigger: 'manual',\n title: step.title,\n content: step.content,\n html: true,\n animation: step.animation,\n container: step.container,\n template: step.template,\n selector: step.element\n }).popover('show');\n $tip = $element.data('bs.popover') ? $element.data('bs.popover').tip() : $element.data('popover').tip();\n $tip.attr('id', step.id);\n this._reposition($tip, step);\n if (isOrphan) {\n return this._center($tip);\n }\n };\n\n Tour.prototype._template = function(step, i) {\n var $navigation, $next, $prev, $resume, $template, template;\n template = step.template;\n if (this._isOrphan(step) && {}.toString.call(step.orphan) !== '[object Boolean]') {\n template = step.orphan;\n }\n $template = $.isFunction(template) ? $(template(i, step)) : $(template);\n $navigation = $template.find('.popover-navigation');\n $prev = $navigation.find('[data-role=\"prev\"]');\n $next = $navigation.find('[data-role=\"next\"]');\n $resume = $navigation.find('[data-role=\"pause-resume\"]');\n if (this._isOrphan(step)) {\n $template.addClass('orphan');\n }\n $template.addClass(\"tour-\" + this._options.name + \" tour-\" + this._options.name + \"-\" + i);\n if (step.reflex) {\n $template.addClass(\"tour-\" + this._options.name + \"-reflex\");\n }\n if (step.prev < 0) {\n $prev.addClass('disabled');\n $prev.prop('disabled', true);\n }\n if (step.next < 0) {\n $next.addClass('disabled');\n $next.prop('disabled', true);\n }\n if (!step.duration) {\n $resume.remove();\n }\n return $template.clone().wrap('
                                            ').parent().html();\n };\n\n Tour.prototype._reflexEvent = function(reflex) {\n if ({}.toString.call(reflex) === '[object Boolean]') {\n return 'click';\n } else {\n return reflex;\n }\n };\n\n Tour.prototype._reposition = function($tip, step) {\n var offsetBottom, offsetHeight, offsetRight, offsetWidth, originalLeft, originalTop, tipOffset;\n offsetWidth = $tip[0].offsetWidth;\n offsetHeight = $tip[0].offsetHeight;\n tipOffset = $tip.offset();\n originalLeft = tipOffset.left;\n originalTop = tipOffset.top;\n offsetBottom = $(document).outerHeight() - tipOffset.top - $tip.outerHeight();\n if (offsetBottom < 0) {\n tipOffset.top = tipOffset.top + offsetBottom;\n }\n offsetRight = $('html').outerWidth() - tipOffset.left - $tip.outerWidth();\n if (offsetRight < 0) {\n tipOffset.left = tipOffset.left + offsetRight;\n }\n if (tipOffset.top < 0) {\n tipOffset.top = 0;\n }\n if (tipOffset.left < 0) {\n tipOffset.left = 0;\n }\n $tip.offset(tipOffset);\n if (step.placement === 'bottom' || step.placement === 'top') {\n if (originalLeft !== tipOffset.left) {\n return this._replaceArrow($tip, (tipOffset.left - originalLeft) * 2, offsetWidth, 'left');\n }\n } else {\n if (originalTop !== tipOffset.top) {\n return this._replaceArrow($tip, (tipOffset.top - originalTop) * 2, offsetHeight, 'top');\n }\n }\n };\n\n Tour.prototype._center = function($tip) {\n return $tip.css('top', $(window).outerHeight() / 2 - $tip.outerHeight() / 2);\n };\n\n Tour.prototype._replaceArrow = function($tip, delta, dimension, position) {\n return $tip.find('.arrow').css(position, delta ? 50 * (1 - delta / dimension) + '%' : '');\n };\n\n Tour.prototype._scrollIntoView = function(element, callback) {\n var $element, $window, counter, offsetTop, scrollTop, windowHeight;\n $element = $(element);\n if (!$element.length) {\n return callback();\n }\n $window = $(window);\n offsetTop = $element.offset().top;\n windowHeight = $window.height();\n scrollTop = Math.max(0, offsetTop - (windowHeight / 2));\n this._debug(\"Scroll into view. ScrollTop: \" + scrollTop + \". Element offset: \" + offsetTop + \". Window height: \" + windowHeight + \".\");\n counter = 0;\n return $('body, html').stop(true, true).animate({\n scrollTop: Math.ceil(scrollTop)\n }, (function(_this) {\n return function() {\n if (++counter === 2) {\n callback();\n return _this._debug(\"Scroll into view.\\nAnimation end element offset: \" + ($element.offset().top) + \".\\nWindow height: \" + ($window.height()) + \".\");\n }\n };\n })(this));\n };\n\n Tour.prototype._onResize = function(callback, timeout) {\n return $(window).on(\"resize.tour-\" + this._options.name, function() {\n clearTimeout(timeout);\n return timeout = setTimeout(callback, 100);\n });\n };\n\n Tour.prototype._initMouseNavigation = function() {\n var _this;\n _this = this;\n return $(document).off(\"click.tour-\" + this._options.name, \".popover.tour-\" + this._options.name + \" *[data-role='prev']\").off(\"click.tour-\" + this._options.name, \".popover.tour-\" + this._options.name + \" *[data-role='next']\").off(\"click.tour-\" + this._options.name, \".popover.tour-\" + this._options.name + \" *[data-role='end']\").off(\"click.tour-\" + this._options.name, \".popover.tour-\" + this._options.name + \" *[data-role='pause-resume']\").on(\"click.tour-\" + this._options.name, \".popover.tour-\" + this._options.name + \" *[data-role='next']\", (function(_this) {\n return function(e) {\n e.preventDefault();\n return _this.next();\n };\n })(this)).on(\"click.tour-\" + this._options.name, \".popover.tour-\" + this._options.name + \" *[data-role='prev']\", (function(_this) {\n return function(e) {\n e.preventDefault();\n return _this.prev();\n };\n })(this)).on(\"click.tour-\" + this._options.name, \".popover.tour-\" + this._options.name + \" *[data-role='end']\", (function(_this) {\n return function(e) {\n e.preventDefault();\n return _this.end();\n };\n })(this)).on(\"click.tour-\" + this._options.name, \".popover.tour-\" + this._options.name + \" *[data-role='pause-resume']\", function(e) {\n var $this;\n e.preventDefault();\n $this = $(this);\n $this.text(_this._paused ? $this.data('pause-text') : $this.data('resume-text'));\n if (_this._paused) {\n return _this.resume();\n } else {\n return _this.pause();\n }\n });\n };\n\n Tour.prototype._initKeyboardNavigation = function() {\n if (!this._options.keyboard) {\n return;\n }\n return $(document).on(\"keyup.tour-\" + this._options.name, (function(_this) {\n return function(e) {\n if (!e.which) {\n return;\n }\n switch (e.which) {\n case 39:\n e.preventDefault();\n if (_this._isLast()) {\n return _this.next();\n } else {\n return _this.end();\n }\n break;\n case 37:\n e.preventDefault();\n if (_this._current > 0) {\n return _this.prev();\n }\n break;\n case 27:\n e.preventDefault();\n return _this.end();\n }\n };\n })(this));\n };\n\n Tour.prototype._makePromise = function(result) {\n if (result && $.isFunction(result.then)) {\n return result;\n } else {\n return null;\n }\n };\n\n Tour.prototype._callOnPromiseDone = function(promise, cb, arg) {\n if (promise) {\n return promise.then((function(_this) {\n return function(e) {\n return cb.call(_this, arg);\n };\n })(this));\n } else {\n return cb.call(this, arg);\n }\n };\n\n Tour.prototype._showBackdrop = function(step) {\n if (this.backdrop.backgroundShown) {\n return;\n }\n this.backdrop = $('
                                            ', {\n \"class\": 'tour-backdrop'\n });\n this.backdrop.backgroundShown = true;\n return $(step.backdropContainer).append(this.backdrop);\n };\n\n Tour.prototype._hideBackdrop = function() {\n this._hideOverlayElement();\n return this._hideBackground();\n };\n\n Tour.prototype._hideBackground = function() {\n if (this.backdrop) {\n this.backdrop.remove();\n this.backdrop.overlay = null;\n return this.backdrop.backgroundShown = false;\n }\n };\n\n Tour.prototype._showOverlayElement = function(step, force) {\n var $element, elementData;\n $element = $(step.element);\n if (!$element || $element.length === 0 || this.backdrop.overlayElementShown && !force) {\n return;\n }\n if (!this.backdrop.overlayElementShown) {\n this.backdrop.$element = $element.addClass('tour-step-backdrop');\n this.backdrop.$background = $('
                                            ', {\n \"class\": 'tour-step-background'\n });\n this.backdrop.$background.appendTo(step.backdropContainer);\n this.backdrop.overlayElementShown = true;\n }\n elementData = {\n width: $element.innerWidth(),\n height: $element.innerHeight(),\n offset: $element.offset()\n };\n if (step.backdropPadding) {\n elementData = this._applyBackdropPadding(step.backdropPadding, elementData);\n }\n return this.backdrop.$background.width(elementData.width).height(elementData.height).offset(elementData.offset);\n };\n\n Tour.prototype._hideOverlayElement = function() {\n if (!this.backdrop.overlayElementShown) {\n return;\n }\n this.backdrop.$element.removeClass('tour-step-backdrop');\n this.backdrop.$background.remove();\n this.backdrop.$element = null;\n this.backdrop.$background = null;\n return this.backdrop.overlayElementShown = false;\n };\n\n Tour.prototype._applyBackdropPadding = function(padding, data) {\n if (typeof padding === 'object') {\n if (padding.top == null) {\n padding.top = 0;\n }\n if (padding.right == null) {\n padding.right = 0;\n }\n if (padding.bottom == null) {\n padding.bottom = 0;\n }\n if (padding.left == null) {\n padding.left = 0;\n }\n data.offset.top = data.offset.top - padding.top;\n data.offset.left = data.offset.left - padding.left;\n data.width = data.width + padding.left + padding.right;\n data.height = data.height + padding.top + padding.bottom;\n } else {\n data.offset.top = data.offset.top - padding;\n data.offset.left = data.offset.left - padding;\n data.width = data.width + (padding * 2);\n data.height = data.height + (padding * 2);\n }\n return data;\n };\n\n Tour.prototype._clearTimer = function() {\n window.clearTimeout(this._timer);\n this._timer = null;\n return this._duration = null;\n };\n\n Tour.prototype._getProtocol = function(url) {\n url = url.split('://');\n if (url.length > 1) {\n return url[0];\n } else {\n return 'http';\n }\n };\n\n Tour.prototype._getHost = function(url) {\n url = url.split('//');\n url = url.length > 1 ? url[1] : url[0];\n return url.split('/')[0];\n };\n\n Tour.prototype._getPath = function(path) {\n return path.replace(/\\/?$/, '').split('?')[0].split('#')[0];\n };\n\n Tour.prototype._getQuery = function(path) {\n return this._getParams(path, '?');\n };\n\n Tour.prototype._getHash = function(path) {\n return this._getParams(path, '#');\n };\n\n Tour.prototype._getParams = function(path, start) {\n var param, params, paramsObject, _i, _len;\n params = path.split(start);\n if (params.length === 1) {\n return {};\n }\n params = params[1].split('&');\n paramsObject = {};\n for (_i = 0, _len = params.length; _i < _len; _i++) {\n param = params[_i];\n param = param.split('=');\n paramsObject[param[0]] = param[1] || '';\n }\n return paramsObject;\n };\n\n Tour.prototype._equal = function(obj1, obj2) {\n var k, v;\n if ({}.toString.call(obj1) === '[object Object]' && {}.toString.call(obj2) === '[object Object]') {\n for (k in obj1) {\n v = obj1[k];\n if (obj2[k] !== v) {\n return false;\n }\n }\n for (k in obj2) {\n v = obj2[k];\n if (obj1[k] !== v) {\n return false;\n }\n }\n return true;\n }\n return obj1 === obj2;\n };\n\n return Tour;\n\n })();\n return window.Tour = Tour;\n})(jQuery, window);\n\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./galaxy/scripts/libs/bootstrap-tour.js\n ** module id = 60\n ** module chunks = 2\n **/","/*! jQuery UI - v1.9.1 - 2012-10-29\n* http://jqueryui.com\n* Includes: jquery.ui.core.js, jquery.ui.widget.js, jquery.ui.mouse.js, jquery.ui.position.js, jquery.ui.autocomplete.js, jquery.ui.button.js, jquery.ui.menu.js, jquery.ui.slider.js\n* Copyright (c) 2012 jQuery Foundation and other contributors Licensed MIT */\n\n(function(e,t){function i(t,n){var r,i,o,u=t.nodeName.toLowerCase();return\"area\"===u?(r=t.parentNode,i=r.name,!t.href||!i||r.nodeName.toLowerCase()!==\"map\"?!1:(o=e(\"img[usemap=#\"+i+\"]\")[0],!!o&&s(o))):(/input|select|textarea|button|object/.test(u)?!t.disabled:\"a\"===u?t.href||n:n)&&s(t)}function s(t){return e.expr.filters.visible(t)&&!e(t).parents().andSelf().filter(function(){return e.css(this,\"visibility\")===\"hidden\"}).length}var n=0,r=/^ui-id-\\d+$/;e.ui=e.ui||{};if(e.ui.version)return;e.extend(e.ui,{version:\"1.9.1\",keyCode:{BACKSPACE:8,COMMA:188,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SPACE:32,TAB:9,UP:38}}),e.fn.extend({_focus:e.fn.focus,focus:function(t,n){return typeof t==\"number\"?this.each(function(){var r=this;setTimeout(function(){e(r).focus(),n&&n.call(r)},t)}):this._focus.apply(this,arguments)},scrollParent:function(){var t;return e.ui.ie&&/(static|relative)/.test(this.css(\"position\"))||/absolute/.test(this.css(\"position\"))?t=this.parents().filter(function(){return/(relative|absolute|fixed)/.test(e.css(this,\"position\"))&&/(auto|scroll)/.test(e.css(this,\"overflow\")+e.css(this,\"overflow-y\")+e.css(this,\"overflow-x\"))}).eq(0):t=this.parents().filter(function(){return/(auto|scroll)/.test(e.css(this,\"overflow\")+e.css(this,\"overflow-y\")+e.css(this,\"overflow-x\"))}).eq(0),/fixed/.test(this.css(\"position\"))||!t.length?e(document):t},zIndex:function(n){if(n!==t)return this.css(\"zIndex\",n);if(this.length){var r=e(this[0]),i,s;while(r.length&&r[0]!==document){i=r.css(\"position\");if(i===\"absolute\"||i===\"relative\"||i===\"fixed\"){s=parseInt(r.css(\"zIndex\"),10);if(!isNaN(s)&&s!==0)return s}r=r.parent()}}return 0},uniqueId:function(){return this.each(function(){this.id||(this.id=\"ui-id-\"+ ++n)})},removeUniqueId:function(){return this.each(function(){r.test(this.id)&&e(this).removeAttr(\"id\")})}}),e(\"\").outerWidth(1).jquery||e.each([\"Width\",\"Height\"],function(n,r){function u(t,n,r,s){return e.each(i,function(){n-=parseFloat(e.css(t,\"padding\"+this))||0,r&&(n-=parseFloat(e.css(t,\"border\"+this+\"Width\"))||0),s&&(n-=parseFloat(e.css(t,\"margin\"+this))||0)}),n}var i=r===\"Width\"?[\"Left\",\"Right\"]:[\"Top\",\"Bottom\"],s=r.toLowerCase(),o={innerWidth:e.fn.innerWidth,innerHeight:e.fn.innerHeight,outerWidth:e.fn.outerWidth,outerHeight:e.fn.outerHeight};e.fn[\"inner\"+r]=function(n){return n===t?o[\"inner\"+r].call(this):this.each(function(){e(this).css(s,u(this,n)+\"px\")})},e.fn[\"outer\"+r]=function(t,n){return typeof t!=\"number\"?o[\"outer\"+r].call(this,t):this.each(function(){e(this).css(s,u(this,t,!0,n)+\"px\")})}}),e.extend(e.expr[\":\"],{data:e.expr.createPseudo?e.expr.createPseudo(function(t){return function(n){return!!e.data(n,t)}}):function(t,n,r){return!!e.data(t,r[3])},focusable:function(t){return i(t,!isNaN(e.attr(t,\"tabindex\")))},tabbable:function(t){var n=e.attr(t,\"tabindex\"),r=isNaN(n);return(r||n>=0)&&i(t,!r)}}),e(function(){var t=document.body,n=t.appendChild(n=document.createElement(\"div\"));n.offsetHeight,e.extend(n.style,{minHeight:\"100px\",height:\"auto\",padding:0,borderWidth:0}),e.support.minHeight=n.offsetHeight===100,e.support.selectstart=\"onselectstart\"in n,t.removeChild(n).style.display=\"none\"}),function(){var t=/msie ([\\w.]+)/.exec(navigator.userAgent.toLowerCase())||[];e.ui.ie=t.length?!0:!1,e.ui.ie6=parseFloat(t[1],10)===6}(),e.fn.extend({disableSelection:function(){return this.bind((e.support.selectstart?\"selectstart\":\"mousedown\")+\".ui-disableSelection\",function(e){e.preventDefault()})},enableSelection:function(){return this.unbind(\".ui-disableSelection\")}}),e.extend(e.ui,{plugin:{add:function(t,n,r){var i,s=e.ui[t].prototype;for(i in r)s.plugins[i]=s.plugins[i]||[],s.plugins[i].push([n,r[i]])},call:function(e,t,n){var r,i=e.plugins[t];if(!i||!e.element[0].parentNode||e.element[0].parentNode.nodeType===11)return;for(r=0;r0?!0:(t[r]=1,i=t[r]>0,t[r]=0,i)},isOverAxis:function(e,t,n){return e>t&&e\",options:{disabled:!1,create:null},_createWidget:function(t,r){r=e(r||this.defaultElement||this)[0],this.element=e(r),this.uuid=n++,this.eventNamespace=\".\"+this.widgetName+this.uuid,this.options=e.widget.extend({},this.options,this._getCreateOptions(),t),this.bindings=e(),this.hoverable=e(),this.focusable=e(),r!==this&&(e.data(r,this.widgetName,this),e.data(r,this.widgetFullName,this),this._on(this.element,{remove:function(e){e.target===r&&this.destroy()}}),this.document=e(r.style?r.ownerDocument:r.document||r),this.window=e(this.document[0].defaultView||this.document[0].parentWindow)),this._create(),this._trigger(\"create\",null,this._getCreateEventData()),this._init()},_getCreateOptions:e.noop,_getCreateEventData:e.noop,_create:e.noop,_init:e.noop,destroy:function(){this._destroy(),this.element.unbind(this.eventNamespace).removeData(this.widgetName).removeData(this.widgetFullName).removeData(e.camelCase(this.widgetFullName)),this.widget().unbind(this.eventNamespace).removeAttr(\"aria-disabled\").removeClass(this.widgetFullName+\"-disabled \"+\"ui-state-disabled\"),this.bindings.unbind(this.eventNamespace),this.hoverable.removeClass(\"ui-state-hover\"),this.focusable.removeClass(\"ui-state-focus\")},_destroy:e.noop,widget:function(){return this.element},option:function(n,r){var i=n,s,o,u;if(arguments.length===0)return e.widget.extend({},this.options);if(typeof n==\"string\"){i={},s=n.split(\".\"),n=s.shift();if(s.length){o=i[n]=e.widget.extend({},this.options[n]);for(u=0;u=9||!!t.button?this._mouseStarted?(this._mouseDrag(t),t.preventDefault()):(this._mouseDistanceMet(t)&&this._mouseDelayMet(t)&&(this._mouseStarted=this._mouseStart(this._mouseDownEvent,t)!==!1,this._mouseStarted?this._mouseDrag(t):this._mouseUp(t)),!this._mouseStarted):this._mouseUp(t)},_mouseUp:function(t){return e(document).unbind(\"mousemove.\"+this.widgetName,this._mouseMoveDelegate).unbind(\"mouseup.\"+this.widgetName,this._mouseUpDelegate),this._mouseStarted&&(this._mouseStarted=!1,t.target===this._mouseDownEvent.target&&e.data(t.target,this.widgetName+\".preventClickEvent\",!0),this._mouseStop(t)),!1},_mouseDistanceMet:function(e){return Math.max(Math.abs(this._mouseDownEvent.pageX-e.pageX),Math.abs(this._mouseDownEvent.pageY-e.pageY))>=this.options.distance},_mouseDelayMet:function(e){return this.mouseDelayMet},_mouseStart:function(e){},_mouseDrag:function(e){},_mouseStop:function(e){},_mouseCapture:function(e){return!0}})})(jQuery);(function(e,t){function h(e,t,n){return[parseInt(e[0],10)*(l.test(e[0])?t/100:1),parseInt(e[1],10)*(l.test(e[1])?n/100:1)]}function p(t,n){return parseInt(e.css(t,n),10)||0}e.ui=e.ui||{};var n,r=Math.max,i=Math.abs,s=Math.round,o=/left|center|right/,u=/top|center|bottom/,a=/[\\+\\-]\\d+%?/,f=/^\\w+/,l=/%$/,c=e.fn.position;e.position={scrollbarWidth:function(){if(n!==t)return n;var r,i,s=e(\"
                                            \"),o=s.children()[0];return e(\"body\").append(s),r=o.offsetWidth,s.css(\"overflow\",\"scroll\"),i=o.offsetWidth,r===i&&(i=s[0].clientWidth),s.remove(),n=r-i},getScrollInfo:function(t){var n=t.isWindow?\"\":t.element.css(\"overflow-x\"),r=t.isWindow?\"\":t.element.css(\"overflow-y\"),i=n===\"scroll\"||n===\"auto\"&&t.width0?\"right\":\"center\",vertical:u<0?\"top\":o>0?\"bottom\":\"middle\"};lr(i(o),i(u))?h.important=\"horizontal\":h.important=\"vertical\",t.using.call(this,e,h)}),a.offset(e.extend(C,{using:u}))})},e.ui.position={fit:{left:function(e,t){var n=t.within,i=n.isWindow?n.scrollLeft:n.offset.left,s=n.width,o=e.left-t.collisionPosition.marginLeft,u=i-o,a=o+t.collisionWidth-s-i,f;t.collisionWidth>s?u>0&&a<=0?(f=e.left+u+t.collisionWidth-s-i,e.left+=u-f):a>0&&u<=0?e.left=i:u>a?e.left=i+s-t.collisionWidth:e.left=i:u>0?e.left+=u:a>0?e.left-=a:e.left=r(e.left-o,e.left)},top:function(e,t){var n=t.within,i=n.isWindow?n.scrollTop:n.offset.top,s=t.within.height,o=e.top-t.collisionPosition.marginTop,u=i-o,a=o+t.collisionHeight-s-i,f;t.collisionHeight>s?u>0&&a<=0?(f=e.top+u+t.collisionHeight-s-i,e.top+=u-f):a>0&&u<=0?e.top=i:u>a?e.top=i+s-t.collisionHeight:e.top=i:u>0?e.top+=u:a>0?e.top-=a:e.top=r(e.top-o,e.top)}},flip:{left:function(e,t){var n=t.within,r=n.offset.left+n.scrollLeft,s=n.width,o=n.isWindow?n.scrollLeft:n.offset.left,u=e.left-t.collisionPosition.marginLeft,a=u-o,f=u+t.collisionWidth-s-o,l=t.my[0]===\"left\"?-t.elemWidth:t.my[0]===\"right\"?t.elemWidth:0,c=t.at[0]===\"left\"?t.targetWidth:t.at[0]===\"right\"?-t.targetWidth:0,h=-2*t.offset[0],p,d;if(a<0){p=e.left+l+c+h+t.collisionWidth-s-r;if(p<0||p0){d=e.left-t.collisionPosition.marginLeft+l+c+h-o;if(d>0||i(d)a&&(v<0||v0&&(d=e.top-t.collisionPosition.marginTop+c+h+p-o,e.top+c+h+p>f&&(d>0||i(d)10&&i<11,t.innerHTML=\"\",n.removeChild(t)}(),e.uiBackCompat!==!1&&function(e){var n=e.fn.position;e.fn.position=function(r){if(!r||!r.offset)return n.call(this,r);var i=r.offset.split(\" \"),s=r.at.split(\" \");return i.length===1&&(i[1]=i[0]),/^\\d/.test(i[0])&&(i[0]=\"+\"+i[0]),/^\\d/.test(i[1])&&(i[1]=\"+\"+i[1]),s.length===1&&(/left|center|right/.test(s[0])?s[1]=\"center\":(s[1]=s[0],s[0]=\"center\")),n.call(this,e.extend(r,{at:s[0]+i[0]+\" \"+s[1]+i[1],offset:t}))}}(jQuery)})(jQuery);(function(e,t){var n=0;e.widget(\"ui.autocomplete\",{version:\"1.9.1\",defaultElement:\"\",options:{appendTo:\"body\",autoFocus:!1,delay:300,minLength:1,position:{my:\"left top\",at:\"left bottom\",collision:\"none\"},source:null,change:null,close:null,focus:null,open:null,response:null,search:null,select:null},pending:0,_create:function(){var t,n,r;this.isMultiLine=this._isMultiLine(),this.valueMethod=this.element[this.element.is(\"input,textarea\")?\"val\":\"text\"],this.isNewMenu=!0,this.element.addClass(\"ui-autocomplete-input\").attr(\"autocomplete\",\"off\"),this._on(this.element,{keydown:function(i){if(this.element.prop(\"readOnly\")){t=!0,r=!0,n=!0;return}t=!1,r=!1,n=!1;var s=e.ui.keyCode;switch(i.keyCode){case s.PAGE_UP:t=!0,this._move(\"previousPage\",i);break;case s.PAGE_DOWN:t=!0,this._move(\"nextPage\",i);break;case s.UP:t=!0,this._keyEvent(\"previous\",i);break;case s.DOWN:t=!0,this._keyEvent(\"next\",i);break;case s.ENTER:case s.NUMPAD_ENTER:this.menu.active&&(t=!0,i.preventDefault(),this.menu.select(i));break;case s.TAB:this.menu.active&&this.menu.select(i);break;case s.ESCAPE:this.menu.element.is(\":visible\")&&(this._value(this.term),this.close(i),i.preventDefault());break;default:n=!0,this._searchTimeout(i)}},keypress:function(r){if(t){t=!1,r.preventDefault();return}if(n)return;var i=e.ui.keyCode;switch(r.keyCode){case i.PAGE_UP:this._move(\"previousPage\",r);break;case i.PAGE_DOWN:this._move(\"nextPage\",r);break;case i.UP:this._keyEvent(\"previous\",r);break;case i.DOWN:this._keyEvent(\"next\",r)}},input:function(e){if(r){r=!1,e.preventDefault();return}this._searchTimeout(e)},focus:function(){this.selectedItem=null,this.previous=this._value()},blur:function(e){if(this.cancelBlur){delete this.cancelBlur;return}clearTimeout(this.searching),this.close(e),this._change(e)}}),this._initSource(),this.menu=e(\"
                                              \").addClass(\"ui-autocomplete\").appendTo(this.document.find(this.options.appendTo||\"body\")[0]).menu({input:e(),role:null}).zIndex(this.element.zIndex()+1).hide().data(\"menu\"),this._on(this.menu.element,{mousedown:function(t){t.preventDefault(),this.cancelBlur=!0,this._delay(function(){delete this.cancelBlur});var n=this.menu.element[0];e(t.target).closest(\".ui-menu-item\").length||this._delay(function(){var t=this;this.document.one(\"mousedown\",function(r){r.target!==t.element[0]&&r.target!==n&&!e.contains(n,r.target)&&t.close()})})},menufocus:function(t,n){if(this.isNewMenu){this.isNewMenu=!1;if(t.originalEvent&&/^mouse/.test(t.originalEvent.type)){this.menu.blur(),this.document.one(\"mousemove\",function(){e(t.target).trigger(t.originalEvent)});return}}var r=n.item.data(\"ui-autocomplete-item\")||n.item.data(\"item.autocomplete\");!1!==this._trigger(\"focus\",t,{item:r})?t.originalEvent&&/^key/.test(t.originalEvent.type)&&this._value(r.value):this.liveRegion.text(r.value)},menuselect:function(e,t){var n=t.item.data(\"ui-autocomplete-item\")||t.item.data(\"item.autocomplete\"),r=this.previous;this.element[0]!==this.document[0].activeElement&&(this.element.focus(),this.previous=r,this._delay(function(){this.previous=r,this.selectedItem=n})),!1!==this._trigger(\"select\",e,{item:n})&&this._value(n.value),this.term=this._value(),this.close(e),this.selectedItem=n}}),this.liveRegion=e(\"\",{role:\"status\",\"aria-live\":\"polite\"}).addClass(\"ui-helper-hidden-accessible\").insertAfter(this.element),e.fn.bgiframe&&this.menu.element.bgiframe(),this._on(this.window,{beforeunload:function(){this.element.removeAttr(\"autocomplete\")}})},_destroy:function(){clearTimeout(this.searching),this.element.removeClass(\"ui-autocomplete-input\").removeAttr(\"autocomplete\"),this.menu.element.remove(),this.liveRegion.remove()},_setOption:function(e,t){this._super(e,t),e===\"source\"&&this._initSource(),e===\"appendTo\"&&this.menu.element.appendTo(this.document.find(t||\"body\")[0]),e===\"disabled\"&&t&&this.xhr&&this.xhr.abort()},_isMultiLine:function(){return this.element.is(\"textarea\")?!0:this.element.is(\"input\")?!1:this.element.prop(\"isContentEditable\")},_initSource:function(){var t,n,r=this;e.isArray(this.options.source)?(t=this.options.source,this.source=function(n,r){r(e.ui.autocomplete.filter(t,n.term))}):typeof this.options.source==\"string\"?(n=this.options.source,this.source=function(t,i){r.xhr&&r.xhr.abort(),r.xhr=e.ajax({url:n,data:t,dataType:\"json\",success:function(e){i(e)},error:function(){i([])}})}):this.source=this.options.source},_searchTimeout:function(e){clearTimeout(this.searching),this.searching=this._delay(function(){this.term!==this._value()&&(this.selectedItem=null,this.search(null,e))},this.options.delay)},search:function(e,t){e=e!=null?e:this._value(),this.term=this._value();if(e.length\").append(e(\"\").text(n.label)).appendTo(t)},_move:function(e,t){if(!this.menu.element.is(\":visible\")){this.search(null,t);return}if(this.menu.isFirstItem()&&/^previous/.test(e)||this.menu.isLastItem()&&/^next/.test(e)){this._value(this.term),this.menu.blur();return}this.menu[e](t)},widget:function(){return this.menu.element},_value:function(){return this.valueMethod.apply(this.element,arguments)},_keyEvent:function(e,t){if(!this.isMultiLine||this.menu.element.is(\":visible\"))this._move(e,t),t.preventDefault()}}),e.extend(e.ui.autocomplete,{escapeRegex:function(e){return e.replace(/[\\-\\[\\]{}()*+?.,\\\\\\^$|#\\s]/g,\"\\\\$&\")},filter:function(t,n){var r=new RegExp(e.ui.autocomplete.escapeRegex(n),\"i\");return e.grep(t,function(e){return r.test(e.label||e.value||e)})}}),e.widget(\"ui.autocomplete\",e.ui.autocomplete,{options:{messages:{noResults:\"No search results.\",results:function(e){return e+(e>1?\" results are\":\" result is\")+\" available, use up and down arrow keys to navigate.\"}}},__response:function(e){var t;this._superApply(arguments);if(this.options.disabled||this.cancelSearch)return;e&&e.length?t=this.options.messages.results(e.length):t=this.options.messages.noResults,this.liveRegion.text(t)}})})(jQuery);(function(e,t){var n,r,i,s,o=\"ui-button ui-widget ui-state-default ui-corner-all\",u=\"ui-state-hover ui-state-active \",a=\"ui-button-icons-only ui-button-icon-only ui-button-text-icons ui-button-text-icon-primary ui-button-text-icon-secondary ui-button-text-only\",f=function(){var t=e(this).find(\":ui-button\");setTimeout(function(){t.button(\"refresh\")},1)},l=function(t){var n=t.name,r=t.form,i=e([]);return n&&(r?i=e(r).find(\"[name='\"+n+\"']\"):i=e(\"[name='\"+n+\"']\",t.ownerDocument).filter(function(){return!this.form})),i};e.widget(\"ui.button\",{version:\"1.9.1\",defaultElement:\"