mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-21 05:45:37 +08:00
Merge remote-tracking branch 'jmchilton/dev' into neo_datatype
This commit is contained in:
Executable
+2
@@ -0,0 +1,2 @@
|
||||
exclude=$(sed -e 's|^|./|' -e 's|/$||' .ci/flake8_blacklist.txt | paste -s -d ',' - )
|
||||
autopep8 -i -r --exclude $exclude --select E11,E101,E127,E201,E202,E22,E301,E302,E303,E304,E306,E711,W291,W292,W293,W391 ./lib/ ./test/
|
||||
@@ -1,397 +0,0 @@
|
||||
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/
|
||||
lib/galaxy/config.py
|
||||
lib/galaxy/containers/
|
||||
lib/galaxy/dataset_collections/builder.py
|
||||
lib/galaxy/dataset_collections/__init__.py
|
||||
lib/galaxy/dataset_collections/structure.py
|
||||
lib/galaxy/dataset_collections/subcollections.py
|
||||
lib/galaxy/dataset_collections/type_description.py
|
||||
lib/galaxy/dataset_collections/types/__init__.py
|
||||
lib/galaxy/datatypes/assembly.py
|
||||
lib/galaxy/datatypes/binary.py
|
||||
lib/galaxy/datatypes/checkers.py
|
||||
lib/galaxy/datatypes/constructive_solid_geometry.py
|
||||
lib/galaxy/datatypes/converters/bcf_bgzip_to_bcf_converter.py
|
||||
lib/galaxy/datatypes/converters/bcf_to_bcf_bgzip_converter.py
|
||||
lib/galaxy/datatypes/converters/bedgraph_to_array_tree_converter.py
|
||||
lib/galaxy/datatypes/converters/bed_to_gff_converter.py
|
||||
lib/galaxy/datatypes/converters/bgzip.py
|
||||
lib/galaxy/datatypes/converters/fasta_to_len.py
|
||||
lib/galaxy/datatypes/converters/fastqsolexa_to_fasta_converter.py
|
||||
lib/galaxy/datatypes/converters/fastqsolexa_to_qual_converter.py
|
||||
lib/galaxy/datatypes/converters/fastq_to_fqtoc.py
|
||||
lib/galaxy/datatypes/converters/gff_to_bed_converter.py
|
||||
lib/galaxy/datatypes/converters/gff_to_interval_index_converter.py
|
||||
lib/galaxy/datatypes/converters/__init__.py
|
||||
lib/galaxy/datatypes/converters/interval_to_bed_converter.py
|
||||
lib/galaxy/datatypes/converters/interval_to_bedstrict_converter.py
|
||||
lib/galaxy/datatypes/converters/interval_to_coverage.py
|
||||
lib/galaxy/datatypes/converters/interval_to_fli.py
|
||||
lib/galaxy/datatypes/converters/interval_to_interval_index_converter.py
|
||||
lib/galaxy/datatypes/converters/interval_to_tabix_converter.py
|
||||
lib/galaxy/datatypes/converters/lped_to_fped_converter.py
|
||||
lib/galaxy/datatypes/converters/lped_to_pbed_converter.py
|
||||
lib/galaxy/datatypes/converters/maf_to_fasta_converter.py
|
||||
lib/galaxy/datatypes/converters/maf_to_interval_converter.py
|
||||
lib/galaxy/datatypes/converters/pbed_to_lped_converter.py
|
||||
lib/galaxy/datatypes/converters/picard_interval_list_to_bed6_converter.py
|
||||
lib/galaxy/datatypes/converters/pileup_to_interval_index_converter.py
|
||||
lib/galaxy/datatypes/converters/tabular_to_dbnsfp.py
|
||||
lib/galaxy/datatypes/converters/vcf_to_vcf_bgzip.py
|
||||
lib/galaxy/datatypes/converters/wiggle_to_array_tree_converter.py
|
||||
lib/galaxy/datatypes/coverage.py
|
||||
lib/galaxy/datatypes/dataproviders/base.py
|
||||
lib/galaxy/datatypes/dataproviders/exceptions.py
|
||||
lib/galaxy/datatypes/dataproviders/__init__.py
|
||||
lib/galaxy/datatypes/data.py
|
||||
lib/galaxy/datatypes/display_applications/__init__.py
|
||||
lib/galaxy/datatypes/display_applications/util.py
|
||||
lib/galaxy/datatypes/genetics.py
|
||||
lib/galaxy/datatypes/images.py
|
||||
lib/galaxy/datatypes/__init__.py
|
||||
lib/galaxy/datatypes/interval.py
|
||||
lib/galaxy/datatypes/metadata.py
|
||||
lib/galaxy/datatypes/msa.py
|
||||
lib/galaxy/datatypes/ngsindex.py
|
||||
lib/galaxy/datatypes/proteomics.py
|
||||
lib/galaxy/datatypes/sequence.py
|
||||
lib/galaxy/datatypes/tabular.py
|
||||
lib/galaxy/datatypes/text.py
|
||||
lib/galaxy/datatypes/tracks.py
|
||||
lib/galaxy/datatypes/util/
|
||||
lib/galaxy/eggs/
|
||||
lib/galaxy/exceptions/__init__.py
|
||||
lib/galaxy/external_services/__init__.py
|
||||
lib/galaxy/external_services/parameters.py
|
||||
lib/galaxy/external_services/result_handlers/basic.py
|
||||
lib/galaxy/external_services/result_handlers/__init__.py
|
||||
lib/galaxy_ext/
|
||||
lib/galaxy/forms/__init__.py
|
||||
lib/galaxy/jobs/actions/
|
||||
lib/galaxy/jobs/command_factory.py
|
||||
lib/galaxy/jobs/datasets.py
|
||||
lib/galaxy/jobs/deferred/
|
||||
lib/galaxy/jobs/error_level.py
|
||||
lib/galaxy/jobs/__init__.py
|
||||
lib/galaxy/jobs/manager.py
|
||||
lib/galaxy/jobs/metrics/
|
||||
lib/galaxy/jobs/rules/
|
||||
lib/galaxy/jobs/runners/cli.py
|
||||
lib/galaxy/jobs/runners/condor.py
|
||||
lib/galaxy/jobs/runners/drmaa.py
|
||||
lib/galaxy/jobs/runners/godocker.py
|
||||
lib/galaxy/jobs/runners/kubernetes.py
|
||||
lib/galaxy/jobs/runners/local.py
|
||||
lib/galaxy/jobs/runners/pbs.py
|
||||
lib/galaxy/jobs/runners/pulsar.py
|
||||
lib/galaxy/jobs/runners/slurm.py
|
||||
lib/galaxy/jobs/runners/state_handlers/
|
||||
lib/galaxy/jobs/runners/tasks.py
|
||||
lib/galaxy/jobs/runners/util/
|
||||
lib/galaxy/jobs/splitters/basic.py
|
||||
lib/galaxy/jobs/splitters/__init__.py
|
||||
lib/galaxy/jobs/stock_rules.py
|
||||
lib/galaxy/jobs/transfer_manager.py
|
||||
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
|
||||
lib/galaxy/managers/lddas.py
|
||||
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/
|
||||
lib/galaxy/model/orm/
|
||||
lib/galaxy/model/tool_shed_install/migrate/__init__.py
|
||||
lib/galaxy/model/tool_shed_install/migrate/versions/
|
||||
lib/galaxy/model/util.py
|
||||
lib/galaxy/objectstore/pulsar.py
|
||||
lib/galaxy/objectstore/s3_multipart_upload.py
|
||||
lib/galaxy/openid/__init__.py
|
||||
lib/galaxy/quota/
|
||||
lib/galaxy/sample_tracking/data_transfer.py
|
||||
lib/galaxy/sample_tracking/__init__.py
|
||||
lib/galaxy/sample_tracking/sample.py
|
||||
lib/galaxy/security/validate_user_input.py
|
||||
lib/galaxy/tags/
|
||||
lib/galaxy/tools/
|
||||
lib/galaxy/util/
|
||||
lib/galaxy/version.py
|
||||
lib/galaxy/visualization/data_providers/basic.py
|
||||
lib/galaxy/visualization/data_providers/cigar.py
|
||||
lib/galaxy/visualization/data_providers/__init__.py
|
||||
lib/galaxy/visualization/data_providers/phyloviz/baseparser.py
|
||||
lib/galaxy/visualization/genome/
|
||||
lib/galaxy/visualization/genomes.py
|
||||
lib/galaxy/visualization/__init__.py
|
||||
lib/galaxy/visualization/plugins/__init__.py
|
||||
lib/galaxy/visualization/plugins/utils.py
|
||||
lib/galaxy/visualization/tracks/
|
||||
lib/galaxy/web/base/controllers/__init__.py
|
||||
lib/galaxy/web/base/__init__.py
|
||||
lib/galaxy/web/base/interactive_environments.py
|
||||
lib/galaxy/web/buildapp.py
|
||||
lib/galaxy/web/formatting.py
|
||||
lib/galaxy/web/framework/base.py
|
||||
lib/galaxy/web/framework/decorators.py
|
||||
lib/galaxy/web/framework/helpers/grids.py
|
||||
lib/galaxy/web/framework/__init__.py
|
||||
lib/galaxy/web/framework/middleware/error.py
|
||||
lib/galaxy/web/framework/middleware/__init__.py
|
||||
lib/galaxy/web/framework/middleware/request_id.py
|
||||
lib/galaxy/web/framework/middleware/sentry.py
|
||||
lib/galaxy/web/framework/middleware/static.py
|
||||
lib/galaxy/web/framework/middleware/statsd.py
|
||||
lib/galaxy/web/framework/middleware/translogger.py
|
||||
lib/galaxy/web/framework/middleware/xforwardedhost.py
|
||||
lib/galaxy/web/__init__.py
|
||||
lib/galaxy/web/params.py
|
||||
lib/galaxy/webapps/galaxy/api/genomes.py
|
||||
lib/galaxy/webapps/galaxy/api/histories.py
|
||||
lib/galaxy/webapps/galaxy/api/__init__.py
|
||||
lib/galaxy/webapps/galaxy/api/jobs.py
|
||||
lib/galaxy/webapps/galaxy/api/lda_datasets.py
|
||||
lib/galaxy/webapps/galaxy/api/requests.py
|
||||
lib/galaxy/webapps/galaxy/api/roles.py
|
||||
lib/galaxy/webapps/galaxy/api/samples.py
|
||||
lib/galaxy/webapps/galaxy/api/tools.py
|
||||
lib/galaxy/webapps/galaxy/api/tours.py
|
||||
lib/galaxy/webapps/galaxy/api/workflows.py
|
||||
lib/galaxy/webapps/galaxy/controllers/async.py
|
||||
lib/galaxy/webapps/galaxy/controllers/data_manager.py
|
||||
lib/galaxy/webapps/galaxy/controllers/error.py
|
||||
lib/galaxy/webapps/galaxy/controllers/external_services.py
|
||||
lib/galaxy/webapps/galaxy/controllers/history.py
|
||||
lib/galaxy/webapps/galaxy/controllers/__init__.py
|
||||
lib/galaxy/webapps/galaxy/controllers/mobile.py
|
||||
lib/galaxy/webapps/galaxy/controllers/page.py
|
||||
lib/galaxy/webapps/galaxy/controllers/requests.py
|
||||
lib/galaxy/webapps/galaxy/controllers/search.py
|
||||
lib/galaxy/webapps/galaxy/controllers/tool_runner.py
|
||||
lib/galaxy/webapps/galaxy/controllers/userskeys.py
|
||||
lib/galaxy/webapps/galaxy/config_watchers.py
|
||||
lib/galaxy/webapps/galaxy/__init__.py
|
||||
lib/galaxy/webapps/__init__.py
|
||||
lib/galaxy/webapps/reports/config.py
|
||||
lib/galaxy/webapps/reports/controllers/__init__.py
|
||||
lib/galaxy/webapps/reports/controllers/query.py
|
||||
lib/galaxy/webapps/reports/__init__.py
|
||||
lib/galaxy/webapps/tool_shed/api/__init__.py
|
||||
lib/galaxy/webapps/tool_shed/controllers/groups.py
|
||||
lib/galaxy/webapps/tool_shed/controllers/__init__.py
|
||||
lib/galaxy/webapps/tool_shed/controllers/user.py
|
||||
lib/galaxy/webapps/tool_shed/framework/__init__.py
|
||||
lib/galaxy/webapps/tool_shed/framework/middleware/__init__.py
|
||||
lib/galaxy/webapps/tool_shed/framework/middleware/remoteuser.py
|
||||
lib/galaxy/webapps/tool_shed/__init__.py
|
||||
lib/galaxy/webapps/tool_shed/model/mapping.py
|
||||
lib/galaxy/webapps/tool_shed/model/migrate/
|
||||
lib/galaxy/webapps/tool_shed/search/__init__.py
|
||||
lib/galaxy/webapps/tool_shed/security/
|
||||
lib/galaxy/webapps/tool_shed/util/__init__.py
|
||||
lib/galaxy/webapps/tool_shed/util/ratings_util.py
|
||||
lib/galaxy/webapps/tool_shed/util/shed_statistics.py
|
||||
lib/galaxy/webapps/util.py
|
||||
lib/galaxy/workflow/extract.py
|
||||
lib/galaxy/workflow/__init__.py
|
||||
lib/galaxy/workflow/render.py
|
||||
lib/galaxy/workflow/run.py
|
||||
lib/galaxy/workflow/schedulers/
|
||||
lib/galaxy/workflow/steps.py
|
||||
lib/galaxy/work/__init__.py
|
||||
lib/mimeparse.py
|
||||
lib/psyco_full.py
|
||||
lib/tool_shed/
|
||||
scripts/api/common.py
|
||||
scripts/api/copy_hda_to_library_folder.py
|
||||
scripts/api/create.py
|
||||
scripts/api/delete.py
|
||||
scripts/api/example_watch_folder.py
|
||||
scripts/api/filter_failed_datasets_from_collection.py
|
||||
scripts/api/form_create_from_xml.py
|
||||
scripts/api/history_create_history.py
|
||||
scripts/api/history_delete_history.py
|
||||
scripts/api/import_library_dataset_to_history.py
|
||||
scripts/api/import_workflows_from_installed_tool_shed_repository.py
|
||||
scripts/api/install_tool_shed_repositories.py
|
||||
scripts/api/library_create_folder.py
|
||||
scripts/api/library_create_library.py
|
||||
scripts/api/library_upload_dir.py
|
||||
scripts/api/library_upload_from_import_dir.py
|
||||
scripts/api/load_data_with_metadata.py
|
||||
scripts/api/repair_tool_shed_repository.py
|
||||
scripts/api/requests_update_state.py
|
||||
scripts/api/request_type_create_from_xml.py
|
||||
scripts/api/reset_metadata_on_installed_repositories.py
|
||||
scripts/api/sample_dataset_update_status.py
|
||||
scripts/api/sample_update_state.py
|
||||
scripts/api/sequencer_configuration_create.py
|
||||
scripts/api/update.py
|
||||
scripts/api/upload_to_history.py
|
||||
scripts/api/workflow_delete.py
|
||||
scripts/api/workflow_execute_parameters.py
|
||||
scripts/api/workflow_execute.py
|
||||
scripts/api/workflow_import_from_file_rpark.py
|
||||
scripts/api/workflow_import.py
|
||||
scripts/auth/
|
||||
scripts/bootstrap_history.py
|
||||
scripts/build_toolbox.py
|
||||
scripts/build_universe_config.py
|
||||
scripts/check_galaxy.py
|
||||
scripts/check_python.py
|
||||
scripts/cleanup_datasets/
|
||||
scripts/create_db.py
|
||||
scripts/data_libraries/
|
||||
scripts/db_shell.py
|
||||
scripts/drmaa_external_killer.py
|
||||
scripts/drmaa_external_runner.py
|
||||
scripts/external_chown_script.py
|
||||
scripts/extract_dataset_part.py
|
||||
scripts/extract_toolbox_sections.py
|
||||
scripts/functional_tests.py
|
||||
scripts/get_platforms.py
|
||||
scripts/helper.py
|
||||
scripts/__init__.py
|
||||
scripts/loc_files/
|
||||
scripts/manage_db.py
|
||||
scripts/manage_tools.py
|
||||
scripts/metagenomics/
|
||||
scripts/microbes/create_bacteria_loc_file.py
|
||||
scripts/microbes/create_bacteria_table.py
|
||||
scripts/microbes/create_nib_seq_loc_file.py
|
||||
scripts/microbes/get_builds_lengths.py
|
||||
scripts/microbes/ncbi_to_ucsc.py
|
||||
scripts/microbes/util.py
|
||||
scripts/migrate_tools/
|
||||
scripts/nosetests.py
|
||||
scripts/others/
|
||||
scripts/runtime_stats.py
|
||||
scripts/set_dataset_sizes.py
|
||||
scripts/set_user_disk_usage.py
|
||||
scripts/sync_reports_config.py
|
||||
scripts/tool_shed/api/add_repository_registry_entry.py
|
||||
scripts/tool_shed/api/common.py
|
||||
scripts/tool_shed/api/create_categories.py
|
||||
scripts/tool_shed/api/create_users.py
|
||||
scripts/tool_shed/api/import_capsule.py
|
||||
scripts/tool_shed/api/remove_repository_registry_entry.py
|
||||
scripts/tool_shed/api/reset_metadata_on_repositories.py
|
||||
scripts/tool_shed/api/tool_shed_repository_revision_update.py
|
||||
scripts/tool_shed/build_ts_whoosh_index.py
|
||||
scripts/tool_shed/check_download_urls.py
|
||||
scripts/tool_shed/check_filesystem_for_empty_tool_dependency_installation_paths.py
|
||||
scripts/tool_shed/clean_up_tool_dependency_directory.py
|
||||
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_framework.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_tours.py
|
||||
test/api/test_workflow_extraction.py
|
||||
test/api/test_workflows_from_yaml.py
|
||||
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/base/workflows_format_2/
|
||||
test/casperjs/
|
||||
test/functional/database_contexts.py
|
||||
test/functional/__init__.py
|
||||
test/functional/test_data_managers.py
|
||||
test/functional/tool-data/
|
||||
test/functional/tools/
|
||||
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/
|
||||
test/unit/datatypes/dataproviders/__init__.py
|
||||
test/unit/datatypes/dataproviders/test_line_dataproviders.py
|
||||
test/unit/datatypes/__init__.py
|
||||
test/unit/datatypes/test_data.py
|
||||
test/unit/jobs/dynamic_tool_destination/
|
||||
test/unit/jobs/__init__.py
|
||||
test/unit/jobs/test_command_factory.py
|
||||
test/unit/jobs/test_datasets.py
|
||||
test/unit/jobs/test_job_configuration.py
|
||||
test/unit/jobs/test_job_output_checker.py
|
||||
test/unit/jobs/test_mapper.py
|
||||
test/unit/jobs/test_rule_helper.py
|
||||
test/unit/jobs/test_rules/
|
||||
test/unit/jobs/test_runner_params.py
|
||||
test/unit/managers/__init__.py
|
||||
test/unit/managers/test_CollectionManager.py
|
||||
test/unit/managers/test_DatasetManager.py
|
||||
test/unit/managers/test_HDAManager.py
|
||||
test/unit/managers/test_HDCAManager.py
|
||||
test/unit/managers/test_HistoryContentsManager.py
|
||||
test/unit/managers/test_UserManager.py
|
||||
test/unit/shed_unit/__init__.py
|
||||
test/unit/shed_unit/test_fabric_util.py
|
||||
test/unit/shed_unit/test_td_common_util.py
|
||||
test/unit/test_galaxy_transactions.py
|
||||
test/unit/test_lazy_process.py
|
||||
test/unit/test_routes.py
|
||||
test/unit/test_security_helper.py
|
||||
test/unit/test_sockets.py
|
||||
test/unit/test_sqlite_utils.py
|
||||
test/unit/test_topsort.py
|
||||
test/unit/tools/filter_modules/
|
||||
test/unit/tools/__init__.py
|
||||
test/unit/tools/test_citations.py
|
||||
test/unit/tools/test_conda_resolution.py
|
||||
test/unit/tools/test_parsing.py
|
||||
test/unit/tools/test_select_parameters.py
|
||||
test/unit/tools/test_toolbox_filters.py
|
||||
test/unit/tools/test_tool_dependency_description.py
|
||||
test/unit/tools/test_tool_deps.py
|
||||
test/unit/tools/test_tool_external_files.py
|
||||
test/unit/tools/test_tool_loader.py
|
||||
test/unit/tools/test_tool_panel.py
|
||||
test/unit/tools/test_watcher.py
|
||||
test/unit/tools/test_wrappers.py
|
||||
test/unit/tools_support.py
|
||||
test/unit/unittest_utils/
|
||||
test/unit/visualizations/__init__.py
|
||||
test/unit/visualizations/plugins/__init__.py
|
||||
test/unit/web/base/__init__.py
|
||||
test/unit/web/framework/__init__.py
|
||||
test/unit/web/__init__.py
|
||||
test/unit/workflows/
|
||||
test/unit/test_objectstore.py
|
||||
tool_list.py
|
||||
tools/
|
||||
@@ -1,5 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
flake8 --exclude `paste -sd, .ci/flake8_blacklist.txt` `paste -s .ci/py3_sources.txt`
|
||||
@@ -1,5 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
flake8 `paste .ci/flake8_lint_include_list.txt`
|
||||
@@ -2,11 +2,11 @@ FROM toolshed/requirements
|
||||
MAINTAINER John Chilton, jmchilton@gmail.com
|
||||
|
||||
RUN apt-get -qq update && \
|
||||
apt-get install --no-install-recommends -y postgresql-client python-pip && \
|
||||
apt-get install --no-install-recommends -y postgresql-client python-pip libffi-dev python-cffi && \
|
||||
apt-get autoremove -y && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
ENV GALAXY_ROOT=/galaxy \
|
||||
GALAXY_CONFIG_OVERRIDE_ADMIN_USERS=admin@galaxy.org \
|
||||
GALAXY_CONFIG_OVERRIDE_ADMIN_USERS=admin@galaxy.org,test@bx.psu.edu \
|
||||
GALAXY_CONFIG_OVERRIDE_ALLOW_USER_DATASET_PURGE=true \
|
||||
GALAXY_CONFIG_OVERRIDE_ALLOW_LIBRARY_PATH_PASTE=true \
|
||||
GALAXY_CONFIG_OVERRIDE_ENABLE_BETA_WORKFLOW_MODULES=true \
|
||||
+3
-1
@@ -11,8 +11,10 @@ services:
|
||||
- postgres
|
||||
ports:
|
||||
- "${GALAXY_PORT}:8080"
|
||||
environment:
|
||||
GALAXY_TEST_UID: "${MY_UID}"
|
||||
selenium:
|
||||
image: selenium/standalone-chrome:3.0.1-aluminum
|
||||
image: selenium/standalone-chrome:3.5.2
|
||||
ports:
|
||||
- "${SELENIUM_PORT}:4444"
|
||||
links:
|
||||
@@ -6,6 +6,12 @@ host = 0.0.0.0
|
||||
use_threadpool = True
|
||||
threadpool_kill_thread_limit = 10800
|
||||
|
||||
[filter:proxy-prefix]
|
||||
use = egg:PasteDeploy#prefix
|
||||
prefix = /galaxypf
|
||||
|
||||
[app:main]
|
||||
|
||||
paste.app_factory = galaxy.web.buildapp:app_factory
|
||||
|
||||
filter-with = proxy-prefix
|
||||
Executable
+40
@@ -0,0 +1,40 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
# Same hacks to setup a Galaxy user as used in test/docker/base/run_test_wrapper.sh
|
||||
# We want to make sure Galaxy runs as the same user as the external user so the files have the correct permission.
|
||||
echo "Deleting galaxy user - it may not exist and this is fine."
|
||||
deluser galaxy | true
|
||||
|
||||
: ${GALAXY_TEST_UID:-"1"}
|
||||
|
||||
echo "Creating galaxy group with gid $GALAXY_TEST_UID - it may already exist and this is fine."
|
||||
groupadd -r galaxy -g "$GALAXY_TEST_UID" | true
|
||||
echo "Creating galaxy user with uid $GALAXY_TEST_UID - it may already exist and this is fine."
|
||||
useradd -u $GALAXY_TEST_UID -r -g galaxy -d /home/galaxy -c "Galaxy User" galaxy -s /bin/bash | true
|
||||
echo "Setting galaxy user password - the operation may fail."
|
||||
echo "galaxy:galaxy" | chpasswd | true
|
||||
|
||||
virtualenv "$GALAXY_VIRTUAL_ENV"
|
||||
chown -R "$GALAXY_TEST_UID:$GALAXY_TEST_UID" "$GALAXY_VIRTUAL_ENV"
|
||||
|
||||
cd /galaxy
|
||||
HOME=/galaxy
|
||||
sudo -E -u "#${GALAXY_TEST_UID}" ./scripts/common_startup.sh || { echo "common_startup.sh failed"; exit 1; }
|
||||
|
||||
echo "Waiting for postgres to become available"
|
||||
while ! nc -z postgres 5432;
|
||||
do
|
||||
sleep 1
|
||||
printf "."
|
||||
done
|
||||
|
||||
echo "Creating postgres database for Galaxy"
|
||||
createdb -w -U postgres -h postgres galaxy
|
||||
|
||||
echo "Starting and waiting for Galaxy daemon(s)"
|
||||
sudo -E -u "#${GALAXY_TEST_UID}" GALAXY_RUN_ALL=1 bash "$GALAXY_ROOT/run.sh" --daemon --wait
|
||||
|
||||
echo "Galaxy daemon ready, monitoring Galaxy logs"
|
||||
tail -f "$GALAXY_ROOT/main.log"
|
||||
Executable
+124
@@ -0,0 +1,124 @@
|
||||
#!/bin/bash
|
||||
|
||||
TEST_DIRECTORY=`dirname $0`
|
||||
|
||||
DEFAULT_COMPOSE_PROJECT_NAME=`basename $TEST_DIRECTORY`
|
||||
COMPOSE_PROJECT_NAME=${COMPOSE_PROJECT_NAME:-$DEFAULT_COMPOSE_PROJECT_NAME}
|
||||
# If in Jenkins environment, append ${BUILD_NUMBER} to project so builds don't interfer.
|
||||
if [ ! -z "$BUILD_NUMBER" ];
|
||||
then
|
||||
COMPOSE_PROJECT_NAME="${COMPOSE_PROJECT_NAME}${BUILD_NUMBER}"
|
||||
fi
|
||||
export COMPOSE_PROJECT_NAME
|
||||
|
||||
echo "Running Jenkins test from $TEST_DIRECTORY with compose project name $COMPOSE_PROJECT_NAME"
|
||||
|
||||
find lib -iname '*pyc' -exec rm -rf {} \;
|
||||
find test -iname '*pyc' -exec rm -rf {} \;
|
||||
|
||||
./scripts/common_startup.sh --dev-wheels
|
||||
|
||||
. .venv/bin/activate
|
||||
|
||||
pip install docker-compose
|
||||
|
||||
# TODO: Let docker-compose pick these at random.
|
||||
export GALAXY_PORT=`python -c 'import socket; s=socket.socket(); s.bind(("", 0)); print(s.getsockname()[1]); s.close()'`
|
||||
export SELENIUM_PORT=`python -c 'import socket; s=socket.socket(); s.bind(("", 0)); print(s.getsockname()[1]); s.close()'`
|
||||
|
||||
export TARGET_ROOT=`pwd`
|
||||
export TARGET_PATH=/galaxy
|
||||
export MY_UID=$(id -u)
|
||||
|
||||
cd $TEST_DIRECTORY
|
||||
|
||||
echo "Cleaning up previous executions if needed."
|
||||
docker-compose down | true
|
||||
docker-compose build galaxy
|
||||
docker-compose up -d
|
||||
|
||||
function tear_down {
|
||||
docker-compose down
|
||||
}
|
||||
|
||||
trap tear_down EXIT
|
||||
|
||||
for service_name in postgres galaxy selenium
|
||||
do
|
||||
echo "Waiting on service ${service_name}"
|
||||
while true
|
||||
do
|
||||
if docker ps | grep -q "${COMPOSE_PROJECT_NAME}_${service_name}"
|
||||
then
|
||||
container_id=`docker ps | grep "${COMPOSE_PROJECT_NAME}_${service_name}" | cut -d " " -f 1`
|
||||
eval "${service_name}_container_id=${container_id}"
|
||||
echo "Service ${service_name} ready - with container ID ${container_id}"
|
||||
break
|
||||
fi
|
||||
printf "."
|
||||
sleep 1;
|
||||
done
|
||||
done
|
||||
|
||||
|
||||
if [ "$1" = "--debug-running-containers" ];
|
||||
then
|
||||
env
|
||||
exit 0
|
||||
fi
|
||||
|
||||
export GALAXY_TEST_EXTERNAL="http://localhost:$GALAXY_PORT/"
|
||||
|
||||
echo "Waiting on docker-compose managed Galaxy server - $GALAXY_TEST_EXTERNAL."
|
||||
while ! curl -s "$GALAXY_TEST_EXTERNAL";
|
||||
do
|
||||
for service_name in postgres galaxy selenium
|
||||
do
|
||||
if ! docker ps | grep -q "${COMPOSE_PROJECT_NAME}_${service_name}"
|
||||
then
|
||||
echo "Service ${service_name} stopped before Galaxy came up, exiting and halting containers."
|
||||
for service_name in postgres galaxy selenium
|
||||
do
|
||||
container_id_var="${service_name}_container_id"
|
||||
container_id="${!container_id_var}"
|
||||
echo "Dumping logs for $service_name container (${container_id})..."
|
||||
echo "---"
|
||||
docker logs "${container_id}"
|
||||
echo "---"
|
||||
done
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
printf "."
|
||||
sleep 4;
|
||||
done;
|
||||
|
||||
# Access Selenium on localhost via port $SELENIUM_PORT
|
||||
export GALAXY_TEST_SELENIUM_REMOTE=1
|
||||
export GALAXY_TEST_SELENIUM_REMOTE_PORT="${SELENIUM_PORT}"
|
||||
|
||||
# Retry all failed Selenium tests a second time to deal
|
||||
# with transiently failing tests. Failure information for
|
||||
# first tests is still populated in database/test_errors
|
||||
# and available at the top of the Jenkins test report.
|
||||
export GALAXY_TEST_SELENIUM_RETRIES=1
|
||||
|
||||
# Access Galaxy on localhost via port $GALAXY_PORT
|
||||
export GALAXY_TEST_PORT="${GALAXY_PORT}"
|
||||
|
||||
# Have Selenium access Galaxy at this URL
|
||||
export GALAXY_TEST_EXTERNAL_FROM_SELENIUM="http://galaxy:8080/galaxypf"
|
||||
export GALAXY_TEST_EXTERNAL="http://localhost:${GALAXY_TEST_PORT}/galaxypf"
|
||||
|
||||
# Point tests at the Master API Key configured in the Dockerfile.
|
||||
export GALAXY_CONFIG_MASTER_API_KEY=94a548bea347a35e457a804bf75bec53
|
||||
|
||||
cd ../../..
|
||||
|
||||
./run_tests.sh --selenium "$@"
|
||||
exit_code=$?
|
||||
|
||||
cd $TEST_DIRECTORY
|
||||
|
||||
exit $exit_code
|
||||
@@ -1,19 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
echo "Waiting for postgres to become available"
|
||||
while ! nc -z postgres 5432;
|
||||
do
|
||||
sleep 1
|
||||
printf "."
|
||||
done
|
||||
|
||||
echo "Creating postgres database for Galaxy"
|
||||
createdb -w -U postgres -h postgres galaxy
|
||||
|
||||
echo "Starting and waiting for Galaxy daemon(s)"
|
||||
GALAXY_RUN_ALL=1 bash "$GALAXY_ROOT/run.sh" --daemon --wait
|
||||
|
||||
echo "Galaxy daemon ready, monitoring Galaxy logs"
|
||||
tail -f "$GALAXY_ROOT/main.log"
|
||||
Executable → Regular
+15
-111
@@ -1,119 +1,23 @@
|
||||
#!/bin/bash
|
||||
|
||||
TEST_DIRECTORY=`dirname $0`
|
||||
# Enable retries on tests to reduce chances of transient failures.
|
||||
: ${GALAXY_TEST_SELENIUM_RETRIES:=1}
|
||||
|
||||
DEFAULT_COMPOSE_PROJECT_NAME=`basename $TEST_DIRECTORY`
|
||||
COMPOSE_PROJECT_NAME=${COMPOSE_PROJECT_NAME:-$DEFAULT_COMPOSE_PROJECT_NAME}
|
||||
# If in Jenkins environment, append ${BUILD_NUMBER} to project so builds don't interfer.
|
||||
if [ ! -z "$BUILD_NUMBER" ];
|
||||
# If in Jenkins environment, use it for artifacts.
|
||||
if [ -n "$BUILD_NUMBER" ];
|
||||
then
|
||||
COMPOSE_PROJECT_NAME="${COMPOSE_PROJECT_NAME}${BUILD_NUMBER}"
|
||||
fi
|
||||
export COMPOSE_PROJECT_NAME
|
||||
|
||||
echo "Running Jenkins test from $TEST_DIRECTORY with compose project name $COMPOSE_PROJECT_NAME"
|
||||
|
||||
find lib -iname '*pyc' -exec rm -rf {} \;
|
||||
find test -iname '*pyc' -exec rm -rf {} \;
|
||||
|
||||
./scripts/common_startup.sh --dev-wheels
|
||||
|
||||
. .venv/bin/activate
|
||||
|
||||
pip install docker-compose
|
||||
|
||||
# TODO: Let docker-compose pick these at random.
|
||||
export GALAXY_PORT=`python -c 'import socket; s=socket.socket(); s.bind(("", 0)); print(s.getsockname()[1]); s.close()'`
|
||||
export SELENIUM_PORT=`python -c 'import socket; s=socket.socket(); s.bind(("", 0)); print(s.getsockname()[1]); s.close()'`
|
||||
|
||||
export TARGET_ROOT=`pwd`
|
||||
export TARGET_PATH=/galaxy
|
||||
|
||||
cd $TEST_DIRECTORY
|
||||
|
||||
echo "Cleaning up previous executions if needed."
|
||||
docker-compose down | true
|
||||
docker-compose build galaxy
|
||||
docker-compose up -d
|
||||
|
||||
function tear_down {
|
||||
docker-compose down
|
||||
}
|
||||
|
||||
trap tear_down EXIT
|
||||
|
||||
for service_name in postgres galaxy selenium
|
||||
do
|
||||
echo "Waiting on service ${service_name}"
|
||||
while true
|
||||
do
|
||||
if docker ps | grep -q "${COMPOSE_PROJECT_NAME}_${service_name}"
|
||||
then
|
||||
container_id=`docker ps | grep "${COMPOSE_PROJECT_NAME}_${service_name}" | cut -d " " -f 1`
|
||||
eval "${service_name}_container_id=${container_id}"
|
||||
echo "Service ${service_name} ready - with container ID ${container_id}"
|
||||
break
|
||||
fi
|
||||
printf "."
|
||||
sleep 1;
|
||||
done
|
||||
done
|
||||
|
||||
|
||||
if [ "$1" = "--debug-running-containers" ];
|
||||
then
|
||||
env
|
||||
exit 0
|
||||
: ${GALAXY_TEST_ERRORS_DIRECTORY:=${BUILD_NUMBER}-test-errors}
|
||||
: ${GALAXY_TEST_SCREENSHOTS_DIRECTORY:=${BUILD_NUMBER}-test-screenshots}
|
||||
else
|
||||
: ${GALAXY_TEST_ERRORS_DIRECTORY:=database/test-errors}
|
||||
: ${GALAXY_TEST_SCREENSHOTS_DIRECTORY:=database/test-screenshots}
|
||||
fi
|
||||
|
||||
export GALAXY_TEST_EXTERNAL="http://localhost:$GALAXY_PORT/"
|
||||
mkdir -p "$GALAXY_TEST_ERRORS_DIRECTORY"
|
||||
mkdir -p "$GALAXY_TEST_SCREENSHOTS_DIRECTORY"
|
||||
|
||||
echo "Waiting on docker-compose managed Galaxy server - $GALAXY_TEST_EXTERNAL."
|
||||
while ! curl -s "$GALAXY_TEST_EXTERNAL";
|
||||
do
|
||||
for service_name in postgres galaxy selenium
|
||||
do
|
||||
if ! docker ps | grep -q "${COMPOSE_PROJECT_NAME}_${service_name}"
|
||||
then
|
||||
echo "Service ${service_name} stopped before Galaxy came up, exiting and halting containers."
|
||||
for service_name in postgres galaxy selenium
|
||||
do
|
||||
container_id_var="${service_name}_container_id"
|
||||
container_id="${!container_id_var}"
|
||||
echo "Dumping logs for $service_name container (${container_id})..."
|
||||
echo "---"
|
||||
docker logs "${container_id}"
|
||||
echo "---"
|
||||
done
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
# Start Selenium server in the test Docker container.
|
||||
DOCKER_RUN_EXTRA_ARGS="-e USE_SELENIUM=1 -e GALAXY_TEST_SELENIUM_RETRIES=${GALAXY_TEST_SELENIUM_RETRIES} -e GALAXY_TEST_ERRORS_DIRECTORY=${GALAXY_TEST_ERRORS_DIRECTORY} -e GALAXY_TEST_SCREENSHOTS_DIRECTORY=${GALAXY_TEST_SCREENSHOTS_DIRECTORY} ${DOCKER_RUN_EXTRA_ARGS}"
|
||||
export DOCKER_RUN_EXTRA_ARGS
|
||||
|
||||
printf "."
|
||||
sleep 4;
|
||||
done;
|
||||
|
||||
# Access Selenium on localhost via port $SELENIUM_PORT
|
||||
export GALAXY_TEST_SELENIUM_REMOTE=1
|
||||
export GALAXY_TEST_SELENIUM_REMOTE_PORT="${SELENIUM_PORT}"
|
||||
|
||||
# Retry all failed Selenium tests a second time to deal
|
||||
# with transiently failing tests. Failure information for
|
||||
# first tests is still populated in database/test_errors
|
||||
# and available at the top of the Jenkins test report.
|
||||
export GALAXY_TEST_SELENIUM_RETRIES=1
|
||||
|
||||
# Access Galaxy on localhost via port $GALAXY_PORT
|
||||
export GALAXY_TEST_PORT="${GALAXY_PORT}"
|
||||
|
||||
# Have Selenium access Galaxy at this URL
|
||||
export GALAXY_TEST_EXTERNAL_FROM_SELENIUM="http://galaxy:8080"
|
||||
|
||||
cd ../../..
|
||||
|
||||
./run_tests.sh --selenium "$@"
|
||||
exit_code=$?
|
||||
|
||||
cd $TEST_DIRECTORY
|
||||
|
||||
exit $exit_code
|
||||
./run_tests.sh --dockerize --db postgres --external_tmp --clean_pyc --skip_flakey_fails --selenium "$@"
|
||||
|
||||
+27
-26
@@ -8,22 +8,7 @@ lib/galaxy/auth/
|
||||
lib/galaxy/config.py
|
||||
lib/galaxy/containers/
|
||||
lib/galaxy/dataset_collections/
|
||||
lib/galaxy/datatypes/assembly.py
|
||||
lib/galaxy/datatypes/binary.py
|
||||
lib/galaxy/datatypes/constructive_solid_geometry.py
|
||||
lib/galaxy/datatypes/converters/
|
||||
lib/galaxy/datatypes/dataproviders/
|
||||
lib/galaxy/datatypes/data.py
|
||||
lib/galaxy/datatypes/genetics.py
|
||||
lib/galaxy/datatypes/images.py
|
||||
lib/galaxy/datatypes/interval.py
|
||||
lib/galaxy/datatypes/msa.py
|
||||
lib/galaxy/datatypes/ngsindex.py
|
||||
lib/galaxy/datatypes/proteomics.py
|
||||
lib/galaxy/datatypes/sequence.py
|
||||
lib/galaxy/datatypes/sniff.py
|
||||
lib/galaxy/datatypes/tabular.py
|
||||
lib/galaxy/datatypes/tracks.py
|
||||
lib/galaxy/datatypes/
|
||||
lib/galaxy/dependencies/
|
||||
lib/galaxy/eggs/
|
||||
lib/galaxy/exceptions/
|
||||
@@ -31,12 +16,7 @@ lib/galaxy/external_services/
|
||||
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/model/
|
||||
lib/galaxy/objectstore/
|
||||
lib/galaxy/openid/
|
||||
lib/galaxy/quota/
|
||||
@@ -47,34 +27,47 @@ lib/galaxy/tools/
|
||||
lib/galaxy/tours/
|
||||
lib/galaxy/util/
|
||||
lib/galaxy/visualization/
|
||||
lib/galaxy/web/base/
|
||||
lib/galaxy/web/buildapp.py
|
||||
lib/galaxy/web/framework/base.py
|
||||
lib/galaxy/web/framework/decorators.py
|
||||
lib/galaxy/web/framework/helpers/grids.py
|
||||
lib/galaxy/web/framework/__init__.py
|
||||
lib/galaxy/web/framework/middleware/error.py
|
||||
lib/galaxy/web/framework/middleware/static.py
|
||||
lib/galaxy/web/framework/middleware/statsd.py
|
||||
lib/galaxy/web/framework/webapp.py
|
||||
lib/galaxy/web/__init__.py
|
||||
lib/galaxy/webapps/galaxy/api/histories.py
|
||||
lib/galaxy/webapps/galaxy/api/library_datasets.py
|
||||
lib/galaxy/webapps/galaxy/api/tours.py
|
||||
lib/galaxy/webapps/galaxy/api/users.py
|
||||
lib/galaxy/webapps/galaxy/api/workflows.py
|
||||
lib/galaxy/webapps/galaxy/buildapp.py
|
||||
lib/galaxy/webapps/galaxy/controllers/admin_toolshed.py
|
||||
lib/galaxy/webapps/galaxy/controllers/external_services.py
|
||||
lib/galaxy/webapps/galaxy/controllers/forms.py
|
||||
lib/galaxy/webapps/galaxy/controllers/history.py
|
||||
lib/galaxy/webapps/galaxy/controllers/library_common.py
|
||||
lib/galaxy/webapps/galaxy/controllers/search.py
|
||||
lib/galaxy/webapps/galaxy/controllers/userskeys.py
|
||||
lib/galaxy/webapps/reports/config.py
|
||||
lib/galaxy/webapps/reports/controllers/tools.py
|
||||
lib/galaxy/webapps/reports/__init__.py
|
||||
lib/galaxy/webapps/tool_shed/config.py
|
||||
lib/galaxy/webapps/tool_shed/controllers/groups.py
|
||||
lib/galaxy/webapps/tool_shed/controllers/user.py
|
||||
lib/galaxy/webapps/tool_shed/framework/middleware/remoteuser.py
|
||||
lib/galaxy/webapps/tool_shed/__init__.py
|
||||
lib/galaxy/webapps/tool_shed/model/__init__.py
|
||||
lib/galaxy/webapps/tool_shed/search/repo_search.py
|
||||
lib/galaxy/webapps/tool_shed/util/ratings_util.py
|
||||
lib/galaxy/webhooks/
|
||||
lib/galaxy/work/
|
||||
lib/galaxy/workflow/extract.py
|
||||
lib/galaxy/workflow/run.py
|
||||
lib/galaxy/workflow/schedulers/
|
||||
lib/galaxy/workflow/steps.py
|
||||
lib/galaxy/workflow/
|
||||
lib/galaxy_ext/
|
||||
lib/log_tempfile.py
|
||||
lib/mimeparse.py
|
||||
lib/psyco_full.py
|
||||
lib/tool_shed/
|
||||
scripts/api/
|
||||
@@ -85,9 +78,17 @@ scripts/check_eggs.py
|
||||
scripts/check_galaxy.py
|
||||
scripts/check_python.py
|
||||
scripts/cleanup_datasets/
|
||||
scripts/communication/
|
||||
scripts/data_libraries/build_whoosh_index.py
|
||||
scripts/db_shell.py
|
||||
scripts/drmaa_external_runner.py
|
||||
scripts/metagenomics/
|
||||
scripts/microbes/BeautifulSoup.py
|
||||
scripts/microbes/harvest_bacteria.py
|
||||
scripts/secret_decoder_ring.py
|
||||
scripts/tool_shed/api/common.py
|
||||
scripts/tool_shed/build_ts_whoosh_index.py
|
||||
scripts/tool_shed/deprecate_repositories_without_metadata.py
|
||||
test/
|
||||
tool_list.py
|
||||
tools/
|
||||
|
||||
+6
-2
@@ -17,6 +17,7 @@ database/files
|
||||
database/jobs_directory
|
||||
database/job_working_directory
|
||||
database/pbs
|
||||
database/test_errors
|
||||
database/tmp
|
||||
database/*.sqlite
|
||||
database/openid_consumer_cache
|
||||
@@ -93,7 +94,7 @@ tool-data/genome/*
|
||||
tool-data/*.sample
|
||||
tool-data/testtoolshed.g2.bx.psu.edu/
|
||||
tool-data/toolshed.g2.bx.psu.edu/
|
||||
tool-data/**/*.fa
|
||||
tool-data/**/*.fa
|
||||
|
||||
# Test output
|
||||
test-data-cache
|
||||
@@ -122,8 +123,11 @@ sprite-*.less
|
||||
|
||||
# JS, Local node_modules, and bower_components directories
|
||||
static/scripts/bundled/[0-9]*
|
||||
static/maps
|
||||
static/scripts/bundled/*.map
|
||||
node_modules
|
||||
bower_components
|
||||
client/galaxy/scripts/libs/*
|
||||
|
||||
# Documentation build files.
|
||||
doc/build
|
||||
@@ -135,4 +139,4 @@ doc/source/dev/schema.rst
|
||||
.DS_Store
|
||||
*.rej
|
||||
*~
|
||||
.idea/
|
||||
.idea
|
||||
|
||||
+2
-11
@@ -9,21 +9,13 @@ env:
|
||||
- TOX_ENV=py27-unit
|
||||
- TOX_ENV=qunit
|
||||
- TOX_ENV=first_startup
|
||||
- TOX_ENV=py27-lint-imports
|
||||
- TOX_ENV=py27-lint-imports-include-list
|
||||
- TOX_ENV=validate-test-tools
|
||||
- TOX_ENV=py27-lint-docstring-include-list
|
||||
- TOX_ENV=check-python-dependencies
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- os: osx
|
||||
env: TOX_ENV=first_startup
|
||||
language: generic
|
||||
- os: osx
|
||||
env: TOX_ENV=py27-unit
|
||||
language: generic
|
||||
allow_failures:
|
||||
- env: TOX_ENV=py27-lint-imports
|
||||
- env: TOX_ENV=check-python-dependencies
|
||||
|
||||
addons:
|
||||
apt:
|
||||
@@ -32,7 +24,6 @@ addons:
|
||||
|
||||
install:
|
||||
- pip install tox
|
||||
- if [ "$TOX_ENV" == "qunit" ]; then bash -c 'cd test/qunit && npm install'; fi
|
||||
- if [ "$TOX_ENV" == "first_startup" ]; then bash -c "bash scripts/common_startup.sh && wget -q https://github.com/jmchilton/galaxy-downloads/raw/master/db_gx_rev_0127.sqlite && mv db_gx_rev_0127.sqlite database/universe.sqlite && bash manage_db.sh -c ./config/galaxy.ini.sample upgrade"; fi
|
||||
|
||||
script: tox -e $TOX_ENV
|
||||
|
||||
@@ -1,54 +1,31 @@
|
||||
If you use or extend Galaxy in your published work, please cite each of the
|
||||
following publications:
|
||||
|
||||
- Goecks, J, Nekrutenko, A, Taylor, J and The Galaxy Team. "Galaxy: a
|
||||
comprehensive approach for supporting accessible, reproducible, and
|
||||
transparent computational research in the life sciences."
|
||||
Genome Biol. 2010 Aug 25;11(8):R86.
|
||||
|
||||
- Blankenberg D, Von Kuster G, Coraor N, Ananda G, Lazarus R, Mangan M,
|
||||
Nekrutenko A, Taylor J. "Galaxy: a web-based genome analysis tool for
|
||||
experimentalists". Current Protocols in Molecular Biology.
|
||||
2010 Jan; Chapter 19:Unit 19.10.1-21.
|
||||
|
||||
- Giardine B, Riemer C, Hardison RC, Burhans R, Elnitski L, Shah P, Zhang Y,
|
||||
Blankenberg D, Albert I, Taylor J, Miller W, Kent WJ, Nekrutenko A. "Galaxy:
|
||||
a platform for interactive large-scale genome analysis."
|
||||
Genome Research. 2005 Oct; 15(10):1451-5.
|
||||
|
||||
See also: https://galaxyproject.org/citing-galaxy
|
||||
|
||||
If you use or extend Galaxy in your published work, please cite this
|
||||
publication:
|
||||
|
||||
- Afgan E, Baker D, van den Beek M, Blankenberg D, Bouvier D, Čech M, Chilton
|
||||
J, Clements D, Coraor N, Eberhard C, Grüning B, Guerler A, Hillman-Jackson
|
||||
J, Von Kuster G, Rasche E, Soranzo N, Turaga N, Taylor J, Nekrutenko A, Goecks
|
||||
J. "The Galaxy platform for accessible, reproducible and collaborative
|
||||
biomedical analyses: 2016 update"
|
||||
Nucleic Acids Research (2016) 44(W1):W3-W10 doi:10.1093/nar/gkw343
|
||||
|
||||
BibTeX format:
|
||||
|
||||
@article{goecks2010galaxy,
|
||||
title={Galaxy: a comprehensive approach for supporting accessible, reproducible, and transparent computational research in the life sciences},
|
||||
author={Goecks, Jeremy and Nekrutenko, Anton and Taylor, James and The Galaxy Team},
|
||||
journal={Genome Biol},
|
||||
volume={11},
|
||||
number={8},
|
||||
pages={R86},
|
||||
year={2010}
|
||||
@article{afgan2016galaxy,
|
||||
title={The {Galaxy} platform for accessible, reproducible and collaborative biomedical analyses: 2016 update},
|
||||
author={Afgan, Enis and Baker, Dannon and van den Beek, Marius and Blankenberg, Daniel and Bouvier, Dave and \v{C}ech, Martin and Chilton, John and Clements, Dave and Coraor, Nate and Eberhard, Carl and Gr\"uning, Bj\"orn and Guerler, Aysam and Hillman-Jackson, Jennifer and Von Kuster, Greg and Rasche, Eric and Soranzo, Nicola and Turaga, Nitesh and Taylor, James and Nekrutenko, Anton and Goecks, Jeremy},
|
||||
journal={Nucleic Acids Res.},
|
||||
volume={44},
|
||||
number={W1},
|
||||
pages={W3-W10},
|
||||
year={2016},
|
||||
publisher={Oxford University Press}
|
||||
}
|
||||
|
||||
@article{blankenberg2010galaxy,
|
||||
title={Galaxy: A Web-Based Genome Analysis Tool for Experimentalists},
|
||||
author={Blankenberg, Daniel and Kuster, Gregory Von and Coraor, Nathaniel and Ananda, Guruprasad and Lazarus, Ross and Mangan, Mary and Nekrutenko, Anton and Taylor, James},
|
||||
journal={Current protocols in molecular biology},
|
||||
pages={19--10},
|
||||
year={2010},
|
||||
publisher={John Wiley \& Sons, Inc.}
|
||||
}
|
||||
|
||||
@article{giardine2005galaxy,
|
||||
title={Galaxy: a platform for interactive large-scale genome analysis},
|
||||
author={Giardine, Belinda and Riemer, Cathy and Hardison, Ross C and Burhans, Richard and Elnitski, Laura and Shah, Prachi and Zhang, Yi and Blankenberg, Daniel and Albert, Istvan and Taylor, James and Miller, Webb C and Kent, W James and Nekrutenko, Anton},
|
||||
journal={Genome research},
|
||||
volume={15},
|
||||
number={10},
|
||||
pages={1451--1455},
|
||||
year={2005},
|
||||
publisher={Cold Spring Harbor Lab}
|
||||
}
|
||||
More publications
|
||||
-----------------
|
||||
|
||||
To cite specific Galaxy components/features or other Galaxy project
|
||||
publications, see
|
||||
|
||||
https://galaxyproject.org/citing-galaxy
|
||||
|
||||
+2
-5
@@ -66,11 +66,8 @@ opened using [this form](https://github.com/galaxyproject/galaxy/issues/new).
|
||||
should be made against the recent `release_XX.XX` branch (`git checkout release_XX.XX`).
|
||||
|
||||
* Serious security problems should not be fixed via pull request - please
|
||||
responsibly disclose these by e-mailing them (with or without patches) to
|
||||
galaxy-committers@lists.galaxyproject.org . The Galaxy core development team will
|
||||
issue patches to public servers before announcing the issue to ensure there
|
||||
is time to patch and highlight these fixes widely. We will provide you
|
||||
credit for the discovery when publicly disclosing the issue.
|
||||
see [the Galaxy security policies](SECURITY_POLICY.md) for information
|
||||
about responsibly disclosing security issues.
|
||||
|
||||
* If your changes modify code - please ensure the resulting files
|
||||
conform to Galaxy [style
|
||||
|
||||
@@ -10,16 +10,14 @@ MY_UPSTREAM:=origin
|
||||
VENV?=.venv
|
||||
# Source virtualenv to execute command (flake8, sphinx, twine, etc...)
|
||||
IN_VENV=if [ -f $(VENV)/bin/activate ]; then . $(VENV)/bin/activate; fi;
|
||||
CONFIG_MANAGE=$(IN_VENV) python lib/galaxy/webapps/config_manage.py
|
||||
PROJECT_URL?=https://github.com/galaxyproject/galaxy
|
||||
GRUNT_DOCKER_NAME:=galaxy/client-builder:16.01
|
||||
GRUNT_EXEC?=node_modules/grunt-cli/bin/grunt
|
||||
WEBPACK_EXEC?=node_modules/webpack/bin/webpack.js
|
||||
GXY_NODE_MODULES=client/node_modules
|
||||
DOCS_DIR=doc
|
||||
DOC_SOURCE_DIR=$(DOCS_DIR)/source
|
||||
SLIDESHOW_DIR=$(DOC_SOURCE_DIR)/slideshow
|
||||
OPEN_RESOURCE=bash -c 'open $$0 || xdg-open $$0'
|
||||
SLIDESHOW_TO_PDF?=bash -c 'docker run --rm -v `pwd`:/cwd astefanutti/decktape /cwd/$$0 /cwd/`dirname $$0`/`basename -s .html $$0`.pdf'
|
||||
CLIENT_COMMIT_WARNING="Please remember to 'make client-production' when finished developing, before a commit!"
|
||||
|
||||
all: help
|
||||
@echo "This makefile is used for building Galaxy's JS client, documentation, and drive the release process. A sensible all target is not implemented."
|
||||
@@ -33,6 +31,12 @@ docs: ## Generate HTML documentation.
|
||||
$(IN_VENV) $(MAKE) -C doc clean
|
||||
$(IN_VENV) $(MAKE) -C doc html
|
||||
|
||||
setup-venv:
|
||||
if [ ! -f $(VENV)/bin/activate ]; then bash scripts/common_startup.sh --dev-wheels; fi
|
||||
|
||||
list-dependency-updates: setup-venv
|
||||
$(IN_VENV) pip list --outdated --format=columns
|
||||
|
||||
docs-slides-ready:
|
||||
test -f plantuml.jar || wget http://jaist.dl.sourceforge.net/project/plantuml/plantuml.jar
|
||||
java -jar plantuml.jar -c $(DOC_SOURCE_DIR)/slideshow/architecture/images/plantuml_options.txt -tsvg $(SLIDESHOW_DIR)/architecture/images/ *.plantuml.txt
|
||||
@@ -52,6 +56,42 @@ open-project: ## open project on github
|
||||
lint: ## check style using tox and flake8 for Python 2 and Python 3
|
||||
$(IN_VENV) tox -e py27-lint && tox -e py34-lint
|
||||
|
||||
uwsgi-rebuild-validation: ## rebuild uwsgi_config.yml kwalify schema against latest uwsgi master.
|
||||
$(CONFIG_MANAGE) build_uwsgi_yaml
|
||||
|
||||
tool-shed-config-validate: ## validate tool shed YAML configuration file
|
||||
$(CONFIG_MANAGE) validate tool_shed
|
||||
|
||||
tool-shed-config-lint: ## lint tool shed YAML configuration file
|
||||
$(CONFIG_MANAGE) lint tool_shed
|
||||
|
||||
tool-shed-config-convert-dry-run: ## convert old style tool shed ini to yaml (dry run)
|
||||
$(CONFIG_MANAGE) convert tool_shed --dry-run
|
||||
|
||||
tool-shed-config-convert: ## convert old style tool shed ini to yaml
|
||||
$(CONFIG_MANAGE) convert tool_shed
|
||||
|
||||
tool-shed-config-rebuild-sample: ## Rebuild sample tool shed yaml file from schema
|
||||
$(CONFIG_MANAGE) build_sample_yaml tool_shed --add-comments
|
||||
|
||||
reports-config-validate: ## validate reports YAML configuration file
|
||||
$(CONFIG_MANAGE) validate reports
|
||||
|
||||
reports-config-convert-dry-run: ## convert old style reports ini to yaml (dry run)
|
||||
$(CONFIG_MANAGE) convert reports --dry-run
|
||||
|
||||
reports-config-convert: ## convert old style reports ini to yaml
|
||||
$(CONFIG_MANAGE) convert reports
|
||||
|
||||
reports-config-rebuild-sample: ## Rebuild sample reports yaml file from schema
|
||||
$(CONFIG_MANAGE) build_sample_yaml reports --add-comments
|
||||
|
||||
reports-config-lint: ## lint reports YAML configuration file
|
||||
$(CONFIG_MANAGE) lint reports
|
||||
|
||||
reports-config-rebuild-rst: ## Rebuild sample reports RST docs
|
||||
$(CONFIG_MANAGE) build_rst reports > doc/source/admin/reports_options.rst
|
||||
|
||||
release-ensure-upstream: ## Ensure upstream branch for release commands setup
|
||||
ifeq (shell git remote -v | grep $(RELEASE_UPSTREAM), )
|
||||
git remote add $(RELEASE_UPSTREAM) git@github.com:galaxyproject/galaxy.git
|
||||
@@ -80,43 +120,40 @@ release-check-blocking-prs: ## Check github for release blocking PRs
|
||||
release-bootstrap-history: ## bootstrap history for a new release
|
||||
$(IN_VENV) python scripts/bootstrap_history.py --release $(RELEASE_CURR)
|
||||
|
||||
npm-deps: ## Install NodeJS dependencies.
|
||||
cd client && npm install
|
||||
update-dependencies: ## update linting + dev dependencies
|
||||
sh lib/galaxy/dependencies/pipfiles/update.sh
|
||||
|
||||
grunt: npm-deps ## Calls out to Grunt to build client
|
||||
cd client && $(GRUNT_EXEC)
|
||||
update-and-commit-dependencies: ## update and commit linting + dev dependencies
|
||||
sh lib/galaxy/dependencies/pipfiles/update.sh -c
|
||||
|
||||
style: npm-deps ## Calls the style task of Grunt
|
||||
cd client && $(GRUNT_EXEC) style
|
||||
node-deps: ## Install NodeJS dependencies.
|
||||
cd client && yarn install --check-files
|
||||
|
||||
client-install-libs: npm-deps ## Fetch updated client dependencies using bower.
|
||||
cd client && $(GRUNT_EXEC) install-libs
|
||||
client: node-deps ## Rebuild client-side artifacts for local development.
|
||||
cd client && yarn run build
|
||||
@echo $(CLIENT_COMMIT_WARNING)
|
||||
|
||||
client: grunt style ## Rebuild all client-side artifacts
|
||||
client-production: node-deps ## Rebuild client-side artifacts for a production deployment (or committing to the repository).
|
||||
cd client && yarn run build-production
|
||||
|
||||
charts: npm-deps ## Rebuild charts
|
||||
NODE_PATH=$(GXY_NODE_MODULES) client/$(WEBPACK_EXEC) -p --config config/plugins/visualizations/charts/webpack.config.js
|
||||
client-production-maps: node-deps ## Rebuild client-side artifacts for a production deployment, and include sourcemaps to aid in debugging efforts.
|
||||
cd client && yarn run build-production-maps
|
||||
|
||||
grunt-docker-image: ## Build docker image for running grunt
|
||||
docker build -t ${GRUNT_DOCKER_NAME} client
|
||||
client-format: node-deps ## Reformat client code
|
||||
cd client && yarn run prettier
|
||||
|
||||
grunt-docker: grunt-docker-image ## Run grunt inside docker
|
||||
docker run -it -v `pwd`:/data ${GRUNT_DOCKER_NAME}
|
||||
client-watch: node-deps ## A useful target for parallel development building.
|
||||
cd client && yarn run watch
|
||||
@echo $(CLIENT_COMMIT_WARNING)
|
||||
|
||||
clean-grunt-docker-image: ## Remove grunt docker image
|
||||
docker rmi ${GRUNT_DOCKER_NAME}
|
||||
client-test: client ## Run qunit tests via Karma
|
||||
cd client && yarn run test
|
||||
|
||||
grunt-watch-style: npm-deps ## Execute watching style builder for dev purposes
|
||||
cd client && $(GRUNT_EXEC) watch-style
|
||||
client-test-watch: client ## Watch and run qunit tests on changes via Karma
|
||||
cd client && yarn run test-watch
|
||||
|
||||
grunt-watch-develop: npm-deps ## Execute watching grunt builder for dev purposes (unpacked, allows debugger statements)
|
||||
cd client && $(GRUNT_EXEC) watch --develop
|
||||
|
||||
webpack-watch: npm-deps ## Execute watching webpack for dev purposes
|
||||
cd client && ./node_modules/webpack/bin/webpack.js --watch
|
||||
|
||||
client-develop: grunt-watch-style grunt-watch-develop webpack-watch ## A useful target for parallel development building.
|
||||
@echo "Remember to rerun `make client` before committing!"
|
||||
charts: node-deps ## Rebuild charts
|
||||
cd client && yarn run build-charts
|
||||
|
||||
|
||||
# Release Targets
|
||||
|
||||
+11
-11
@@ -10,7 +10,7 @@ The latest information about Galaxy is available via `https://galaxyproject.org/
|
||||
.. image:: https://img.shields.io/badge/chat-irc.freenode.net%23galaxyproject-blue.svg
|
||||
:target: https://webchat.freenode.net/?channels=galaxyproject
|
||||
:alt: Chat on irc
|
||||
|
||||
|
||||
.. image:: https://img.shields.io/badge/chat-gitter-blue.svg
|
||||
:target: https://gitter.im/galaxyproject/Lobby
|
||||
:alt: Chat on gitter
|
||||
@@ -44,17 +44,17 @@ browser at:
|
||||
|
||||
http://localhost:8080
|
||||
|
||||
Configuration & Tools
|
||||
=====================
|
||||
|
||||
You may wish to make changes from the default configuration. This can be
|
||||
done in the ``config/galaxy.ini`` file. Tools can be either installed
|
||||
from the Tool Shed or added manually. For details please see the Galaxy
|
||||
wiki:
|
||||
done in the ``config/galaxy.ini`` file.
|
||||
|
||||
https://galaxyproject.org/admin/tools/add-tool-from-toolshed-tutorial/
|
||||
|
||||
Not all dependencies are included for the tools provided in the sample
|
||||
``tool_conf.xml``. A full list of external dependencies is available at:
|
||||
|
||||
https://wiki.galaxyproject.org/Admin/Tools/ToolDependencies
|
||||
Tools can be either installed from the Tool Shed or added manually.
|
||||
For details please see the `tutorial <https://galaxyproject.org/admin/tools/add-tool-from-toolshed-tutorial/>`__.
|
||||
Note that not all dependencies for the tools provided in the
|
||||
``tool_conf.xml.sample`` are included. To install them please visit
|
||||
"Manage dependencies" in the admin interface.
|
||||
|
||||
Issues and Galaxy Development
|
||||
=============================
|
||||
@@ -64,4 +64,4 @@ Please see `CONTRIBUTING.md <CONTRIBUTING.md>`_ .
|
||||
Roadmap
|
||||
=============================
|
||||
|
||||
Interested in the next steps for Galaxy? Take a look here: https://github.com/galaxyproject/galaxy/issues/1928
|
||||
Interested in the next steps for Galaxy? Take a look at the `roadmap <https://github.com/galaxyproject/galaxy/issues/1928>`__.
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
# Security
|
||||
|
||||
The Galaxy project is strongly committed to security and responsible disclosure. We have adopted and published a set of policies specifying how we will act in response to reported security issues, in order to ensure timely updates are made available to all affected parties.
|
||||
|
||||
## Reporting Security Issues
|
||||
|
||||
If you believe you have discovered a security issue, please email [galaxy-committers@lists.galaxyproject.org](galaxy-committers@lists.galaxyproject.org). Please use `[SECURITY]` in the email title. Someone on that list will acknowledge your email within 2 US business days. We ask that you not disclose the issues on the public issue tracker. We will provide you credit for the discovery when publicly disclosing the issue.
|
||||
|
||||
Security issues which *only* affect a pre-release version of Galaxy (i.e. the `dev` branch in GitHub) do not need to go through this process, so you may open issues and pull requests publicly.
|
||||
|
||||
## Supported versions
|
||||
|
||||
The following branches or releases receive security support:
|
||||
|
||||
- Development on the `dev` branch, hosted on GitHub, which will become the next release of Galaxy
|
||||
- Releases within the past 12 months.
|
||||
- E.g. 16.04 will receive support until 2017-04. As the month changes to 2017-05 it will become unsupported.
|
||||
- There are currently no plans for Long Term Support (LTS) releases.
|
||||
|
||||
For unsupported branches:
|
||||
|
||||
- Older versions of Galaxy may be affected by security issues.
|
||||
- Security patches *may* apply
|
||||
- The security team does not commit to investigating issues that pertain to unsupported releases.
|
||||
- The security team does not commit to issuing patches or new releases of unsupported versions.
|
||||
|
||||
## Issue Severity
|
||||
|
||||
Galaxy takes a very conservative stance on issue severity as individual Galaxy instances often install tools and make customizations that might increase their risk in the face of otherwise less-serious vulnerabilities. As a result, issues that would be considered less-severe in other projects may be treated as higher risk here.
|
||||
|
||||
### Issue Classification
|
||||
|
||||
Severity | Examples
|
||||
------------ | ---------
|
||||
High | Remote code execution (RCE), SQL Injection, Cross-site scripting (XSS), and *any issue allowing user impersonation*.
|
||||
Medium / Low | Unvalidated redirects/forwards, Issues due to uncommon configuration options.
|
||||
|
||||
These are only examples. The security team will provide a severity classification based on its impact on the average Galaxy instance. However, Galaxy administrators should take it upon themselves to evaluate the impact for their instance(s).
|
||||
|
||||
## Notification of Vulnerabilities
|
||||
|
||||
For high severity issues, we will notify [the list of public Galaxy owners](https://lists.galaxyproject.org/listinfo/galaxy-public-servers) with:
|
||||
|
||||
- A description of the issue
|
||||
- List of supported versions that are affected
|
||||
- Steps to update or patch your Galaxy
|
||||
|
||||
The issue will then be embargoed for three (3) days. For medium and low
|
||||
severity issues, or for publicly announcing high severity issues after the
|
||||
embargo, we will:
|
||||
|
||||
- Patch the oldest release within the 12 month support window, and merge that fix forward.
|
||||
- Updates will be available on the `release_XX.YY` branches.
|
||||
- Update each release branch
|
||||
- Post a notice to the [galaxy-announce mailing list](https://lists.galaxyproject.org/listinfo/galaxy-announce) with:
|
||||
- A description of the issue
|
||||
- List of supported versions that are affected
|
||||
- Steps to update or patch your Galaxy
|
||||
|
||||
If an issue is deemed to be time-sensitive – e.g. due to active and ongoing exploits in the wild – the embargo may be shortened considerably.
|
||||
|
||||
If we believe that the reported issue affects other Galaxy Project components or projects outside of the Galaxy ecosystem, we may discuss the issue with those projects and coordinate disclosure and resolution with them.
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"presets": ["env"],
|
||||
"moduleIds": true,
|
||||
"ignore": [
|
||||
"i18n.js",
|
||||
"utils/localization.js",
|
||||
"nls/*"
|
||||
]
|
||||
}
|
||||
+1
-3
@@ -20,9 +20,7 @@ module.exports = function(grunt) {
|
||||
grunt.config.set( 'paths', TOOLSHED_PATHS );
|
||||
}
|
||||
|
||||
grunt.loadNpmTasks('grunt-check-modules');
|
||||
// see the sub directory grunt-tasks/ for individual task definitions
|
||||
grunt.loadTasks( 'grunt-tasks' );
|
||||
// note: 'handlebars' *not* 'templates' since handlebars doesn't call uglify
|
||||
grunt.registerTask( 'default', [ 'check-modules', 'uglify', 'webpack' ] );
|
||||
grunt.registerTask( 'default', [ 'uglify' ] );
|
||||
};
|
||||
|
||||
+46
-91
@@ -1,123 +1,78 @@
|
||||
Client Build System
|
||||
===================
|
||||
|
||||
Builds and moves the client-side scripts necessary for running the Galaxy webapps. There's no need to use this system
|
||||
unless you are modifying or developing client-side scripts.
|
||||
Installs, stages, and builds the client-side scripts necessary for running the
|
||||
Galaxy webapp. There's no need to use this system unless you are modifying or
|
||||
developing client-side scripts.
|
||||
|
||||
The base dependencies you'll need are Node.js and the Node Package Manager
|
||||
(npm). See nodejs.org for more information.
|
||||
The base dependencies you will need are Node.js and Yarn. On OSX the easiest
|
||||
way to get set up is using homebrew and the command `brew install nodejs yarn`.
|
||||
More information including instructions for other platforms is available at
|
||||
nodejs.org and yarnpkg.com.
|
||||
|
||||
|
||||
Simple Full Build
|
||||
=================
|
||||
Complete Client Build
|
||||
================================================
|
||||
|
||||
The simplest way to rebuild the entire client to incorporate any local changes
|
||||
is to run the 'client' rule in the Galaxy makefile, which is in the repository
|
||||
root. This will also ensure any local node modules are installed.
|
||||
There are many moving parts to the client build system, but the entrypoint for
|
||||
most people is the 'client' rule in the Makefile at the root of the Galaxy
|
||||
repository. Execute the following to perform a complete build suitable for
|
||||
local development, including dependency staging, style building, script
|
||||
processing and bundling.
|
||||
|
||||
make client
|
||||
|
||||
Note: Don't commit the files that 'make client' creates. This is a
|
||||
development-specific build which includes extra debugging features, and
|
||||
excludes several production optimizations made during the build process.
|
||||
|
||||
Detailed Build Instructions
|
||||
===========================
|
||||
For a production build, suitable for committing to the repository, or deploying
|
||||
to a live server, use the following:
|
||||
|
||||
Once npm is installed, install the grunt task manager and its command line into your global scope:
|
||||
make client-production
|
||||
|
||||
npm install -g grunt grunt-cli
|
||||
And, lastly, if you want a production build that includes sourcemaps to allow
|
||||
for perusal of the page-level javascript to facilitate live-server debugging,
|
||||
use:
|
||||
|
||||
Next, from within this directory, install the local build dependencies:
|
||||
|
||||
cd client
|
||||
npm install
|
||||
|
||||
You're now ready to re-build the client scripts after modifying them.
|
||||
make client-production-maps
|
||||
|
||||
|
||||
Rebuilding
|
||||
==========
|
||||
Automatic Rebuilding (Watch Mode)
|
||||
=================================
|
||||
|
||||
There are two methods for rebuilding: a complete rebuild and automatic, partial rebuilds while you develop.
|
||||
When you're actively developing, it is sometimes convenient to have the client
|
||||
automatically rebuild every time you save a file. You can do this using:
|
||||
|
||||
A complete rebuild can be done with the following (from the `client` directory):
|
||||
make client-watch
|
||||
|
||||
grunt
|
||||
|
||||
This will:
|
||||
|
||||
1. compress the files in client/galaxy/scripts and place them in static/scripts
|
||||
2. generate source maps and place them in static/maps
|
||||
3. rebuild the webpack-based client apps
|
||||
|
||||
|
||||
Rebuilding Scripts Only
|
||||
=======================
|
||||
|
||||
To re-minify all the individual javascript files:
|
||||
|
||||
grunt scripts
|
||||
|
||||
|
||||
Rebuilding Webpack Apps
|
||||
=======================
|
||||
|
||||
To rebuild the webpack bundles for apps (compressed for production):
|
||||
|
||||
grunt webpack
|
||||
|
||||
To rebuild the apps without compression:
|
||||
|
||||
grunt webpack-dev
|
||||
|
||||
To rebuild without compression and watch and rebuild when scripts change:
|
||||
|
||||
grunt webpack-watch
|
||||
This will first stage any dependencies (yarn-installed packages like jquery,
|
||||
etc), and then will watch for changes in any of the galaxy client source files.
|
||||
When a file is changed, the client will automatically rebuild, after which you
|
||||
can usually force refresh your browser to see changes. Note that it is still
|
||||
recommended to run 'make client' after you are finished actively developing
|
||||
using 'make client-watch'.
|
||||
|
||||
|
||||
Changing Styles/CSS
|
||||
===================
|
||||
|
||||
The CSS and styling used by Galaxy is also controlled from this directory. Galaxy uses LESS, a superset of CSS that
|
||||
compiles to CSS, for its styling. LESS files are kept in client/galaxy/style/less. Compiled CSS is in static/style/blue.
|
||||
The CSS and styling used by Galaxy is also controlled from this directory.
|
||||
Galaxy uses LESS, a superset of CSS that compiles to CSS, for its styling. LESS
|
||||
files are kept in client/galaxy/style/less. Compiled CSS is in
|
||||
static/style/blue.
|
||||
|
||||
Use grunt to recompile the LESS in into CSS (from the `client` directory):
|
||||
As mentioned above, 'make client' will also rebuild styles. If you *only* want
|
||||
to run the style task, use the following command from the `client` directory:
|
||||
|
||||
grunt style
|
||||
|
||||
|
||||
Grunt watch
|
||||
===========
|
||||
|
||||
Grunt can also do an automatic, partial rebuild of any files you change *as you develop* by:
|
||||
|
||||
1. opening a new terminal session
|
||||
2. `cd client`
|
||||
3. Watch with:
|
||||
1. `grunt watch` to watch the *scripts/* folder
|
||||
2. `grunt watch-style` to watch the *style/* folder
|
||||
|
||||
This starts a new grunt watch process that will monitor the files, in the corresponding folder, for changes and copy and
|
||||
rebuild them when they change.
|
||||
|
||||
You can stop the watch task by pressing `Ctrl+C`. Note: you should also be able to background that task
|
||||
if you prefer.
|
||||
|
||||
|
||||
Using a Locally Installed Version of Grunt
|
||||
==========================================
|
||||
|
||||
A non-global version of grunt and the grunt-cli are installed when using 'npm install'. If you'd rather build with that
|
||||
version, you'll need to use the full, local path when calling it:
|
||||
|
||||
./node_modules/.bin/grunt
|
||||
# or
|
||||
./node_modules/.bin/grunt watch
|
||||
yarn run style
|
||||
|
||||
|
||||
The Toolshed Client Build
|
||||
=========================
|
||||
|
||||
The commands mentioned above in 'Rebuilding' and 'Grunt watch' also can be applied to toolshed scripts by using the
|
||||
`--app=toolshed` option:
|
||||
The toolshed client is not tightly integrated with the rest of the build
|
||||
system. To build the toolshed client, execute the following command from the
|
||||
`client` directory.
|
||||
|
||||
grunt watch --app=toolshed
|
||||
grunt --app=toolshed
|
||||
yarn run build-toolshed
|
||||
|
||||
+1
-16
@@ -8,33 +8,18 @@
|
||||
],
|
||||
"homepage": "usegalaxy.org",
|
||||
"dependencies": {
|
||||
"jquery": "~1.12",
|
||||
"ravenjs": "~3",
|
||||
"underscore": "~1",
|
||||
"backbone": "~1.3",
|
||||
"bootstrap": "~3.3.2",
|
||||
"bootstrap-tour": "~0.10.2",
|
||||
"d3": "~3",
|
||||
"farbtastic": "~2.0.0-alpha.1",
|
||||
"toastr": "~2.1.0",
|
||||
"jQTouch": "git://github.com/senchalabs/jQTouch#~1.0.0",
|
||||
"bib2json": "git://github.com/galaxyproject/bib2json#galaxy",
|
||||
"jquery-form": "~3.46.0",
|
||||
"jquery-autocomplete": "git://github.com/dyve/jquery-autocomplete",
|
||||
"select2": "~3.5.2",
|
||||
"jStorage": "~0.4.12",
|
||||
"jquery.cookie": "~1.4.1",
|
||||
"dynatree": "~1.2.5",
|
||||
"jquery-mousewheel": "~3.1.12",
|
||||
"wymeditor": "~1.0.0-rc.1",
|
||||
"jstree": "~3.0.9",
|
||||
"jquery-ui": "git://github.com/jquery/jquery-ui.git#~1.11.2",
|
||||
"jquery.complexify": "git://github.com/danpalmer/jquery.complexify.js.git#~0.5.1",
|
||||
"threedubmedia.jquery.event": "*",
|
||||
"jquery-migrate": "~1.4",
|
||||
"requirejs": "~2"
|
||||
},
|
||||
"resolutions": {
|
||||
"jquery": "~1.12"
|
||||
"threedubmedia.jquery.event": "*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,128 +1,134 @@
|
||||
define(["mvc/toolshed/shed-list-view",
|
||||
"mvc/toolshed/categories-view",
|
||||
"mvc/toolshed/repositories-view",
|
||||
"mvc/toolshed/repository-view",
|
||||
"mvc/toolshed/repository-queue-view",
|
||||
"mvc/toolshed/repo-status-view",
|
||||
"mvc/toolshed/workflows-view"],
|
||||
function(mod_shed_list_view,
|
||||
mod_categories_view,
|
||||
mod_repositories_view,
|
||||
mod_repository_view,
|
||||
mod_repoqueue_view,
|
||||
mod_repo_status_view,
|
||||
mod_workflows_view) {
|
||||
import mod_shed_list_view from "mvc/toolshed/shed-list-view";
|
||||
import mod_categories_view from "mvc/toolshed/categories-view";
|
||||
import mod_repositories_view from "mvc/toolshed/repositories-view";
|
||||
import mod_repository_view from "mvc/toolshed/repository-view";
|
||||
import mod_repoqueue_view from "mvc/toolshed/repository-queue-view";
|
||||
import mod_repo_status_view from "mvc/toolshed/repo-status-view";
|
||||
import mod_workflows_view from "mvc/toolshed/workflows-view";
|
||||
var AdminToolshedRouter = Backbone.Router.extend({
|
||||
initialize: function() {
|
||||
this.routesHit = 0;
|
||||
// keep count of number of routes handled by the application
|
||||
Backbone.history.on(
|
||||
"route",
|
||||
function() {
|
||||
this.routesHit++;
|
||||
},
|
||||
this
|
||||
);
|
||||
this.bind("route", this.trackPageview);
|
||||
},
|
||||
|
||||
var AdminToolshedRouter = Backbone.Router.extend({
|
||||
routes: {
|
||||
"": "toolsheds",
|
||||
sheds: "toolsheds",
|
||||
queue: "queue",
|
||||
workflows: "workflows",
|
||||
"status/r/:repositories": "status",
|
||||
status: "status",
|
||||
"categories/s/:tool_shed": "categories",
|
||||
"category/s/:tool_shed/c/:cateory_id": "repositories",
|
||||
"repository/s/:tool_shed/r/:repository_id": "repository"
|
||||
},
|
||||
|
||||
initialize: function() {
|
||||
this.routesHit = 0;
|
||||
// keep count of number of routes handled by the application
|
||||
Backbone.history.on( 'route', function() { this.routesHit++; }, this );
|
||||
this.bind( 'route', this.trackPageview );
|
||||
},
|
||||
|
||||
routes: {
|
||||
"" : "toolsheds",
|
||||
"sheds" : "toolsheds",
|
||||
"queue" : "queue",
|
||||
"workflows" : "workflows",
|
||||
"status/r/:repositories" : "status",
|
||||
"status" : "status",
|
||||
"categories/s/:tool_shed" : "categories",
|
||||
"category/s/:tool_shed/c/:cateory_id" : "repositories",
|
||||
"repository/s/:tool_shed/r/:repository_id" : "repository",
|
||||
},
|
||||
|
||||
/**
|
||||
* If more than one route has been hit the user did not land on current
|
||||
* page directly so we can go back safely. Otherwise go to the home page.
|
||||
* Use replaceState if available so the navigation doesn't create an
|
||||
* extra history entry
|
||||
*/
|
||||
back: function() {
|
||||
if( this.routesHit > 1 ) {
|
||||
window.history.back();
|
||||
} else {
|
||||
this.navigate( '#', { trigger:true, replace:true } );
|
||||
}
|
||||
/**
|
||||
* If more than one route has been hit the user did not land on current
|
||||
* page directly so we can go back safely. Otherwise go to the home page.
|
||||
* Use replaceState if available so the navigation doesn't create an
|
||||
* extra history entry
|
||||
*/
|
||||
back: function() {
|
||||
if (this.routesHit > 1) {
|
||||
window.history.back();
|
||||
} else {
|
||||
this.navigate("#", { trigger: true, replace: true });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
var GalaxyAdminToolshedApp = Backbone.View.extend({
|
||||
app_config: {
|
||||
known_views: ["toolsheds", "queue", "status", "categories", "repositories", "repoository"]
|
||||
},
|
||||
|
||||
var GalaxyAdminToolshedApp = Backbone.View.extend({
|
||||
initialize: function() {
|
||||
Galaxy.admintoolshedapp = this;
|
||||
this.admin_toolshed_router = new AdminToolshedRouter();
|
||||
|
||||
app_config: {
|
||||
known_views: ['toolsheds', 'queue', 'status', 'categories', 'repositories', 'repoository']
|
||||
},
|
||||
this.admin_toolshed_router.on("route:queue", () => {
|
||||
if (Galaxy.admintoolshedapp.adminRepoQueueView) {
|
||||
Galaxy.admintoolshedapp.adminRepoQueueView.reDraw();
|
||||
} else {
|
||||
Galaxy.admintoolshedapp.adminRepoQueueView = new mod_repoqueue_view.RepoQueueView();
|
||||
}
|
||||
});
|
||||
this.admin_toolshed_router.on("route:toolsheds", () => {
|
||||
if (Galaxy.admintoolshedapp.adminShedListView) {
|
||||
Galaxy.admintoolshedapp.adminShedListView.reDraw();
|
||||
} else {
|
||||
Galaxy.admintoolshedapp.adminShedListView = new mod_shed_list_view.ShedListView();
|
||||
}
|
||||
});
|
||||
this.admin_toolshed_router.on("route:categories", tool_shed => {
|
||||
if (Galaxy.admintoolshedapp.adminShedCategoriesView) {
|
||||
Galaxy.admintoolshedapp.adminShedCategoriesView.reDraw({
|
||||
tool_shed: tool_shed.replace(/\//g, "%2f")
|
||||
});
|
||||
} else {
|
||||
Galaxy.admintoolshedapp.adminShedCategoriesView = new mod_categories_view.CategoryView({
|
||||
tool_shed: tool_shed.replace(/\//g, "%2f")
|
||||
});
|
||||
}
|
||||
});
|
||||
this.admin_toolshed_router.on("route:repositories", (tool_shed, category_id) => {
|
||||
if (Galaxy.admintoolshedapp.adminShedCategoryView) {
|
||||
Galaxy.admintoolshedapp.adminShedCategoryView.reDraw({
|
||||
tool_shed: tool_shed.replace(/\//g, "%2f"),
|
||||
category_id: category_id
|
||||
});
|
||||
} else {
|
||||
Galaxy.admintoolshedapp.adminShedCategoryView = new mod_repositories_view.Category({
|
||||
tool_shed: tool_shed.replace(/\//g, "%2f"),
|
||||
category_id: category_id
|
||||
});
|
||||
}
|
||||
});
|
||||
this.admin_toolshed_router.on("route:repository", (tool_shed, repository_id) => {
|
||||
if (Galaxy.admintoolshedapp.adminRepositoryView) {
|
||||
Galaxy.admintoolshedapp.adminRepositoryView.reDraw({
|
||||
tool_shed: tool_shed.replace(/\//g, "%2f"),
|
||||
repository_id: repository_id
|
||||
});
|
||||
} else {
|
||||
Galaxy.admintoolshedapp.adminRepositoryView = new mod_repository_view.RepoDetails({
|
||||
tool_shed: tool_shed.replace(/\//g, "%2f"),
|
||||
repository_id: repository_id
|
||||
});
|
||||
}
|
||||
});
|
||||
this.admin_toolshed_router.on("route:status", repositories => {
|
||||
if (Galaxy.admintoolshedapp.adminRepoStatusView) {
|
||||
Galaxy.admintoolshedapp.adminRepoStatusView.reDraw({
|
||||
repositories: repositories.split("|")
|
||||
});
|
||||
} else {
|
||||
Galaxy.admintoolshedapp.adminRepoStatusView = new mod_repo_status_view.RepoStatus({
|
||||
repositories: repositories.split("|")
|
||||
});
|
||||
}
|
||||
});
|
||||
this.admin_toolshed_router.on("route:workflows", () => {
|
||||
if (Galaxy.admintoolshedapp.adminWorkflowsView) {
|
||||
Galaxy.admintoolshedapp.adminWorkflowsView.reDraw();
|
||||
} else {
|
||||
Galaxy.admintoolshedapp.adminWorkflowsView = new mod_workflows_view.Workflows();
|
||||
}
|
||||
});
|
||||
|
||||
initialize: function() {
|
||||
Galaxy.admintoolshedapp = this;
|
||||
this.admin_toolshed_router = new AdminToolshedRouter();
|
||||
Backbone.history.start({ pushState: false });
|
||||
}
|
||||
});
|
||||
|
||||
this.admin_toolshed_router.on('route:queue', function() {
|
||||
if (Galaxy.admintoolshedapp.adminRepoQueueView) {
|
||||
Galaxy.admintoolshedapp.adminRepoQueueView.reDraw();
|
||||
}
|
||||
else {
|
||||
Galaxy.admintoolshedapp.adminRepoQueueView = new mod_repoqueue_view.RepoQueueView();
|
||||
}
|
||||
});
|
||||
this.admin_toolshed_router.on('route:toolsheds', function() {
|
||||
if (Galaxy.admintoolshedapp.adminShedListView) {
|
||||
Galaxy.admintoolshedapp.adminShedListView.reDraw();
|
||||
}
|
||||
else {
|
||||
Galaxy.admintoolshedapp.adminShedListView = new mod_shed_list_view.ShedListView();
|
||||
}
|
||||
});
|
||||
this.admin_toolshed_router.on('route:categories', function(tool_shed) {
|
||||
if (Galaxy.admintoolshedapp.adminShedCategoriesView) {
|
||||
Galaxy.admintoolshedapp.adminShedCategoriesView.reDraw({tool_shed: tool_shed.replace(/\//g, '%2f')});
|
||||
}
|
||||
else {
|
||||
Galaxy.admintoolshedapp.adminShedCategoriesView = new mod_categories_view.CategoryView({tool_shed: tool_shed.replace(/\//g, '%2f')});
|
||||
}
|
||||
});
|
||||
this.admin_toolshed_router.on('route:repositories', function(tool_shed, category_id) {
|
||||
if (Galaxy.admintoolshedapp.adminShedCategoryView) {
|
||||
Galaxy.admintoolshedapp.adminShedCategoryView.reDraw({tool_shed: tool_shed.replace(/\//g, '%2f'), category_id: category_id});
|
||||
}
|
||||
else {
|
||||
Galaxy.admintoolshedapp.adminShedCategoryView = new mod_repositories_view.Category({tool_shed: tool_shed.replace(/\//g, '%2f'), category_id: category_id});
|
||||
}
|
||||
});
|
||||
this.admin_toolshed_router.on('route:repository', function(tool_shed, repository_id) {
|
||||
if (Galaxy.admintoolshedapp.adminRepositoryView) {
|
||||
Galaxy.admintoolshedapp.adminRepositoryView.reDraw({tool_shed: tool_shed.replace(/\//g, '%2f'), repository_id: repository_id});
|
||||
}
|
||||
else {
|
||||
Galaxy.admintoolshedapp.adminRepositoryView = new mod_repository_view.RepoDetails({tool_shed: tool_shed.replace(/\//g, '%2f'), repository_id: repository_id});
|
||||
}
|
||||
});
|
||||
this.admin_toolshed_router.on('route:status', function(repositories) {
|
||||
if (Galaxy.admintoolshedapp.adminRepoStatusView) {
|
||||
Galaxy.admintoolshedapp.adminRepoStatusView.reDraw({repositories: repositories.split('|')});
|
||||
}
|
||||
else {
|
||||
Galaxy.admintoolshedapp.adminRepoStatusView = new mod_repo_status_view.RepoStatus({repositories: repositories.split('|')});
|
||||
}
|
||||
});
|
||||
this.admin_toolshed_router.on('route:workflows', function() {
|
||||
if (Galaxy.admintoolshedapp.adminWorkflowsView) {
|
||||
Galaxy.admintoolshedapp.adminWorkflowsView.reDraw();
|
||||
}
|
||||
else {
|
||||
Galaxy.admintoolshedapp.adminWorkflowsView = new mod_workflows_view.Workflows();
|
||||
}
|
||||
});
|
||||
|
||||
Backbone.history.start({pushState: false});
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
GalaxyApp: GalaxyAdminToolshedApp
|
||||
};
|
||||
|
||||
});
|
||||
export default {
|
||||
GalaxyApp: GalaxyAdminToolshedApp
|
||||
};
|
||||
|
||||
@@ -1,17 +1,196 @@
|
||||
var jQuery = require( 'jquery' ),
|
||||
$ = jQuery,
|
||||
GalaxyApp = require( 'galaxy' ).GalaxyApp,
|
||||
AdminPanel = require( './panels/admin-panel' ),
|
||||
Page = require( 'layout/page' );
|
||||
import _l from "utils/localization";
|
||||
import jQuery from "jquery";
|
||||
var $ = jQuery;
|
||||
import GalaxyApp from "galaxy";
|
||||
import AdminPanel from "./panels/admin-panel";
|
||||
import FormWrapper from "mvc/form/form-wrapper";
|
||||
import GridView from "mvc/grid/grid-view";
|
||||
import Ui from "mvc/ui/ui-misc";
|
||||
import QueryStringParsing from "utils/query-string-parsing";
|
||||
import Router from "layout/router";
|
||||
import Utils from "utils/utils";
|
||||
import Page from "layout/page";
|
||||
import Vue from "libs/vue";
|
||||
import UserAPIKeys from "components/UserAPIKeys.vue";
|
||||
|
||||
window.app = function app( options, bootstrapped ){
|
||||
window.Galaxy = new GalaxyApp( options, bootstrapped );
|
||||
Galaxy.debug( 'admin app' );
|
||||
Galaxy.params = Galaxy.config.params;
|
||||
$(function(){
|
||||
_.extend( options.config, { active_view : 'admin' } );
|
||||
Galaxy.page = new Page.View( _.extend( options, {
|
||||
Left : AdminPanel
|
||||
} ) );
|
||||
window.app = function app(options, bootstrapped) {
|
||||
window.Galaxy = new GalaxyApp.GalaxyApp(options, bootstrapped);
|
||||
Galaxy.debug("admin app");
|
||||
|
||||
/** Routes */
|
||||
var AdminRouter = Router.extend({
|
||||
routes: {
|
||||
"(/)admin(/)users": "show_users",
|
||||
"(/)admin(/)roles": "show_roles",
|
||||
"(/)admin(/)groups": "show_groups",
|
||||
"(/)admin(/)tool_versions": "show_tool_versions",
|
||||
"(/)admin(/)quotas": "show_quotas",
|
||||
"(/)admin(/)repositories": "show_repositories",
|
||||
"(/)admin(/)forms": "show_forms",
|
||||
"(/)admin(/)form(/)(:form_id)": "show_form",
|
||||
"(/)admin/api_keys": "show_user_api_keys"
|
||||
},
|
||||
|
||||
authenticate: function(args, name) {
|
||||
return Galaxy.user && Galaxy.user.id && Galaxy.user.get("is_admin");
|
||||
},
|
||||
|
||||
show_users: function() {
|
||||
this.page.display(
|
||||
new GridView({
|
||||
url_base: `${Galaxy.root}admin/users_list`,
|
||||
url_data: Galaxy.params,
|
||||
dict_format: true
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
show_roles: function() {
|
||||
this.page.display(
|
||||
new GridView({
|
||||
url_base: `${Galaxy.root}admin/roles_list`,
|
||||
url_data: Galaxy.params,
|
||||
dict_format: true
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
show_groups: function() {
|
||||
this.page.display(
|
||||
new GridView({
|
||||
url_base: `${Galaxy.root}admin/groups_list`,
|
||||
url_data: Galaxy.params,
|
||||
dict_format: true
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
show_repositories: function() {
|
||||
this.page.display(
|
||||
new GridView({
|
||||
url_base: `${Galaxy.root}admin_toolshed/browse_repositories`,
|
||||
url_data: Galaxy.params,
|
||||
dict_format: true
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
show_tool_versions: function() {
|
||||
this.page.display(
|
||||
new GridView({
|
||||
url_base: `${Galaxy.root}admin/tool_versions_list`,
|
||||
url_data: Galaxy.params,
|
||||
dict_format: true
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
show_quotas: function() {
|
||||
this.page.display(
|
||||
new GridView({
|
||||
url_base: `${Galaxy.root}admin/quotas_list`,
|
||||
url_data: Galaxy.params,
|
||||
dict_format: true
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
show_user_api_keys: function() {
|
||||
var vuemount = document.createElement("div");
|
||||
this.page.display(vuemount);
|
||||
new Vue(UserAPIKeys).$mount(vuemount);
|
||||
},
|
||||
|
||||
show_forms: function() {
|
||||
this.page.display(
|
||||
new GridView({
|
||||
url_base: `${Galaxy.root}forms/forms_list`,
|
||||
url_data: Galaxy.params,
|
||||
dict_format: true
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
show_form: function(form_id) {
|
||||
var id = `?id=${QueryStringParsing.get("id")}`;
|
||||
var form_defs = {
|
||||
reset_user_password: {
|
||||
title: _l("Reset passwords"),
|
||||
url: `admin/reset_user_password${id}`,
|
||||
icon: "fa-user",
|
||||
submit_title: "Save new password",
|
||||
redirect: "admin/users"
|
||||
},
|
||||
manage_roles_and_groups_for_user: {
|
||||
url: `admin/manage_roles_and_groups_for_user${id}`,
|
||||
icon: "fa-users",
|
||||
redirect: "admin/users"
|
||||
},
|
||||
manage_users_and_groups_for_role: {
|
||||
url: `admin/manage_users_and_groups_for_role${id}`,
|
||||
redirect: "admin/roles"
|
||||
},
|
||||
manage_users_and_roles_for_group: {
|
||||
url: `admin/manage_users_and_roles_for_group${id}`,
|
||||
redirect: "admin/groups"
|
||||
},
|
||||
manage_users_and_groups_for_quota: {
|
||||
url: `admin/manage_users_and_groups_for_quota${id}`,
|
||||
redirect: "admin/quotas"
|
||||
},
|
||||
create_role: {
|
||||
url: "admin/create_role",
|
||||
redirect: "admin/roles"
|
||||
},
|
||||
create_group: {
|
||||
url: "admin/create_group",
|
||||
redirect: "admin/groups"
|
||||
},
|
||||
create_quota: {
|
||||
url: "admin/create_quota",
|
||||
redirect: "admin/quotas"
|
||||
},
|
||||
rename_role: {
|
||||
url: `admin/rename_role${id}`,
|
||||
redirect: "admin/roles"
|
||||
},
|
||||
rename_group: {
|
||||
url: `admin/rename_group${id}`,
|
||||
redirect: "admin/groups"
|
||||
},
|
||||
rename_quota: {
|
||||
url: `admin/rename_quota${id}`,
|
||||
redirect: "admin/quotas"
|
||||
},
|
||||
edit_quota: {
|
||||
url: `admin/edit_quota${id}`,
|
||||
redirect: "admin/quotas"
|
||||
},
|
||||
set_quota_default: {
|
||||
url: `admin/set_quota_default${id}`,
|
||||
redirect: "admin/quotas"
|
||||
},
|
||||
create_form: {
|
||||
url: "forms/create_form",
|
||||
redirect: "admin/forms"
|
||||
},
|
||||
edit_form: {
|
||||
url: `forms/edit_form${id}`,
|
||||
redirect: "admin/forms"
|
||||
}
|
||||
};
|
||||
this.page.display(new FormWrapper.View(form_defs[form_id]));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$(() => {
|
||||
_.extend(options.config, { active_view: "admin" });
|
||||
Utils.setWindowTitle("Administration");
|
||||
Galaxy.page = new Page.View(
|
||||
_.extend(options, {
|
||||
Left: AdminPanel,
|
||||
Router: AdminRouter
|
||||
})
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,18 +1,28 @@
|
||||
var jQuery = require( 'jquery' ),
|
||||
$ = jQuery,
|
||||
GalaxyApp = require( 'galaxy' ).GalaxyApp,
|
||||
QUERY_STRING = require( 'utils/query-string-parsing' ),
|
||||
ToolPanel = require( './panels/tool-panel' ),
|
||||
HistoryPanel = require( './panels/history-panel' ),
|
||||
Page = require( 'layout/page' ),
|
||||
ToolForm = require( 'mvc/tool/tool-form' ),
|
||||
UserPreferences = require( 'mvc/user/user-preferences' ),
|
||||
CustomBuilds = require( 'mvc/user/user-custom-builds' ),
|
||||
Tours = require( 'mvc/tours' ),
|
||||
GridView = require( 'mvc/grid/grid-view' ),
|
||||
PageList = require( 'mvc/page/page-list' ),
|
||||
Workflows = require( 'mvc/workflow/workflow' ),
|
||||
WorkflowsConfigureMenu = require( 'mvc/workflow/workflow-configure-menu' );
|
||||
import jQuery from "jquery";
|
||||
var $ = jQuery;
|
||||
import GalaxyApp from "galaxy";
|
||||
import Router from "layout/router";
|
||||
import ToolPanel from "./panels/tool-panel";
|
||||
import HistoryPanel from "./panels/history-panel";
|
||||
import Page from "layout/page";
|
||||
import ToolForm from "mvc/tool/tool-form";
|
||||
import FormWrapper from "mvc/form/form-wrapper";
|
||||
import UserPreferences from "mvc/user/user-preferences";
|
||||
import CustomBuilds from "mvc/user/user-custom-builds";
|
||||
import Tours from "mvc/tours";
|
||||
import GridView from "mvc/grid/grid-view";
|
||||
import GridShared from "mvc/grid/grid-shared";
|
||||
import Workflows from "mvc/workflow/workflow";
|
||||
import HistoryImport from "components/HistoryImport.vue";
|
||||
import HistoryList from "mvc/history/history-list";
|
||||
import ToolFormComposite from "mvc/tool/tool-form-composite";
|
||||
import QueryStringParsing from "utils/query-string-parsing";
|
||||
import Utils from "utils/utils";
|
||||
import Ui from "mvc/ui/ui-misc";
|
||||
import DatasetError from "mvc/dataset/dataset-error";
|
||||
import DatasetEditAttributes from "mvc/dataset/dataset-edit-attributes";
|
||||
import Citations from "components/Citations.vue";
|
||||
import Vue from "libs/vue";
|
||||
|
||||
/** define the 'Analyze Data'/analysis/main/home page for Galaxy
|
||||
* * has a masthead
|
||||
@@ -25,179 +35,272 @@ var jQuery = require( 'jquery' ),
|
||||
* * tables showing the contents of datasets
|
||||
* * etc.
|
||||
*/
|
||||
window.app = function app( options, bootstrapped ){
|
||||
window.Galaxy = new GalaxyApp( options, bootstrapped );
|
||||
Galaxy.debug( 'analysis app' );
|
||||
Galaxy.params = Galaxy.config.params;
|
||||
|
||||
var routingMessage = Backbone.View.extend({
|
||||
initialize: function(options) {
|
||||
this.message = options.message || "Undefined Message";
|
||||
this.msg_status = options.type || 'info';
|
||||
this.render();
|
||||
},
|
||||
render: function(){
|
||||
this.$el.html(_.escape(this.message)).addClass(this.msg_status + "message");
|
||||
}
|
||||
});
|
||||
window.app = function app(options, bootstrapped) {
|
||||
window.Galaxy = new GalaxyApp.GalaxyApp(options, bootstrapped);
|
||||
Galaxy.debug("analysis app");
|
||||
|
||||
/** Routes */
|
||||
var Router = Backbone.Router.extend({
|
||||
// TODO: not many client routes at this point - fill and remove from server.
|
||||
// since we're at root here, this may be the last to be routed entirely on the client.
|
||||
initialize : function( page, options ){
|
||||
this.page = page;
|
||||
this.options = options;
|
||||
var AnalysisRouter = Router.extend({
|
||||
routes: {
|
||||
"(/)": "home",
|
||||
"(/)root*": "home",
|
||||
"(/)tours(/)(:tour_id)": "show_tours",
|
||||
"(/)user(/)": "show_user",
|
||||
"(/)user(/)(:form_id)": "show_user_form",
|
||||
"(/)pages(/)create(/)": "show_pages_create",
|
||||
"(/)pages(/)edit(/)": "show_pages_edit",
|
||||
"(/)pages(/)(:action_id)": "show_pages",
|
||||
"(/)visualizations(/)edit(/)": "show_visualizations_edit",
|
||||
"(/)visualizations/(:action_id)": "show_visualizations",
|
||||
"(/)workflows/import_workflow": "show_import_workflow",
|
||||
"(/)workflows/run(/)": "show_run",
|
||||
"(/)workflows(/)list": "show_workflows",
|
||||
"(/)workflows/list_published(/)": "show_workflows_published",
|
||||
"(/)workflows/create(/)": "show_workflows_create",
|
||||
"(/)histories(/)citations(/)": "show_history_citations",
|
||||
"(/)histories(/)rename(/)": "show_histories_rename",
|
||||
"(/)histories(/)import(/)": "show_histories_import",
|
||||
"(/)histories(/)permissions(/)": "show_histories_permissions",
|
||||
"(/)histories(/)(:action_id)": "show_histories",
|
||||
"(/)datasets(/)list(/)": "show_datasets",
|
||||
"(/)custom_builds": "show_custom_builds",
|
||||
"(/)datasets/edit": "show_dataset_edit_attributes",
|
||||
"(/)datasets/error": "show_dataset_error"
|
||||
},
|
||||
|
||||
/** helper to push a new navigation state */
|
||||
push: function( url, data ) {
|
||||
data = data || {};
|
||||
data.__identifer = Math.random().toString( 36 ).substr( 2 );
|
||||
if ( !$.isEmptyObject( data ) ) {
|
||||
url += url.indexOf( '?' ) == -1 ? '?' : '&';
|
||||
url += $.param( data , true );
|
||||
}
|
||||
this.navigate( url, { 'trigger': true } );
|
||||
require_login: ["show_user", "show_user_form", "show_workflows"],
|
||||
|
||||
authenticate: function(args, name) {
|
||||
return (Galaxy.user && Galaxy.user.id) || this.require_login.indexOf(name) == -1;
|
||||
},
|
||||
|
||||
/** override to parse query string into obj and send to each route */
|
||||
execute: function( callback, args, name ){
|
||||
Galaxy.debug( 'router execute:', callback, args, name );
|
||||
var queryObj = QUERY_STRING.parse( args.pop() );
|
||||
args.push( queryObj );
|
||||
if( callback ){
|
||||
if ( this.authenticate( args, name ) ) {
|
||||
callback.apply( this, args );
|
||||
} else {
|
||||
this.loginRequired();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
routes : {
|
||||
'(/)' : 'home',
|
||||
'(/)root*' : 'home',
|
||||
'(/)tours(/)(:tour_id)' : 'show_tours',
|
||||
'(/)user(/)' : 'show_user',
|
||||
'(/)user(/)(:form_id)' : 'show_user_form',
|
||||
'(/)workflow(/)' : 'show_workflows',
|
||||
'(/)pages(/)(:action_id)' : 'show_pages',
|
||||
'(/)workflow/configure_menu(/)' : 'show_configure_menu',
|
||||
'(/)custom_builds' : 'show_custom_builds'
|
||||
},
|
||||
|
||||
require_login: [
|
||||
'show_user',
|
||||
'show_user_form',
|
||||
'show_workflows',
|
||||
'show_configure_menu'
|
||||
],
|
||||
|
||||
loginRequired: function() {
|
||||
this.page.display( new routingMessage({type: 'error', message: "You must be logged in to make this request."}) );
|
||||
},
|
||||
|
||||
authenticate: function( args, name ) {
|
||||
return ( Galaxy.user && Galaxy.user.id ) || this.require_login.indexOf( name ) == -1;
|
||||
},
|
||||
|
||||
show_tours : function( tour_id ){
|
||||
if ( tour_id ){
|
||||
Tours.giveTour( tour_id );
|
||||
show_tours: function(tour_id) {
|
||||
if (tour_id) {
|
||||
Tours.giveTourById(tour_id);
|
||||
} else {
|
||||
this.page.display( new Tours.ToursView() );
|
||||
this.page.display(new Tours.ToursView());
|
||||
}
|
||||
},
|
||||
|
||||
show_user : function(){
|
||||
this.page.display( new UserPreferences.View() );
|
||||
show_user: function() {
|
||||
this.page.display(new UserPreferences.View());
|
||||
},
|
||||
|
||||
show_user_form : function( form_id ) {
|
||||
this.page.display( new UserPreferences.Forms( { form_id: form_id, user_id: Galaxy.params.id } ) );
|
||||
show_user_form: function(form_id) {
|
||||
var model = new UserPreferences.Model({
|
||||
user_id: Galaxy.params.id
|
||||
});
|
||||
this.page.display(new FormWrapper.View(model.get(form_id)));
|
||||
},
|
||||
|
||||
show_pages : function( action_id ) {
|
||||
if ( action_id == 'list' ) {
|
||||
this.page.display( new PageList.View() );
|
||||
} else {
|
||||
this.page.display( new GridView( { url_base: Galaxy.root + 'page/list_published', dict_format: true } ) );
|
||||
}
|
||||
show_visualizations: function(action_id) {
|
||||
this.page.display(
|
||||
new GridShared.View({
|
||||
action_id: action_id,
|
||||
plural: "Visualizations",
|
||||
item: "visualization"
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
show_workflows : function(){
|
||||
this.page.display( new Workflows.View() );
|
||||
show_visualizations_edit: function() {
|
||||
this.page.display(
|
||||
new FormWrapper.View({
|
||||
url: `visualization/edit?id=${QueryStringParsing.get("id")}`,
|
||||
redirect: "visualizations/list"
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
show_configure_menu : function(){
|
||||
this.page.display( new WorkflowsConfigureMenu.View() );
|
||||
show_workflows_published: function() {
|
||||
this.page.display(
|
||||
new GridView({
|
||||
url_base: `${Galaxy.root}workflow/list_published`,
|
||||
dict_format: true
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
show_custom_builds : function() {
|
||||
var self = this;
|
||||
show_histories: function(action_id) {
|
||||
this.page.display(new HistoryList.View({ action_id: action_id }));
|
||||
},
|
||||
|
||||
show_history_citations: function() {
|
||||
var citationInstance = Vue.extend(Citations);
|
||||
var vm = document.createElement("div");
|
||||
this.page.display(vm);
|
||||
new citationInstance({ propsData: { id: QueryStringParsing.get("id"), source: "histories" } }).$mount(vm);
|
||||
},
|
||||
|
||||
show_histories_rename: function() {
|
||||
this.page.display(
|
||||
new FormWrapper.View({
|
||||
url: `history/rename?id=${QueryStringParsing.get("id")}`,
|
||||
redirect: "histories/list"
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
show_histories_import: function() {
|
||||
var historyImportInstance = Vue.extend(HistoryImport);
|
||||
var vm = document.createElement("div");
|
||||
this.page.display(vm);
|
||||
new historyImportInstance().$mount(vm);
|
||||
},
|
||||
|
||||
show_histories_permissions: function() {
|
||||
this.page.display(
|
||||
new FormWrapper.View({
|
||||
url: `history/permissions?id=${QueryStringParsing.get("id")}`,
|
||||
redirect: "histories/list"
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
show_datasets: function() {
|
||||
this.page.display(
|
||||
new GridView({
|
||||
url_base: `${Galaxy.root}dataset/list`,
|
||||
dict_format: true
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
show_pages: function(action_id) {
|
||||
this.page.display(
|
||||
new GridShared.View({
|
||||
action_id: action_id,
|
||||
plural: "Pages",
|
||||
item: "page"
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
show_pages_create: function() {
|
||||
this.page.display(
|
||||
new FormWrapper.View({
|
||||
url: "page/create",
|
||||
redirect: "pages/list"
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
show_pages_edit: function() {
|
||||
this.page.display(
|
||||
new FormWrapper.View({
|
||||
url: `page/edit?id=${QueryStringParsing.get("id")}`,
|
||||
redirect: "pages/list"
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
show_workflows: function() {
|
||||
this.page.display(new Workflows.View());
|
||||
},
|
||||
|
||||
show_workflows_create: function() {
|
||||
this.page.display(
|
||||
new FormWrapper.View({
|
||||
url: `workflow/create`,
|
||||
redirect: "workflow/editor"
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
show_run: function() {
|
||||
this._loadWorkflow();
|
||||
},
|
||||
|
||||
show_import_workflow: function() {
|
||||
this.page.display(new Workflows.ImportWorkflowView());
|
||||
},
|
||||
|
||||
show_custom_builds: function() {
|
||||
var historyPanel = this.page.historyPanel.historyView;
|
||||
if ( !historyPanel || !historyPanel.model || !historyPanel.model.id ) {
|
||||
window.setTimeout(function() { self.show_custom_builds() }, 500)
|
||||
if (!historyPanel || !historyPanel.model || !historyPanel.model.id) {
|
||||
window.setTimeout(() => {
|
||||
this.show_custom_builds();
|
||||
}, 500);
|
||||
return;
|
||||
}
|
||||
this.page.display( new CustomBuilds.View() );
|
||||
this.page.display(new CustomBuilds.View());
|
||||
},
|
||||
|
||||
show_dataset_edit_attributes: function() {
|
||||
this.page.display(new DatasetEditAttributes.View());
|
||||
},
|
||||
|
||||
show_dataset_error: function() {
|
||||
this.page.display(new DatasetError.View());
|
||||
},
|
||||
|
||||
/** */
|
||||
home : function( params ){
|
||||
home: function(params) {
|
||||
// TODO: to router, remove Globals
|
||||
// load a tool by id (tool_id) or rerun a previous tool execution (job_id)
|
||||
if( params.tool_id || params.job_id ) {
|
||||
if ( params.tool_id === 'upload1' ) {
|
||||
if (params.tool_id || params.job_id) {
|
||||
if (params.tool_id === "upload1") {
|
||||
this.page.toolPanel.upload.show();
|
||||
this._loadCenterIframe( 'welcome' );
|
||||
this._loadCenterIframe("welcome");
|
||||
} else {
|
||||
this._loadToolForm( params );
|
||||
this._loadToolForm(params);
|
||||
}
|
||||
} else {
|
||||
// show the workflow run form
|
||||
if( params.workflow_id ){
|
||||
this._loadCenterIframe( 'workflow/run?id=' + params.workflow_id );
|
||||
// load the center iframe with controller.action: galaxy.org/?m_c=history&m_a=list -> history/list
|
||||
} else if( params.m_c ){
|
||||
this._loadCenterIframe( params.m_c + '/' + params.m_a );
|
||||
// show the workflow run form
|
||||
if (params.workflow_id) {
|
||||
this._loadWorkflow();
|
||||
// load the center iframe with controller.action: galaxy.org/?m_c=history&m_a=list -> history/list
|
||||
} else if (params.m_c) {
|
||||
this._loadCenterIframe(`${params.m_c}/${params.m_a}`);
|
||||
// show the workflow run form
|
||||
} else {
|
||||
this._loadCenterIframe( 'welcome' );
|
||||
this._loadCenterIframe("welcome");
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/** load the center panel with a tool form described by the given params obj */
|
||||
_loadToolForm : function( params ){
|
||||
_loadToolForm: function(params) {
|
||||
//TODO: load tool form code async
|
||||
params.id = params.tool_id;
|
||||
this.page.display( new ToolForm.View( params ) );
|
||||
params.id = decodeURIComponent(params.tool_id);
|
||||
this.page.display(new ToolForm.View(params));
|
||||
},
|
||||
|
||||
/** load the center panel iframe using the given url */
|
||||
_loadCenterIframe : function( url, root ){
|
||||
_loadCenterIframe: function(url, root) {
|
||||
root = root || Galaxy.root;
|
||||
url = root + url;
|
||||
this.page.$( '#galaxy_main' ).prop( 'src', url );
|
||||
this.page.$("#galaxy_main").prop("src", url);
|
||||
},
|
||||
|
||||
/** load workflow by its url in run mode */
|
||||
_loadWorkflow: function() {
|
||||
Utils.get({
|
||||
url: `${Galaxy.root}api/workflows/${Utils.getQueryString("id")}/download?style=run`,
|
||||
success: response => {
|
||||
this.page.display(new ToolFormComposite.View(response));
|
||||
},
|
||||
error: response => {
|
||||
var error_msg = response.err_msg || "Error occurred while loading the resource.";
|
||||
var options = {
|
||||
message: error_msg,
|
||||
status: "danger",
|
||||
persistent: true
|
||||
};
|
||||
this.page.display(new Ui.Message(options));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// render and start the router
|
||||
$(function(){
|
||||
|
||||
Galaxy.page = new Page.View( _.extend( options, {
|
||||
Left : ToolPanel,
|
||||
Right : HistoryPanel,
|
||||
Router : Router
|
||||
} ) );
|
||||
|
||||
// start the router - which will call any of the routes above
|
||||
Backbone.history.start({
|
||||
root : Galaxy.root,
|
||||
pushState : true,
|
||||
});
|
||||
$(() => {
|
||||
Galaxy.page = new Page.View(
|
||||
_.extend(options, {
|
||||
Left: ToolPanel,
|
||||
Right: HistoryPanel,
|
||||
Router: AnalysisRouter
|
||||
})
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import WorkflowView from "mvc/workflow/workflow-view";
|
||||
import Trackster from "viz/trackster";
|
||||
import Circster from "viz/circster";
|
||||
import GalaxyLibrary from "galaxy.library";
|
||||
import AdminToolshed from "admin.toolshed";
|
||||
import Masthead from "layout/masthead";
|
||||
import user from "mvc/user/user-model";
|
||||
import Modal from "mvc/ui/ui-modal";
|
||||
|
||||
export function mastheadEntry(options) {
|
||||
if (!Galaxy.user) {
|
||||
Galaxy.user = new user.User(options.user_json);
|
||||
}
|
||||
if (!Galaxy.masthead) {
|
||||
Galaxy.masthead = new Masthead.View(options);
|
||||
Galaxy.modal = new Modal.View();
|
||||
$("#masthead").replaceWith(Galaxy.masthead.render().$el);
|
||||
}
|
||||
}
|
||||
|
||||
export function adminToolshedEntry(options) {
|
||||
new AdminToolshed.GalaxyApp(options);
|
||||
}
|
||||
|
||||
export function tracksterEntry(options) {
|
||||
new Trackster.GalaxyApp(options);
|
||||
}
|
||||
|
||||
export function circsterEntry(options) {
|
||||
new Circster.GalaxyApp(options);
|
||||
}
|
||||
|
||||
export function workflowEntry(options) {
|
||||
new WorkflowView(options);
|
||||
}
|
||||
|
||||
function libraryEntry(options) {
|
||||
new GalaxyLibrary.GalaxyApp(options);
|
||||
}
|
||||
|
||||
export const bundleEntries = {
|
||||
library: libraryEntry,
|
||||
masthead: mastheadEntry,
|
||||
workflow: workflowEntry,
|
||||
trackster: tracksterEntry,
|
||||
circster: circsterEntry,
|
||||
adminToolshed: adminToolshedEntry
|
||||
};
|
||||
|
||||
window.bundleEntries = bundleEntries;
|
||||
@@ -1,40 +1,44 @@
|
||||
var jQuery = require( 'jquery' ),
|
||||
$ = jQuery,
|
||||
GalaxyApp = require( 'galaxy' ).GalaxyApp,
|
||||
_l = require( 'utils/localization' ),
|
||||
Page = require( 'layout/page' );
|
||||
import jQuery from "jquery";
|
||||
var $ = jQuery;
|
||||
import GalaxyApp from "galaxy";
|
||||
import _l from "utils/localization";
|
||||
import Page from "layout/page";
|
||||
|
||||
window.app = function app( options, bootstrapped ){
|
||||
window.Galaxy = new GalaxyApp( options, bootstrapped );
|
||||
Galaxy.debug( 'login app' );
|
||||
var redirect = encodeURI( options.redirect );
|
||||
window.app = function app(options, bootstrapped) {
|
||||
window.Galaxy = new GalaxyApp.GalaxyApp(options, bootstrapped);
|
||||
Galaxy.debug("login app");
|
||||
var redirect = encodeURI(options.redirect);
|
||||
|
||||
// TODO: remove iframe for user login (at least) and render login page from here
|
||||
// then remove this redirect
|
||||
if( !options.show_welcome_with_login ){
|
||||
var params = jQuery.param({ use_panels : 'True', redirect : redirect });
|
||||
window.location.href = Galaxy.root + 'user/login?' + params;
|
||||
if (!options.show_welcome_with_login) {
|
||||
var params = jQuery.param({ use_panels: "True", redirect: redirect });
|
||||
window.location.href = `${Galaxy.root}user/login?${params}`;
|
||||
return;
|
||||
}
|
||||
|
||||
var LoginPage = Backbone.View.extend({
|
||||
initialize: function( page ) {
|
||||
initialize: function(page) {
|
||||
this.page = page;
|
||||
this.model = new Backbone.Model({ title : _l( 'Login required' ) } );
|
||||
this.setElement( this._template() );
|
||||
this.model = new Backbone.Model({ title: _l("Login required") });
|
||||
this.setElement(this._template());
|
||||
},
|
||||
render: function() {
|
||||
this.page.$( '#galaxy_main' ).prop( 'src', options.welcome_url );
|
||||
this.page.$("#galaxy_main").prop("src", options.welcome_url);
|
||||
},
|
||||
_template : function() {
|
||||
var login_url = options.root + 'user/login?' + $.param( { redirect : redirect } );
|
||||
return '<iframe src="' + login_url + '" frameborder="0" style="width: 100%; height: 100%;"/>';
|
||||
_template: function() {
|
||||
var login_url = `${options.root}user/login?${$.param({
|
||||
redirect: redirect
|
||||
})}`;
|
||||
return `<iframe src="${login_url}" frameborder="0" style="width: 100%; height: 100%;"/>`;
|
||||
}
|
||||
});
|
||||
|
||||
$(function(){
|
||||
Galaxy.page = new Page.View( _.extend( options, {
|
||||
Right : LoginPage
|
||||
} ) );
|
||||
$(() => {
|
||||
Galaxy.page = new Page.View(
|
||||
_.extend(options, {
|
||||
Right: LoginPage
|
||||
})
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,169 +1,189 @@
|
||||
var _l = require( 'utils/localization' );
|
||||
import _l from "utils/localization";
|
||||
|
||||
var AdminPanel = Backbone.View.extend({
|
||||
initialize: function( page, options ) {
|
||||
initialize: function(page, options) {
|
||||
var self = this;
|
||||
this.page = page;
|
||||
this.root = options.root;
|
||||
this.config = options.config;
|
||||
this.settings = options.settings;
|
||||
this.message = options.message;
|
||||
this.status = options.status;
|
||||
this.page = page;
|
||||
this.root = options.root;
|
||||
this.config = options.config;
|
||||
this.settings = options.settings;
|
||||
this.message = options.message;
|
||||
this.status = options.status;
|
||||
this.model = new Backbone.Model({
|
||||
title : _l( 'Administration' )
|
||||
title: _l("Administration")
|
||||
});
|
||||
this.categories = new Backbone.Collection([{
|
||||
title : 'Server',
|
||||
items : [ {
|
||||
title : 'Data types registry',
|
||||
url : 'admin/view_datatypes_registry'
|
||||
},{
|
||||
title : 'Data tables registry',
|
||||
url : 'admin/view_tool_data_tables'
|
||||
},{
|
||||
title : 'Display applications',
|
||||
url : 'admin/display_applications'
|
||||
},{
|
||||
title : 'Manage jobs',
|
||||
url : 'admin/jobs'
|
||||
} ]
|
||||
},{
|
||||
title : 'Tools and Tool Shed',
|
||||
items : [ {
|
||||
title : 'Search Tool Shed',
|
||||
url : 'admin_toolshed/browse_tool_sheds',
|
||||
enabled : self.settings.is_tool_shed_installed
|
||||
},{
|
||||
title : 'Search Tool Shed (Beta)',
|
||||
url : 'admin_toolshed/browse_toolsheds',
|
||||
enabled : self.settings.is_tool_shed_installed && self.config.enable_beta_ts_api_install
|
||||
},{
|
||||
title : 'Monitor installing repositories',
|
||||
url : 'admin_toolshed/monitor_repository_installation?installing_repository_ids=' + self.settings.installing_repository_ids,
|
||||
enabled : self.settings.installing_repository_ids
|
||||
},{
|
||||
title : 'Manage installed tools',
|
||||
url : 'admin_toolshed/browse_repositories',
|
||||
enabled : self.settings.is_repo_installed
|
||||
},{
|
||||
title : 'Reset metadata',
|
||||
url : 'admin_toolshed/reset_metadata_on_selected_installed_repositories',
|
||||
enabled : self.settings.is_repo_installed
|
||||
},{
|
||||
title : 'Download local tool',
|
||||
url : 'admin/package_tool'
|
||||
},{
|
||||
title : 'Tool lineage',
|
||||
url : 'admin/tool_versions'
|
||||
},{
|
||||
title : 'Reload a tool\'s configuration',
|
||||
url : 'admin/reload_tool'
|
||||
},{
|
||||
title : 'Review tool migration stages',
|
||||
url : 'admin/review_tool_migration_stages'
|
||||
},{
|
||||
title : 'View Tool Error Logs',
|
||||
url : 'admin/tool_errors'
|
||||
},{
|
||||
title : 'Manage Display Whitelist',
|
||||
url : 'admin/sanitize_whitelist'
|
||||
},{
|
||||
title : 'Manage Tool Dependencies',
|
||||
url : 'admin/manage_tool_dependencies'
|
||||
} ]
|
||||
},{
|
||||
title : 'User Management',
|
||||
items : [ {
|
||||
title : 'Users',
|
||||
url : 'admin/users'
|
||||
},{
|
||||
title : 'Groups',
|
||||
url : 'admin/groups'
|
||||
},{
|
||||
title : 'Roles',
|
||||
url : 'admin/roles'
|
||||
},{
|
||||
title : 'API keys',
|
||||
url : 'userskeys/all_users'
|
||||
},{
|
||||
title : 'Impersonate a user',
|
||||
url : 'admin/impersonate',
|
||||
enabled : self.config.allow_user_impersonation
|
||||
} ]
|
||||
},{
|
||||
title : 'Data',
|
||||
items : [ {
|
||||
title : 'Quotas',
|
||||
url : 'admin/quotas',
|
||||
enabled : self.config.enable_quotas
|
||||
},{
|
||||
title : 'Data libraries',
|
||||
url : 'library_admin/browse_libraries'
|
||||
},{
|
||||
title : 'Roles',
|
||||
url : 'admin/roles'
|
||||
},{
|
||||
title : 'Local data',
|
||||
url : 'data_manager'
|
||||
} ]
|
||||
},{
|
||||
title : 'Form Definitions',
|
||||
items : [ {
|
||||
title : 'Form definitions',
|
||||
url : 'forms/browse_form_definitions'
|
||||
} ]
|
||||
},{
|
||||
title : 'Sample Tracking',
|
||||
items : [ {
|
||||
title : 'Sequencers and external services',
|
||||
url : 'external_service/browse_external_services'
|
||||
},{
|
||||
title : 'Request types',
|
||||
url : 'request_type/browse_request_types'
|
||||
},{
|
||||
title : 'Sequencing requests',
|
||||
url : 'requests_admin/browse_requests'
|
||||
},{
|
||||
title : 'Find samples',
|
||||
url : 'requests_common/find_samples?cntrller=requests_admin'
|
||||
} ]
|
||||
}]);
|
||||
this.setElement( this._template() );
|
||||
this.categories = new Backbone.Collection([
|
||||
{
|
||||
title: _l("Server"),
|
||||
items: [
|
||||
{
|
||||
title: _l("Data types"),
|
||||
url: "admin/view_datatypes_registry"
|
||||
},
|
||||
{
|
||||
title: _l("Data tables"),
|
||||
url: "admin/view_tool_data_tables"
|
||||
},
|
||||
{
|
||||
title: _l("Display applications"),
|
||||
url: "admin/display_applications"
|
||||
},
|
||||
{
|
||||
title: _l("Manage jobs"),
|
||||
url: "admin/jobs"
|
||||
},
|
||||
{
|
||||
title: _l("Local data"),
|
||||
url: "data_manager"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: _l("User Management"),
|
||||
items: [
|
||||
{
|
||||
title: _l("Users"),
|
||||
url: "admin/users",
|
||||
target: "__use_router__"
|
||||
},
|
||||
{
|
||||
title: _l("Quotas"),
|
||||
url: "admin/quotas",
|
||||
target: "__use_router__",
|
||||
enabled: self.config.enable_quotas
|
||||
},
|
||||
{
|
||||
title: _l("Groups"),
|
||||
url: "admin/groups",
|
||||
target: "__use_router__"
|
||||
},
|
||||
{
|
||||
title: _l("Roles"),
|
||||
url: "admin/roles",
|
||||
target: "__use_router__"
|
||||
},
|
||||
{
|
||||
title: _l("Forms"),
|
||||
url: "admin/forms",
|
||||
target: "__use_router__"
|
||||
},
|
||||
{
|
||||
title: _l("API keys"),
|
||||
url: "admin/api_keys",
|
||||
target: "__use_router__"
|
||||
},
|
||||
{
|
||||
title: _l("Impersonate a user"),
|
||||
url: "admin/impersonate",
|
||||
enabled: self.config.allow_user_impersonation
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: _l("Tool Management"),
|
||||
items: [
|
||||
{
|
||||
title: _l("Install new tools"),
|
||||
url: "admin_toolshed/browse_tool_sheds",
|
||||
enabled: self.settings.is_tool_shed_installed
|
||||
},
|
||||
{
|
||||
title: "Install new tools (Beta)",
|
||||
url: "admin_toolshed/browse_toolsheds",
|
||||
enabled: self.settings.is_tool_shed_installed && self.config.enable_beta_ts_api_install
|
||||
},
|
||||
{
|
||||
title: _l("Monitor installation"),
|
||||
url: "admin_toolshed/monitor_repository_installation",
|
||||
enabled: self.settings.installing_repository_ids
|
||||
},
|
||||
{
|
||||
title: _l("Manage tools"),
|
||||
url: "admin/repositories",
|
||||
enabled: self.settings.is_repo_installed,
|
||||
target: "__use_router__"
|
||||
},
|
||||
{
|
||||
title: _l("Manage metadata"),
|
||||
url: "admin_toolshed/reset_metadata_on_selected_installed_repositories",
|
||||
enabled: self.settings.is_repo_installed
|
||||
},
|
||||
{
|
||||
title: _l("Manage whitelist"),
|
||||
url: "admin/sanitize_whitelist"
|
||||
},
|
||||
{
|
||||
title: _l("Manage dependencies"),
|
||||
url: "admin/manage_tool_dependencies"
|
||||
},
|
||||
{
|
||||
title: _l("View lineage"),
|
||||
url: "admin/tool_versions",
|
||||
target: "__use_router__"
|
||||
},
|
||||
{
|
||||
title: _l("View migration stages"),
|
||||
url: "admin/review_tool_migration_stages"
|
||||
},
|
||||
{
|
||||
title: _l("View error logs"),
|
||||
url: "admin/tool_errors"
|
||||
}
|
||||
]
|
||||
}
|
||||
]);
|
||||
this.setElement(this._template());
|
||||
},
|
||||
|
||||
render : function() {
|
||||
render: function() {
|
||||
var self = this;
|
||||
this.$el.empty();
|
||||
this.categories.each( function( category ) {
|
||||
var $section = $( self._templateSection( category.attributes ) );
|
||||
var $entries = $section.find( '.ui-side-section-body' );
|
||||
_.each( category.get( 'items' ), function( item ) {
|
||||
if ( item.enabled === undefined || item.enabled ) {
|
||||
$entries.append( $( '<div/>' ).addClass( 'ui-side-section-body-title' )
|
||||
.append( $( '<a/>' ).attr({
|
||||
href : self.root + item.url,
|
||||
target : 'galaxy_main' }).text( _l( item.title ) ) ) );
|
||||
this.categories.each(category => {
|
||||
var $section = $(self._templateSection(category.attributes));
|
||||
var $entries = $section.find(".ui-side-section-body");
|
||||
_.each(category.get("items"), item => {
|
||||
if (item.enabled === undefined || item.enabled) {
|
||||
var $link = $("<a/>")
|
||||
.attr({ href: self.root + item.url })
|
||||
.text(_l(item.title));
|
||||
if (item.target == "__use_router__") {
|
||||
$link.on("click", e => {
|
||||
e.preventDefault();
|
||||
self.page.router.push(item.url);
|
||||
});
|
||||
} else {
|
||||
$link.attr("target", "galaxy_main");
|
||||
}
|
||||
$entries.append(
|
||||
$("<div/>")
|
||||
.addClass("ui-side-section-body-title")
|
||||
.append($link)
|
||||
);
|
||||
}
|
||||
});
|
||||
self.$el.append( $section );
|
||||
self.$el.append($section);
|
||||
});
|
||||
this.page.$( '#galaxy_main' ).prop( 'src', this.root + 'admin/center?message=' + this.message + '&status=' + this.status );
|
||||
this.page
|
||||
.$("#galaxy_main")
|
||||
.prop("src", `${this.root}admin/center?message=${this.message}&status=${this.status}`);
|
||||
},
|
||||
|
||||
_templateSection : function( options ) {
|
||||
_templateSection: function(options) {
|
||||
return [
|
||||
'<div>',
|
||||
'<div class="ui-side-section-title">' + _l( options.title ) + '</div>',
|
||||
'<div class="ui-side-section-body"/>',
|
||||
'</div>'
|
||||
].join('');
|
||||
"<div>",
|
||||
`<div class="ui-side-section-title">${_l(options.title)}</div>`,
|
||||
'<div class="ui-side-section-body"/>',
|
||||
"</div>"
|
||||
].join("");
|
||||
},
|
||||
|
||||
_template : function() {
|
||||
_template: function() {
|
||||
return '<div class="ui-side-panel"/>';
|
||||
},
|
||||
|
||||
toString : function() { return 'adminPanel' }
|
||||
toString: function() {
|
||||
return "adminPanel";
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = AdminPanel;
|
||||
export default AdminPanel;
|
||||
|
||||
@@ -1,90 +1,91 @@
|
||||
var Ui = require( 'mvc/ui/ui-misc' ),
|
||||
historyOptionsMenu = require( 'mvc/history/options-menu' );
|
||||
CurrentHistoryView = require( 'mvc/history/history-view-edit-current' ).CurrentHistoryView,
|
||||
_l = require( 'utils/localization' );
|
||||
import Ui from "mvc/ui/ui-misc";
|
||||
import historyOptionsMenu from "mvc/history/options-menu";
|
||||
import CurrentHistoryView from "mvc/history/history-view-edit-current";
|
||||
import _l from "utils/localization";
|
||||
|
||||
/** the right hand panel in the analysis page that shows the current history */
|
||||
var HistoryPanel = Backbone.View.extend({
|
||||
|
||||
initialize : function( page, options ) {
|
||||
initialize: function(page, options) {
|
||||
var self = this;
|
||||
this.userIsAnonymous = Galaxy.user.isAnonymous();
|
||||
this.allow_user_dataset_purge = options.config.allow_user_dataset_purge;
|
||||
this.root = options.root;
|
||||
this.userIsAnonymous = Galaxy.user.isAnonymous();
|
||||
this.allow_user_dataset_purge = options.config.allow_user_dataset_purge;
|
||||
this.root = options.root;
|
||||
|
||||
// view of the current history
|
||||
this.historyView = new CurrentHistoryView({
|
||||
className : CurrentHistoryView.prototype.className + ' middle',
|
||||
purgeAllowed : this.allow_user_dataset_purge,
|
||||
linkTarget : 'galaxy_main'
|
||||
this.historyView = new CurrentHistoryView.CurrentHistoryView({
|
||||
className: `${CurrentHistoryView.CurrentHistoryView.prototype.className} middle`,
|
||||
purgeAllowed: this.allow_user_dataset_purge,
|
||||
linkTarget: "galaxy_main"
|
||||
});
|
||||
|
||||
// add history panel to Galaxy object
|
||||
Galaxy.currHistoryPanel = this.historyView;
|
||||
Galaxy.currHistoryPanel.listenToGalaxy( Galaxy );
|
||||
Galaxy.currHistoryPanel.listenToGalaxy(Galaxy);
|
||||
|
||||
// build buttons
|
||||
this.buttonRefresh = new Ui.ButtonLink({
|
||||
id : 'history-refresh-button',
|
||||
title : 'Refresh history',
|
||||
cls : 'panel-header-button',
|
||||
icon : 'fa fa-refresh',
|
||||
onclick : function() {
|
||||
id: "history-refresh-button",
|
||||
title: _l("Refresh history"),
|
||||
cls: "panel-header-button",
|
||||
icon: "fa fa-refresh",
|
||||
onclick: function() {
|
||||
self.historyView.loadCurrentHistory();
|
||||
}
|
||||
});
|
||||
this.buttonOptions = new Ui.ButtonLink({
|
||||
id : 'history-options-button',
|
||||
title : 'History options',
|
||||
cls : 'panel-header-button',
|
||||
target : 'galaxy_main',
|
||||
icon : 'fa fa-cog',
|
||||
href : this.root + 'root/history_options'
|
||||
id: "history-options-button",
|
||||
title: _l("History options"),
|
||||
cls: "panel-header-button",
|
||||
target: "galaxy_main",
|
||||
icon: "fa fa-cog",
|
||||
href: `${this.root}root/history_options`
|
||||
});
|
||||
this.buttonViewMulti = new Ui.ButtonLink({
|
||||
id : 'history-view-multi-button',
|
||||
title : 'View all histories',
|
||||
cls : 'panel-header-button',
|
||||
icon : 'fa fa-columns',
|
||||
href : this.root + 'history/view_multiple'
|
||||
id: "history-view-multi-button",
|
||||
title: _l("View all histories"),
|
||||
cls: "panel-header-button",
|
||||
icon: "fa fa-columns",
|
||||
href: `${this.root}history/view_multiple`
|
||||
});
|
||||
|
||||
// define components
|
||||
this.model = new Backbone.Model({
|
||||
cls : 'history-right-panel',
|
||||
title : _l( 'History' ),
|
||||
buttons : [ this.buttonRefresh, this.buttonOptions, this.buttonViewMulti ]
|
||||
cls: "history-right-panel",
|
||||
title: _l("History"),
|
||||
buttons: [this.buttonRefresh, this.buttonOptions, this.buttonViewMulti]
|
||||
});
|
||||
|
||||
// build body template and connect history view
|
||||
this.setElement( this._template() );
|
||||
this.historyView.setElement( this.$el );
|
||||
this.historyView.connectToQuotaMeter( Galaxy.quotaMeter );
|
||||
this.setElement(this._template());
|
||||
this.historyView.setElement(this.$el);
|
||||
this.historyView.connectToQuotaMeter(Galaxy.quotaMeter);
|
||||
this.historyView.loadCurrentHistory();
|
||||
|
||||
// fetch to update the quota meter adding 'current' for any anon-user's id
|
||||
Galaxy.listenTo( this.historyView, 'history-size-change', function(){
|
||||
Galaxy.user.fetch({ url: Galaxy.user.urlRoot() + '/' + ( Galaxy.user.id || 'current' ) });
|
||||
Galaxy.listenTo(this.historyView, "history-size-change", () => {
|
||||
Galaxy.user.fetch({
|
||||
url: `${Galaxy.user.urlRoot()}/${Galaxy.user.id || "current"}`
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
render : function() {
|
||||
this.optionsMenu = historyOptionsMenu( this.buttonOptions.$el, {
|
||||
anonymous : this.userIsAnonymous,
|
||||
purgeAllowed : this.allow_user_dataset_purge,
|
||||
root : this.root
|
||||
render: function() {
|
||||
this.optionsMenu = historyOptionsMenu(this.buttonOptions.$el, {
|
||||
anonymous: this.userIsAnonymous,
|
||||
purgeAllowed: this.allow_user_dataset_purge,
|
||||
root: this.root
|
||||
});
|
||||
this.buttonViewMulti.$el[ !this.userIsAnonymous ? 'show' : 'hide' ]();
|
||||
this.buttonViewMulti.$el[!this.userIsAnonymous ? "show" : "hide"]();
|
||||
},
|
||||
|
||||
/** add history view div */
|
||||
_template : function( data ){
|
||||
return [
|
||||
'<div id="current-history-panel" class="history-panel middle"/>',
|
||||
].join('');
|
||||
_template: function(data) {
|
||||
return ['<div id="current-history-panel" class="history-panel middle"/>'].join("");
|
||||
},
|
||||
|
||||
toString : function() { return 'historyPanel' }
|
||||
toString: function() {
|
||||
return "historyPanel";
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = HistoryPanel;
|
||||
export default HistoryPanel;
|
||||
|
||||
@@ -1,34 +1,37 @@
|
||||
var Tools = require( 'mvc/tool/tools' ),
|
||||
Upload = require( 'mvc/upload/upload-view' ),
|
||||
_l = require( 'utils/localization' );
|
||||
import Tools from "mvc/tool/tools";
|
||||
import Upload from "mvc/upload/upload-view";
|
||||
import _l from "utils/localization";
|
||||
import ToolForm from "mvc/tool/tool-form-composite";
|
||||
|
||||
var ToolPanel = Backbone.View.extend({
|
||||
initialize: function( page, options ) {
|
||||
initialize: function(page, options) {
|
||||
// access configuration options
|
||||
var config = options.config;
|
||||
this.root = options.root;
|
||||
this.root = options.root;
|
||||
|
||||
/** @type {Object[]} descriptions of user's workflows to be shown in the tool menu */
|
||||
this.stored_workflow_menu_entries = config.stored_workflow_menu_entries || [];
|
||||
|
||||
// create tool search, tool panel, and tool panel view.
|
||||
var tool_search = new Tools.ToolSearch({
|
||||
hidden : false
|
||||
hidden: false
|
||||
});
|
||||
var tools = new Tools.ToolCollection( config.toolbox );
|
||||
var tools = new Tools.ToolCollection(config.toolbox);
|
||||
this.tool_panel = new Tools.ToolPanel({
|
||||
tool_search : tool_search,
|
||||
tools : tools,
|
||||
layout : config.toolbox_in_panel
|
||||
tool_search: tool_search,
|
||||
tools: tools,
|
||||
layout: config.toolbox_in_panel
|
||||
});
|
||||
this.tool_panel_view = new Tools.ToolPanelView({
|
||||
model: this.tool_panel
|
||||
});
|
||||
this.tool_panel_view = new Tools.ToolPanelView({ model: this.tool_panel });
|
||||
|
||||
// add upload modal
|
||||
this.upload_button = new Upload({
|
||||
nginx_upload_path : config.nginx_upload_path,
|
||||
ftp_upload_site : config.ftp_upload_site,
|
||||
default_genome : config.default_genome,
|
||||
default_extension : config.default_extension,
|
||||
nginx_upload_path: config.nginx_upload_path,
|
||||
ftp_upload_site: config.ftp_upload_site,
|
||||
default_genome: config.default_genome,
|
||||
default_extension: config.default_extension
|
||||
});
|
||||
|
||||
// add uploader button to Galaxy object
|
||||
@@ -36,76 +39,110 @@ var ToolPanel = Backbone.View.extend({
|
||||
|
||||
// components for panel definition
|
||||
this.model = new Backbone.Model({
|
||||
title : _l( 'Tools' ),
|
||||
buttons : [ this.upload_button ]
|
||||
title: _l("Tools"),
|
||||
buttons: [this.upload_button]
|
||||
});
|
||||
|
||||
// build body template
|
||||
this.setElement( this._template() );
|
||||
this.setElement(this._template());
|
||||
},
|
||||
|
||||
render : function(){
|
||||
render: function() {
|
||||
// if there are tools, render panel and display everything
|
||||
var self = this;
|
||||
if ( this.tool_panel.get( 'layout' ).size() > 0 ) {
|
||||
this.$el.prepend( this.tool_panel_view.$el );
|
||||
if (this.tool_panel.get("layout").size() > 0) {
|
||||
this.$el.prepend(this.tool_panel_view.$el);
|
||||
this.tool_panel_view.render();
|
||||
}
|
||||
// build the dom for the workflow portion of the tool menu
|
||||
// add internal workflow list
|
||||
self.$( '#internal-workflows' ).append( self._templateAllWorkflow({
|
||||
title : _l( 'All workflows' ),
|
||||
href : 'workflow'
|
||||
}));
|
||||
_.each( this.stored_workflow_menu_entries, function( menu_entry ){
|
||||
self.$( '#internal-workflows' ).append( self._templateTool({
|
||||
title : menu_entry.stored_workflow.name,
|
||||
href : 'workflow/run?id=' + menu_entry.encoded_stored_workflow_id
|
||||
}));
|
||||
self.$("#internal-workflows").append(
|
||||
self._templateAllWorkflow({
|
||||
title: _l("All workflows"),
|
||||
href: "workflows/list"
|
||||
})
|
||||
);
|
||||
_.each(this.stored_workflow_menu_entries, menu_entry => {
|
||||
self.$("#internal-workflows").append(
|
||||
self._templateWorkflowLink({
|
||||
title: menu_entry.stored_workflow.name,
|
||||
href: `workflows/run?id=${menu_entry.encoded_stored_workflow_id}`
|
||||
})
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
/** build a link to one tool */
|
||||
_templateTool: function( tool ) {
|
||||
_templateTool: function(tool) {
|
||||
return [
|
||||
'<div class="toolTitle">',
|
||||
'<a href="', this.root, tool.href, '" target="galaxy_main">', tool.title, '</a>',
|
||||
'</div>'
|
||||
].join('');
|
||||
'<a href="',
|
||||
this.root,
|
||||
tool.href,
|
||||
'" target="galaxy_main">',
|
||||
tool.title,
|
||||
"</a>",
|
||||
"</div>"
|
||||
].join("");
|
||||
},
|
||||
|
||||
/** build a link to 'All Workflows' */
|
||||
_templateAllWorkflow: function( tool ) {
|
||||
_templateAllWorkflow: function(tool) {
|
||||
return [
|
||||
'<div class="toolTitle">',
|
||||
// global
|
||||
'<a href="', Galaxy.root, tool.href, '">', tool.title, '</a>',
|
||||
'</div>'
|
||||
].join('');
|
||||
// global
|
||||
'<a href="',
|
||||
Galaxy.root,
|
||||
tool.href,
|
||||
'">',
|
||||
tool.title,
|
||||
"</a>",
|
||||
"</div>"
|
||||
].join("");
|
||||
},
|
||||
|
||||
/** build links to workflows in toolpanel */
|
||||
_templateWorkflowLink: function(wf) {
|
||||
return [
|
||||
'<div class="toolTitle">',
|
||||
`<a class="${wf.cls} " href="`,
|
||||
Galaxy.root,
|
||||
wf.href,
|
||||
'">',
|
||||
wf.title,
|
||||
"</a>",
|
||||
"</div>"
|
||||
].join("");
|
||||
},
|
||||
|
||||
/** override to include inital menu dom and workflow section */
|
||||
_template : function() {
|
||||
_template: function() {
|
||||
return [
|
||||
'<div class="toolMenuContainer">',
|
||||
'<div class="toolMenu" style="display: none">',
|
||||
'<div id="search-no-results" style="display: none; padding-top: 5px">',
|
||||
'<em><strong>', _l( 'Search did not match any tools.' ), '</strong></em>',
|
||||
'</div>',
|
||||
'</div>',
|
||||
'<div class="toolSectionPad"/>',
|
||||
'<div class="toolSectionPad"/>',
|
||||
'<div class="toolSectionTitle" id="title_XXinternalXXworkflow">',
|
||||
'<span>', _l( 'Workflows' ), '</span>',
|
||||
'</div>',
|
||||
'<div id="internal-workflows" class="toolSectionBody">',
|
||||
'<div class="toolSectionBg"/>',
|
||||
'</div>',
|
||||
'</div>'
|
||||
].join('');
|
||||
'<div class="toolMenu" style="display: none">',
|
||||
'<div id="search-no-results" style="display: none; padding-top: 5px">',
|
||||
"<em><strong>",
|
||||
_l("Search did not match any tools."),
|
||||
"</strong></em>",
|
||||
"</div>",
|
||||
"</div>",
|
||||
'<div class="toolSectionPad"/>',
|
||||
'<div class="toolSectionPad"/>',
|
||||
'<div class="toolSectionTitle" id="title_XXinternalXXworkflow">',
|
||||
"<span>",
|
||||
_l("Workflows"),
|
||||
"</span>",
|
||||
"</div>",
|
||||
'<div id="internal-workflows" class="toolSectionBody">',
|
||||
'<div class="toolSectionBg"/>',
|
||||
"</div>",
|
||||
"</div>"
|
||||
].join("");
|
||||
},
|
||||
|
||||
toString : function() { return 'toolPanel' }
|
||||
toString: function() {
|
||||
return "toolPanel";
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = ToolPanel;
|
||||
export default ToolPanel;
|
||||
|
||||
@@ -0,0 +1,202 @@
|
||||
<template>
|
||||
<div class="toolForm">
|
||||
<div class="toolFormTitle">
|
||||
Citations
|
||||
<button v-if="viewRender" v-on:click="toggleViewRender" type="button" class="btn btn-xs citations-to-bibtex" title="Show all in BibTeX format.">
|
||||
<i class="fa fa-pencil-square-o"></i>
|
||||
Show BibTeX
|
||||
</button>
|
||||
<button v-else type="button" v-on:click="toggleViewRender" class="btn btn-xs citations-to-formatted" title="Return to formatted citation list.">
|
||||
<i class="fa fa-times"></i>
|
||||
Hide BibTeX
|
||||
</button>
|
||||
</div>
|
||||
<div class="citations-bibtex toolFormBody citation-padding">
|
||||
<div v-if="source === 'histories'" class="infomessage">
|
||||
When writing up your analysis, remember to include all references that should be cited in order
|
||||
to completely describe your work. Also, please remember to <a href="https://galaxyproject.org/citing-galaxy">cite Galaxy</a>.
|
||||
</div>
|
||||
<span v-if="viewRender" class="citations-formatted">
|
||||
<p v-html="formattedReferences">
|
||||
</p>
|
||||
</span>
|
||||
<textarea v-else class="citations-bibtex-text">
|
||||
{{ content }}
|
||||
</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import axios from "axios";
|
||||
import * as bibtexParse from "libs/bibtexParse";
|
||||
import { convertLaTeX } from "latex-to-unicode-converter";
|
||||
import { stringifyLaTeX } from "latex-parser";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
source: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
viewRender: {
|
||||
type: Boolean,
|
||||
requried: false,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
citations: [],
|
||||
content: "",
|
||||
errors: []
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
formattedReferences: function() {
|
||||
return this.citations.reduce((a, b) => a.concat(`<p class="formatted-reference">${this.formattedReference(b)}</p>`), "");
|
||||
}
|
||||
},
|
||||
created: function() {
|
||||
axios
|
||||
.get(`${Galaxy.root}api/${this.source}/${this.id}/citations`)
|
||||
.then(response => {
|
||||
this.content = "";
|
||||
for (var rawCitation of response.data) {
|
||||
try {
|
||||
var citation = {
|
||||
fields: {},
|
||||
entryType: undefined
|
||||
};
|
||||
var parsed = bibtexParse.toJSON(rawCitation.content);
|
||||
if (parsed) {
|
||||
parsed = _.first(parsed);
|
||||
citation.entryType = parsed.entryType || undefined;
|
||||
for (var key in parsed.entryTags) {
|
||||
citation.fields[key.toLowerCase()] = parsed.entryTags[key];
|
||||
}
|
||||
}
|
||||
this.citations.push(citation);
|
||||
this.content += rawCitation.content;
|
||||
} catch (err) {
|
||||
console.warn("Error parsing bibtex: " + err);
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
console.error(e);
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
formattedReference: function(citation) {
|
||||
var entryType = citation.entryType;
|
||||
var fields = citation.fields;
|
||||
|
||||
var ref = "";
|
||||
var authorsAndYear = `${this._asSentence(
|
||||
(fields.author ? fields.author : "") + (fields.year ? ` (${fields.year})` : "")
|
||||
)} `;
|
||||
var title = fields.title || "";
|
||||
var pages = fields.pages ? `pp. ${fields.pages}` : "";
|
||||
var address = fields.address;
|
||||
if (entryType == "article") {
|
||||
var volume =
|
||||
(fields.volume ? fields.volume : "") +
|
||||
(fields.number ? ` (${fields.number})` : "") +
|
||||
(pages ? `, ${pages}` : "");
|
||||
ref = `${authorsAndYear +
|
||||
this._asSentence(title) +
|
||||
(fields.journal ? `In <em>${fields.journal}, ` : "") +
|
||||
this._asSentence(volume) +
|
||||
this._asSentence(fields.address)}</em>`;
|
||||
} else if (entryType == "inproceedings" || entryType == "proceedings") {
|
||||
ref = `${authorsAndYear +
|
||||
this._asSentence(title) +
|
||||
(fields.booktitle ? `In <em>${fields.booktitle}, ` : "") +
|
||||
(pages ? pages : "") +
|
||||
(address ? `, ${address}` : "")}.</em>`;
|
||||
} else if (entryType == "mastersthesis" || entryType == "phdthesis") {
|
||||
ref =
|
||||
authorsAndYear +
|
||||
this._asSentence(title) +
|
||||
(fields.howpublished ? `${fields.howpublished}. ` : "") +
|
||||
(fields.note ? `${fields.note}.` : "");
|
||||
} else if (entryType == "techreport") {
|
||||
ref =
|
||||
authorsAndYear +
|
||||
this._asSentence(title) +
|
||||
this._asSentence(fields.institution) +
|
||||
this._asSentence(fields.number) +
|
||||
this._asSentence(fields.type);
|
||||
} else if (entryType == "book" || entryType == "inbook" || entryType == "incollection") {
|
||||
ref = `${authorsAndYear} ${this._formatBookInfo(fields)}`;
|
||||
} else {
|
||||
ref = `${authorsAndYear} ${this._asSentence(title)}${this._asSentence(
|
||||
fields.howpublished
|
||||
)}${this._asSentence(fields.note)}`;
|
||||
}
|
||||
var doiUrl = "";
|
||||
if (fields.doi) {
|
||||
doiUrl = `http://dx.doi.org/${fields.doi}`;
|
||||
ref += `[<a href="${doiUrl}" target="_blank">doi:${fields.doi}</a>]`;
|
||||
}
|
||||
var url = fields.url || doiUrl;
|
||||
if (url) {
|
||||
ref += `[<a href="${url}" target="_blank">Link</a>]`;
|
||||
}
|
||||
return convertLaTeX({ onError: (error, latex) => `{${stringifyLaTeX(latex)}}` }, ref);
|
||||
},
|
||||
_formatBookInfo: function(fields) {
|
||||
var info = "";
|
||||
if (fields.chapter) {
|
||||
info += `${fields.chapter} in `;
|
||||
}
|
||||
if (fields.title) {
|
||||
info += `<em>${fields.title}</em>`;
|
||||
}
|
||||
if (fields.editor) {
|
||||
info += `, Edited by ${fields.editor}, `;
|
||||
}
|
||||
if (fields.publisher) {
|
||||
info += `, ${fields.publisher}`;
|
||||
}
|
||||
if (fields.pages) {
|
||||
info += `, pp. ${fields.pages}`;
|
||||
}
|
||||
if (fields.series) {
|
||||
info += `, <em>${fields.series}</em>`;
|
||||
}
|
||||
if (fields.volume) {
|
||||
info += `, Vol.${fields.volume}`;
|
||||
}
|
||||
if (fields.issn) {
|
||||
info += `, ISBN: ${fields.issn}`;
|
||||
}
|
||||
return `${info}.`;
|
||||
},
|
||||
_asSentence: function(str) {
|
||||
return str && str.trim() ? `${str}. ` : "";
|
||||
},
|
||||
toggleViewRender: function() {
|
||||
this.viewRender = !this.viewRender;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
.citations-formatted{
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.citations-bibtex-text{
|
||||
width: 100%;
|
||||
height: 500px;
|
||||
}
|
||||
|
||||
.citation-padding{
|
||||
padding:5px 10px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<div class="ui-portlet-limited">
|
||||
<div class="portlet-header">
|
||||
<div class="portlet-title">
|
||||
<i class="portlet-title-icon fa fa-upload"></i>
|
||||
<span class="portlet-title-text"><b>Import a History from an Archive</b></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="portlet-content">
|
||||
<div v-if="errormessage" class="ui-message alert alert-danger">
|
||||
{{ errormessage }}
|
||||
</div>
|
||||
<div class="portlet-body">
|
||||
<form ref="form">
|
||||
<div class="ui-form-element">
|
||||
<div class="ui-form-title">Archived History URL</div>
|
||||
<input class="ui-input" type="text" name="archive_source"/>
|
||||
</div>
|
||||
<div class="ui-form-element">
|
||||
<div class="ui-form-title">Archived History file</div>
|
||||
<input type="file" name="archive_file"/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="portlet-buttons">
|
||||
<input class="btn btn-primary" type="button" value="Import History" @click="submit"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
errormessage: null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
submit: function() {
|
||||
$.ajax({
|
||||
url: `${Galaxy.root}api/histories`,
|
||||
data: new FormData(this.$refs.form),
|
||||
cache: false,
|
||||
contentType: false,
|
||||
processData: false,
|
||||
method: "POST"
|
||||
})
|
||||
.done(response => {
|
||||
window.location = `${Galaxy.root}histories/list?message=${response.message}&status=success`
|
||||
})
|
||||
.fail(response => {
|
||||
let message = response.responseJSON && response.responseJSON.err_msg;
|
||||
this.errormessage = message || "Import failed for unkown reason.";
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
.ui-message {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<div id="form-userkeys" class="toolForm" v-cloak>
|
||||
<div class="toolFormTitle">User Information</div>
|
||||
<div v-if="users && users.length > 0">
|
||||
<table class="grid">
|
||||
<thead><th>Encoded UID</th><th>Email</th><th>API Key</th><th>Actions</th></thead>
|
||||
<tbody>
|
||||
<tr v-for="user in users">
|
||||
<td>
|
||||
{{ user.uid }}
|
||||
</td>
|
||||
<td>
|
||||
{{ user.email }}
|
||||
</td>
|
||||
<td>
|
||||
{{ user.key }}
|
||||
</td>
|
||||
<td>
|
||||
<input type="button" value="Generate a new key now" v-on:click="generateKey( user.uid )" />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div>No user information available</div>
|
||||
</div>
|
||||
<div style="clear: both"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
// Should really do something with errors here. Eventually.
|
||||
users: [],
|
||||
errors: []
|
||||
};
|
||||
},
|
||||
created: function() {
|
||||
axios
|
||||
.get(`${Galaxy.root}userskeys/all_users`)
|
||||
.then(response => {
|
||||
this.users = response.data;
|
||||
})
|
||||
.catch(e => {
|
||||
this.errors.push(e);
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
generateKey: function(id) {
|
||||
axios
|
||||
.get(`${Galaxy.root}userskeys/admin_api_keys`, { params: { uid: id } })
|
||||
.then(response => {
|
||||
this.users = response.data;
|
||||
})
|
||||
.catch(e => {
|
||||
this.errors.push(e);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
</style>
|
||||
@@ -1,97 +1,175 @@
|
||||
/**
|
||||
* Internal function to remove content from the main area and add the notebook.
|
||||
* Not idempotent
|
||||
* TODO: This isn't just internal, some notebooks are calling it?
|
||||
*/
|
||||
function append_notebook(url){
|
||||
export function append_notebook(url) {
|
||||
clear_main_area();
|
||||
$('#main').append('<iframe frameBorder="0" seamless="seamless" style="width: 100%; height: 100%; overflow:hidden;" scrolling="no" src="'+ url +'"></iframe>'
|
||||
$("#main").append(
|
||||
`<iframe frameBorder="0" seamless="seamless" style="width: 100%; height: 100%; overflow:hidden;" scrolling="no" src="${
|
||||
url
|
||||
}"></iframe>`
|
||||
);
|
||||
}
|
||||
|
||||
function clear_main_area(){
|
||||
$('#spinner').remove();
|
||||
$('#main').children().remove();
|
||||
export function clear_main_area() {
|
||||
$("#spinner").remove();
|
||||
$("#main")
|
||||
.children()
|
||||
.remove();
|
||||
}
|
||||
|
||||
function display_spinner(){
|
||||
$('#main').append('<img id="spinner" src="' + galaxy_root + 'static/style/largespinner.gif" style="position:absolute;margin:auto;top:0;left:0;right:0;bottom:0;">');
|
||||
export function display_spinner() {
|
||||
$("#main").append(
|
||||
`<img id="spinner" src="${
|
||||
galaxy_root
|
||||
}static/style/largespinner.gif" style="position:absolute;margin:auto;top:0;left:0;right:0;bottom:0;">`
|
||||
);
|
||||
}
|
||||
|
||||
function not_ready(timeout, timeout_values, timeout_time_max, timeout_time_step) {
|
||||
if(timeout_values.count == 0){
|
||||
display_spinner();
|
||||
toastr.info(
|
||||
"Galaxy is launching a container in which to run this interactive environment. Please wait...",
|
||||
{'closeButton': true, 'tapToDismiss': false}
|
||||
/* Create a spin_state object used by spin() and spin_again() */
|
||||
export function make_spin_state(
|
||||
type,
|
||||
ajax_timeout_init,
|
||||
ajax_timeout_max,
|
||||
ajax_timeout_step,
|
||||
sleep_init,
|
||||
sleep_max,
|
||||
sleep_step,
|
||||
log_attempts
|
||||
) {
|
||||
var s = {
|
||||
type: typeof type !== "undefined" ? type : "GIE spin",
|
||||
ajax_timeout: typeof ajax_timeout_init !== "undefined" ? ajax_timeout_init : 2000,
|
||||
ajax_timeout_max: typeof ajax_timeout_max !== "undefined" ? ajax_timeout_max : 16000,
|
||||
ajax_timeout_step: typeof ajax_timeout_step !== "undefined" ? ajax_timeout_step : 500,
|
||||
sleep: typeof sleep_init !== "undefined" ? sleep_init : 500,
|
||||
sleep_max: typeof sleep_max !== "undefined" ? sleep_max : 8000,
|
||||
sleep_step: typeof sleep_step !== "undefined" ? sleep_step : 100,
|
||||
log_attempts: typeof log_attempts !== "undefined" ? log_attempts : true,
|
||||
count: 0
|
||||
};
|
||||
return s;
|
||||
}
|
||||
|
||||
/* Log/display an error when spinning fails. */
|
||||
export function spin_error(console_msg, user_msg, clear) {
|
||||
console.log(console_msg);
|
||||
if (clear) clear_main_area();
|
||||
if (typeof user_msg == "string") {
|
||||
toastr.clear();
|
||||
toastr.error(user_msg, "Error", {
|
||||
closeButton: true,
|
||||
timeOut: 0,
|
||||
extendedTimeOut: 0,
|
||||
tapToDismiss: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/* Increase sleep time and spin again. */
|
||||
function spin_again(spin_state) {
|
||||
if (spin_state.sleep < spin_state.sleep_max) {
|
||||
spin_state.sleep += spin_state.sleep_step;
|
||||
}
|
||||
if (spin_state.log_attempts) {
|
||||
console.log(
|
||||
`${spin_state.type} request ${spin_state.count} request timeout ${
|
||||
spin_state.ajax_timeout
|
||||
}ms sleeping ${spin_state.sleep / 1000}s`
|
||||
);
|
||||
}
|
||||
timeout_values.count++;
|
||||
if(timeout_values.time < timeout_time_max){
|
||||
timeout_values.time += timeout_time_step;
|
||||
}
|
||||
console.log("Readiness request " + timeout_values.count + " sleeping " + timeout_values.time / 1000 + "s");
|
||||
window.setTimeout(timeout, timeout_values.time)
|
||||
window.setTimeout(spin_state.spinner, spin_state.sleep);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check a URL for a boolean true/false and call a callback when done.
|
||||
/*
|
||||
* Spin on a URL as long as it times out, otherwise, call the provided success or error callback. If the callback
|
||||
* returns `true`, the condition is considered "resolved" and spinning stops. Otherwise, continue spinning, increasing
|
||||
* AJAX timeouts and/or sleep values as configured in the spin_state.
|
||||
*/
|
||||
function load_when_ready(url, success_callback){
|
||||
var ajax_timeout = 500;
|
||||
var ajax_timeout_max = 10000;
|
||||
var ajax_timeout_step = 250;
|
||||
var timeout_values = { time: 1000, count: 0 };
|
||||
var timeout_time_max = 15000;
|
||||
var timeout_time_step = 1000;
|
||||
var timeout = function(){
|
||||
$.ajax({
|
||||
export function spin(url, bool_response, success_callback, timeout_callback, error_callback, spin_state) {
|
||||
var spinner = () => {
|
||||
var ajax_params = {
|
||||
url: url,
|
||||
xhrFields: {
|
||||
withCredentials: true
|
||||
},
|
||||
type: "GET",
|
||||
timeout: ajax_timeout,
|
||||
dataType: "json",
|
||||
success: function(data){
|
||||
if(data == true){
|
||||
console.log("Galaxy reports IE container ready, returning");
|
||||
clear_main_area();
|
||||
toastr.clear();
|
||||
success_callback();
|
||||
}else if(data == false){
|
||||
not_ready(timeout, timeout_values, timeout_time_max, timeout_time_step);
|
||||
}else{
|
||||
clear_main_area();
|
||||
toastr.clear();
|
||||
toastr.error(
|
||||
"Galaxy failed to launch a container in which to run this interactive environment, contact your administrator.",
|
||||
"Error",
|
||||
{'closeButton': true, 'tapToDismiss': false}
|
||||
);
|
||||
timeout: spin_state.ajax_timeout,
|
||||
success: function(data, status, jqxhr) {
|
||||
if (!success_callback(data, status, jqxhr)) {
|
||||
spin_state.count++;
|
||||
spin_again(spin_state);
|
||||
}
|
||||
},
|
||||
error: function(jqXHR, textStatus, errorThrown){
|
||||
if(textStatus == "timeout"){
|
||||
if(ajax_timeout < ajax_timeout_max){
|
||||
ajax_timeout += ajax_timeout_step;
|
||||
error: function(jqxhr, status, error) {
|
||||
if (status == "timeout") {
|
||||
if (spin_state.ajax_timeout < spin_state.ajax_timeout_max) {
|
||||
spin_state.ajax_timeout += spin_state.ajax_timeout_step;
|
||||
}
|
||||
not_ready(timeout, timeout_values, timeout_time_max, timeout_time_step);
|
||||
}else{
|
||||
clear_main_area();
|
||||
toastr.clear();
|
||||
toastr.error(
|
||||
"Galaxy encountered an error while attempting to determine the readiness of this interactive environment, contact your administrator.",
|
||||
"Error",
|
||||
{'closeButton': true, 'tapToDismiss': false}
|
||||
);
|
||||
spin_state.count++;
|
||||
if (!timeout_callback(jqxhr, status, error)) spin_again(spin_state);
|
||||
} else {
|
||||
spin_state.count++;
|
||||
if (!error_callback(jqxhr, status, error)) spin_again(spin_state);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
window.setTimeout(timeout, timeout_values.time);
|
||||
};
|
||||
if (bool_response) ajax_params["dataType"] = "json";
|
||||
$.ajax(ajax_params);
|
||||
};
|
||||
console.log(`Setting up new spinner for ${spin_state.type} on ${url}`);
|
||||
spin_state.spinner = spinner;
|
||||
window.setTimeout(spinner, spin_state.sleep);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Spin on a URL forever until there is an acceptable response.
|
||||
* @param {String} url: URL to test response of. Must return a 200 (302->200 is OK).
|
||||
* @param {Boolean} bool_response: If set to `true`, do not stop spinning until the response is `true`. Otherwise, stop
|
||||
* as soon as a successful response is received.
|
||||
*/
|
||||
function spin_until(url, bool_response, messages, success_callback, spin_state) {
|
||||
var warn_at = 40; // ~2 mins
|
||||
var message_once = (message, spin_state) => {
|
||||
if (spin_state.count == 1) {
|
||||
display_spinner();
|
||||
toastr.info(message, null, {
|
||||
closeButton: true,
|
||||
timeOut: 0,
|
||||
extendedTimeOut: 0,
|
||||
tapToDismiss: false
|
||||
});
|
||||
}
|
||||
};
|
||||
var wrapped_success = data => {
|
||||
if (!bool_response || (bool_response && data == true)) {
|
||||
console.log(messages["success"]);
|
||||
clear_main_area();
|
||||
toastr.clear();
|
||||
success_callback();
|
||||
} else if (bool_response && data == false) {
|
||||
message_once(messages["not_ready"], spin_state);
|
||||
return false; // keep spinning
|
||||
} else {
|
||||
spin_error(`Invalid response to ${spin_state.type} request`, messages["invalid_response"], true);
|
||||
}
|
||||
return true; // stop spinning
|
||||
};
|
||||
var timeout_error = (jqxhr, status, error) => {
|
||||
message_once(messages["waiting"], spin_state);
|
||||
if (spin_state.count == warn_at) {
|
||||
toastr.warning(messages["wait_warn"], "Warning", {
|
||||
closeButton: true,
|
||||
timeOut: 0,
|
||||
extendedTimeOut: 0,
|
||||
tapToDismiss: false
|
||||
});
|
||||
}
|
||||
return false; // keep spinning
|
||||
};
|
||||
spin(url, bool_response, wrapped_success, timeout_error, timeout_error, spin_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test availability of a URL, and call a callback when done.
|
||||
@@ -100,35 +178,38 @@ function load_when_ready(url, success_callback){
|
||||
* @param {String} callback: function to call once successfully connected.
|
||||
*
|
||||
*/
|
||||
function test_ie_availability(url, success_callback){
|
||||
var request_count = 0;
|
||||
display_spinner();
|
||||
interval = setInterval(function(){
|
||||
$.ajax({
|
||||
url: url,
|
||||
xhrFields: {
|
||||
withCredentials: true
|
||||
},
|
||||
type: "GET",
|
||||
timeout: 500,
|
||||
success: function(){
|
||||
console.log("Connected to IE, returning");
|
||||
clearInterval(interval);
|
||||
success_callback();
|
||||
},
|
||||
error: function(jqxhr, status, error){
|
||||
request_count++;
|
||||
console.log("Availability request " + request_count);
|
||||
if(request_count > 30){
|
||||
clearInterval(interval);
|
||||
clear_main_area();
|
||||
toastr.error(
|
||||
"Could not connect to IE, contact your administrator",
|
||||
"Error",
|
||||
{'closeButton': true, 'timeOut': 20000, 'tapToDismiss': false}
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}, 1000);
|
||||
export function test_ie_availability(url, success_callback) {
|
||||
var messages = {
|
||||
success: "IE connection succeeded, returning",
|
||||
waiting: "Interactive environment container is running, attempting to connect to the IE. Please wait...",
|
||||
wait_warn:
|
||||
"It is taking an usually long time to connect to the interactive environment. Attempts will continue but you may want to report this condition to a Galaxy administrator if it does not succeed soon.",
|
||||
error:
|
||||
"An error was encountered while attempting to connect to the interactive environment, contact your administrator."
|
||||
};
|
||||
var spin_state = make_spin_state("IE availability");
|
||||
spin_until(url, false, messages, success_callback, spin_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test a boolean (json) response from a URL, and call a callback when done.
|
||||
* http://stackoverflow.com/q/25390206/347368
|
||||
* @param {String} url: URL to test response of. Must return a 200 (302->200 is OK) and either `true` or `false`.
|
||||
* @param {String} callback: function to call once successfully connected.
|
||||
*
|
||||
*/
|
||||
export function load_when_ready(url, success_callback) {
|
||||
var messages = {
|
||||
success: "Galaxy reports IE container ready, returning",
|
||||
not_ready: "Galaxy is launching a container in which to run this interactive environment. Please wait...",
|
||||
unknown_response:
|
||||
"Galaxy failed to launch a container in which to run this interactive environment, contact a Galaxy administrator.",
|
||||
waiting: "Galaxy is launching a container in which to run this interactive environment. Please wait...",
|
||||
wait_warn:
|
||||
"It is taking an usually long time to start a container. Attempts will continue but you may want to report this condition to a Galaxy administrator if it does not succeed soon.",
|
||||
error:
|
||||
"Galaxy encountered an error while attempting to determine the readiness of this interactive environment's container, contact a Galaxy administrator."
|
||||
};
|
||||
var spin_state = make_spin_state("IE container readiness");
|
||||
spin_until(url, true, messages, success_callback, spin_state);
|
||||
}
|
||||
|
||||
+104
-98
@@ -1,13 +1,10 @@
|
||||
define([
|
||||
'libs/underscore',
|
||||
'libs/backbone',
|
||||
'mvc/base-mvc',
|
||||
'mvc/user/user-model',
|
||||
'utils/metrics-logger',
|
||||
'utils/add-logging',
|
||||
'utils/localization'
|
||||
], function( _, Backbone, BASE_MVC, userModel, metricsLogger, addLogging, localize ){
|
||||
|
||||
import * as _ from "libs/underscore";
|
||||
import * as Backbone from "libs/backbone";
|
||||
import BASE_MVC from "mvc/base-mvc";
|
||||
import userModel from "mvc/user/user-model";
|
||||
import metricsLogger from "utils/metrics-logger";
|
||||
import addLogging from "utils/add-logging";
|
||||
import localize from "utils/localization";
|
||||
// TODO: move into a singleton pattern and have dependents import Galaxy
|
||||
// ============================================================================
|
||||
/** Base galaxy client-side application.
|
||||
@@ -18,57 +15,63 @@ define([
|
||||
* galaxy.ini available from the configuration API)
|
||||
* user : the current user (as a mvc/user/user-model)
|
||||
*/
|
||||
function GalaxyApp( options, bootstrapped ){
|
||||
function GalaxyApp(options, bootstrapped) {
|
||||
var self = this;
|
||||
return self._init( options || {}, bootstrapped || {} );
|
||||
return self._init(options || {}, bootstrapped || {});
|
||||
}
|
||||
|
||||
// add logging shortcuts for this object
|
||||
addLogging( GalaxyApp, 'GalaxyApp' );
|
||||
addLogging(GalaxyApp, "GalaxyApp");
|
||||
|
||||
// a debug flag can be set via local storage and made available during script/page loading
|
||||
var DEBUGGING_KEY = 'galaxy:debug',
|
||||
NAMESPACE_KEY = DEBUGGING_KEY + ':namespaces',
|
||||
localDebugging = false;
|
||||
var DEBUGGING_KEY = "galaxy:debug";
|
||||
|
||||
var NAMESPACE_KEY = `${DEBUGGING_KEY}:namespaces`;
|
||||
var FLATTEN_LOG_MESSAGES_KEY = `${DEBUGGING_KEY}:flatten`;
|
||||
|
||||
var localDebugging = false;
|
||||
try {
|
||||
localDebugging = localStorage.getItem( DEBUGGING_KEY ) == 'true';
|
||||
} catch( storageErr ){
|
||||
console.log( localize( 'localStorage not available for debug flag retrieval' ) );
|
||||
localDebugging = localStorage.getItem(DEBUGGING_KEY) == "true";
|
||||
} catch (storageErr) {
|
||||
console.log(localize("localStorage not available for debug flag retrieval"));
|
||||
}
|
||||
|
||||
/** initalize options and sub-components */
|
||||
GalaxyApp.prototype._init = function __init( options, bootstrapped ){
|
||||
GalaxyApp.prototype._init = function __init(options, bootstrapped) {
|
||||
var self = this;
|
||||
_.extend( self, Backbone.Events );
|
||||
if( localDebugging ){
|
||||
_.extend(self, Backbone.Events);
|
||||
if (localDebugging) {
|
||||
self.logger = console;
|
||||
console.debug( 'debugging galaxy:', 'options:', options, 'bootstrapped:', bootstrapped );
|
||||
console.debug("debugging galaxy:", "options:", options, "bootstrapped:", bootstrapped);
|
||||
}
|
||||
|
||||
self._processOptions( options );
|
||||
// special case for root
|
||||
self.root = options.root || '/';
|
||||
self._processOptions(options);
|
||||
|
||||
self._initConfig( options.config || {} );
|
||||
self._patchGalaxy( window.Galaxy );
|
||||
// add root and url parameters
|
||||
self.root = options.root || "/";
|
||||
self.params = options.params || {};
|
||||
self.session_csrf_token = options.session_csrf_token || null;
|
||||
|
||||
self._initLogger( self.options.loggerOptions || {} );
|
||||
self._initConfig(options.config || {});
|
||||
self._patchGalaxy(window.Galaxy);
|
||||
|
||||
self._initLogger(self.options.loggerOptions || {});
|
||||
// at this point, either logging or not and namespaces are enabled - chat it up
|
||||
self.debug( 'GalaxyApp.options: ', self.options );
|
||||
self.debug( 'GalaxyApp.config: ', self.config );
|
||||
self.debug( 'GalaxyApp.logger: ', self.logger );
|
||||
self.debug("GalaxyApp.options: ", self.options);
|
||||
self.debug("GalaxyApp.config: ", self.config);
|
||||
self.debug("GalaxyApp.logger: ", self.logger);
|
||||
|
||||
self._initLocale();
|
||||
self.debug( 'GalaxyApp.localize: ', self.localize );
|
||||
self.debug("GalaxyApp.localize: ", self.localize);
|
||||
|
||||
self.config = options.config || {};
|
||||
self.debug( 'GalaxyApp.config: ', self.config );
|
||||
self.debug("GalaxyApp.config: ", self.config);
|
||||
|
||||
self._initUser( options.user || {} );
|
||||
self.debug( 'GalaxyApp.user: ', self.user );
|
||||
self._initUser(options.user || {});
|
||||
self.debug("GalaxyApp.user: ", self.user);
|
||||
|
||||
self._setUpListeners();
|
||||
self.trigger( 'ready', self );
|
||||
self.trigger("ready", self);
|
||||
|
||||
return self;
|
||||
};
|
||||
@@ -76,27 +79,28 @@ GalaxyApp.prototype._init = function __init( options, bootstrapped ){
|
||||
/** default options */
|
||||
GalaxyApp.prototype.defaultOptions = {
|
||||
/** monkey patch attributes from existing window.Galaxy object? */
|
||||
patchExisting : true,
|
||||
patchExisting: true,
|
||||
/** root url of this app */
|
||||
root : '/'
|
||||
root: "/",
|
||||
session_csrf_token: null
|
||||
};
|
||||
|
||||
/** filter to options present in defaultOptions (and default to them) */
|
||||
GalaxyApp.prototype._processOptions = function _processOptions( options ){
|
||||
var self = this,
|
||||
defaults = self.defaultOptions;
|
||||
GalaxyApp.prototype._processOptions = function _processOptions(options) {
|
||||
var self = this;
|
||||
var defaults = self.defaultOptions;
|
||||
|
||||
self.options = {};
|
||||
for( var k in defaults ){
|
||||
if( defaults.hasOwnProperty( k ) ){
|
||||
self.options[ k ] = ( options.hasOwnProperty( k ) )?( options[ k ] ):( defaults[ k ] );
|
||||
for (var k in defaults) {
|
||||
if (defaults.hasOwnProperty(k)) {
|
||||
self.options[k] = options.hasOwnProperty(k) ? options[k] : defaults[k];
|
||||
}
|
||||
}
|
||||
return self;
|
||||
};
|
||||
|
||||
/** parse the config and any extra info derived from it */
|
||||
GalaxyApp.prototype._initConfig = function _initConfig( config ){
|
||||
GalaxyApp.prototype._initConfig = function _initConfig(config) {
|
||||
var self = this;
|
||||
self.config = config;
|
||||
|
||||
@@ -107,45 +111,49 @@ GalaxyApp.prototype._initConfig = function _initConfig( config ){
|
||||
};
|
||||
|
||||
/** add an option from options if the key matches an option in defaultOptions */
|
||||
GalaxyApp.prototype._patchGalaxy = function _patchGalaxy( patchWith ){
|
||||
GalaxyApp.prototype._patchGalaxy = function _patchGalaxy(patchWith) {
|
||||
var self = this;
|
||||
// in case req or plain script tag order has created a prev. version of the Galaxy obj...
|
||||
if( self.options.patchExisting && patchWith ){
|
||||
if (self.options.patchExisting && patchWith) {
|
||||
// self.debug( 'found existing Galaxy object:', patchWith );
|
||||
// ...(for now) monkey patch any added attributes that the previous Galaxy may have had
|
||||
//TODO: move those attributes to more formal assignment in GalaxyApp
|
||||
for( var k in patchWith ){
|
||||
if( patchWith.hasOwnProperty( k ) ){
|
||||
for (var k in patchWith) {
|
||||
if (patchWith.hasOwnProperty(k)) {
|
||||
// self.debug( '\t patching in ' + k + ' to Galaxy:', self[ k ] );
|
||||
self[ k ] = patchWith[ k ];
|
||||
self[k] = patchWith[k];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** set up the metrics logger (utils/metrics-logger) and pass loggerOptions */
|
||||
GalaxyApp.prototype._initLogger = function _initLogger( loggerOptions ){
|
||||
GalaxyApp.prototype._initLogger = function _initLogger(loggerOptions) {
|
||||
var self = this;
|
||||
|
||||
// default to console logging at the debug level if the debug flag is set
|
||||
if( self.config.debug ){
|
||||
if (self.config.debug) {
|
||||
loggerOptions.consoleLogger = loggerOptions.consoleLogger || console;
|
||||
loggerOptions.consoleLevel = loggerOptions.consoleLevel || metricsLogger.MetricsLogger.ALL;
|
||||
// load any logging namespaces from localStorage if we can
|
||||
try {
|
||||
loggerOptions.consoleNamespaceWhitelist = localStorage.getItem( NAMESPACE_KEY ).split( ',' );
|
||||
} catch( storageErr ){}
|
||||
loggerOptions.consoleNamespaceWhitelist = localStorage.getItem(NAMESPACE_KEY).split(",");
|
||||
} catch (storageErr) {}
|
||||
try {
|
||||
loggerOptions.consoleFlattenMessages = localStorage.getItem(FLATTEN_LOG_MESSAGES_KEY) == "true";
|
||||
} catch (storageErr) {}
|
||||
console.log(loggerOptions.consoleFlattenMessages);
|
||||
}
|
||||
|
||||
self.logger = new metricsLogger.MetricsLogger( loggerOptions );
|
||||
self.logger = new metricsLogger.MetricsLogger(loggerOptions);
|
||||
self.emit = {};
|
||||
[ 'log', 'debug', 'info', 'warn', 'error', 'metric' ].map(function( i ) {
|
||||
self.emit[ i ] = function( data ){
|
||||
self.logger.emit( i, arguments[ 0 ], Array.prototype.slice.call( arguments, 1 ) );
|
||||
["log", "debug", "info", "warn", "error", "metric"].map(i => {
|
||||
self.emit[i] = function(data) {
|
||||
self.logger.emit(i, arguments[0], Array.prototype.slice.call(arguments, 1));
|
||||
};
|
||||
});
|
||||
|
||||
if( self.config.debug ){
|
||||
if (self.config.debug) {
|
||||
// add this logger to mvc's loggable mixin so that all models can use the logger
|
||||
BASE_MVC.LoggableMixin.logger = self.logger;
|
||||
}
|
||||
@@ -153,9 +161,9 @@ GalaxyApp.prototype._initLogger = function _initLogger( loggerOptions ){
|
||||
};
|
||||
|
||||
/** add the localize fn to this object and the window namespace (as '_l') */
|
||||
GalaxyApp.prototype._initLocale = function _initLocale( options ){
|
||||
GalaxyApp.prototype._initLocale = function _initLocale(options) {
|
||||
var self = this;
|
||||
self.debug( '_initLocale:', options );
|
||||
self.debug("_initLocale:", options);
|
||||
self.localize = localize;
|
||||
// add to window as global shortened alias
|
||||
// TODO: temporary - remove when can require for plugins
|
||||
@@ -164,30 +172,30 @@ GalaxyApp.prototype._initLocale = function _initLocale( options ){
|
||||
};
|
||||
|
||||
/** set up the current user as a Backbone model (mvc/user/user-model) */
|
||||
GalaxyApp.prototype._initUser = function _initUser( userJSON ){
|
||||
GalaxyApp.prototype._initUser = function _initUser(userJSON) {
|
||||
var self = this;
|
||||
self.debug( '_initUser:', userJSON );
|
||||
self.user = new userModel.User( userJSON );
|
||||
self.debug("_initUser:", userJSON);
|
||||
self.user = new userModel.User(userJSON);
|
||||
self.user.logger = self.logger;
|
||||
return self;
|
||||
};
|
||||
|
||||
/** Set up DOM/jQuery/Backbone event listeners enabled for all pages */
|
||||
GalaxyApp.prototype._setUpListeners = function _setUpListeners(){
|
||||
GalaxyApp.prototype._setUpListeners = function _setUpListeners() {
|
||||
var self = this;
|
||||
|
||||
// hook to jq beforeSend to record the most recent ajax call and cache some data about it
|
||||
/** cached info about the last ajax call made through jQuery */
|
||||
self.lastAjax = {};
|
||||
$( document ).bind( 'ajaxSend', function( ev, xhr, options ){
|
||||
$(document).bind("ajaxSend", (ev, xhr, options) => {
|
||||
var data = options.data;
|
||||
try {
|
||||
data = JSON.parse( data );
|
||||
} catch( err ){}
|
||||
data = JSON.parse(data);
|
||||
} catch (err) {}
|
||||
|
||||
self.lastAjax = {
|
||||
url : location.href.slice( 0, -1 ) + options.url,
|
||||
data : data
|
||||
url: location.href.slice(0, -1) + options.url,
|
||||
data: data
|
||||
};
|
||||
//TODO:?? we might somehow manage to *retry* ajax using either this hook or Backbone.sync
|
||||
});
|
||||
@@ -195,23 +203,22 @@ GalaxyApp.prototype._setUpListeners = function _setUpListeners(){
|
||||
};
|
||||
|
||||
/** Turn debugging/console-output on/off by passing boolean. Pass nothing to get current setting. */
|
||||
GalaxyApp.prototype.debugging = function _debugging( setting ){
|
||||
GalaxyApp.prototype.debugging = function _debugging(setting) {
|
||||
var self = this;
|
||||
try {
|
||||
if( setting === undefined ){
|
||||
return localStorage.getItem( DEBUGGING_KEY ) === 'true';
|
||||
if (setting === undefined) {
|
||||
return localStorage.getItem(DEBUGGING_KEY) === "true";
|
||||
}
|
||||
if( setting ){
|
||||
localStorage.setItem( DEBUGGING_KEY, true );
|
||||
if (setting) {
|
||||
localStorage.setItem(DEBUGGING_KEY, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
localStorage.removeItem( DEBUGGING_KEY );
|
||||
localStorage.removeItem(DEBUGGING_KEY);
|
||||
// also remove all namespaces
|
||||
self.debuggingNamespaces( null );
|
||||
|
||||
} catch( storageErr ){
|
||||
console.log( localize( 'localStorage not available for debug flag retrieval' ) );
|
||||
self.debuggingNamespaces(null);
|
||||
} catch (storageErr) {
|
||||
console.log(localize("localStorage not available for debug flag retrieval"));
|
||||
}
|
||||
return false;
|
||||
};
|
||||
@@ -222,35 +229,34 @@ GalaxyApp.prototype.debugging = function _debugging( setting ){
|
||||
* Pass in an array of strings or single string of the namespaces to filter to.
|
||||
* Returns the new/current namespaces as an array;
|
||||
*/
|
||||
GalaxyApp.prototype.debuggingNamespaces = function _debuggingNamespaces( namespaces ){
|
||||
GalaxyApp.prototype.debuggingNamespaces = function _debuggingNamespaces(namespaces) {
|
||||
var self = this;
|
||||
try {
|
||||
if( namespaces === undefined ){
|
||||
var csv = localStorage.getItem( NAMESPACE_KEY );
|
||||
return typeof( csv ) === 'string'? csv.split( ',' ) : [];
|
||||
} else if( namespaces === null ) {
|
||||
localStorage.removeItem( NAMESPACE_KEY );
|
||||
if (namespaces === undefined) {
|
||||
var csv = localStorage.getItem(NAMESPACE_KEY);
|
||||
return typeof csv === "string" ? csv.split(",") : [];
|
||||
} else if (namespaces === null) {
|
||||
localStorage.removeItem(NAMESPACE_KEY);
|
||||
} else {
|
||||
localStorage.setItem( NAMESPACE_KEY, namespaces );
|
||||
localStorage.setItem(NAMESPACE_KEY, namespaces);
|
||||
}
|
||||
var newSettings = self.debuggingNamespaces();
|
||||
if( self.logger ){
|
||||
if (self.logger) {
|
||||
self.logger.options.consoleNamespaceWhitelist = newSettings;
|
||||
}
|
||||
return newSettings;
|
||||
} catch( storageErr ){
|
||||
console.log( localize( 'localStorage not available for debug namespace retrieval' ) );
|
||||
} catch (storageErr) {
|
||||
console.log(localize("localStorage not available for debug namespace retrieval"));
|
||||
}
|
||||
};
|
||||
|
||||
/** string rep */
|
||||
GalaxyApp.prototype.toString = function toString(){
|
||||
var userEmail = this.user? ( this.user.get( 'email' ) || '(anonymous)' ) : 'uninitialized';
|
||||
return 'GalaxyApp(' + userEmail + ')';
|
||||
GalaxyApp.prototype.toString = function toString() {
|
||||
var userEmail = this.user ? this.user.get("email") || "(anonymous)" : "uninitialized";
|
||||
return `GalaxyApp(${userEmail})`;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
return {
|
||||
GalaxyApp : GalaxyApp
|
||||
};
|
||||
});
|
||||
export default {
|
||||
GalaxyApp: GalaxyApp
|
||||
};
|
||||
|
||||
@@ -2,101 +2,90 @@
|
||||
// === MAIN GALAXY LIBRARY MODULE ====
|
||||
// MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
|
||||
|
||||
define([
|
||||
"layout/masthead",
|
||||
"utils/utils",
|
||||
"libs/toastr",
|
||||
"mvc/base-mvc",
|
||||
"mvc/library/library-model",
|
||||
"mvc/library/library-folderlist-view",
|
||||
"mvc/library/library-librarylist-view",
|
||||
"mvc/library/library-librarytoolbar-view",
|
||||
"mvc/library/library-foldertoolbar-view",
|
||||
"mvc/library/library-dataset-view",
|
||||
"mvc/library/library-library-view",
|
||||
"mvc/library/library-folder-view"
|
||||
],
|
||||
function(mod_masthead,
|
||||
mod_utils,
|
||||
mod_toastr,
|
||||
mod_baseMVC,
|
||||
mod_library_model,
|
||||
mod_folderlist_view,
|
||||
mod_librarylist_view,
|
||||
mod_librarytoolbar_view,
|
||||
mod_foldertoolbar_view,
|
||||
mod_library_dataset_view,
|
||||
mod_library_library_view,
|
||||
mod_library_folder_view
|
||||
) {
|
||||
|
||||
import mod_utils from "utils/utils";
|
||||
import mod_toastr from "libs/toastr";
|
||||
import mod_baseMVC from "mvc/base-mvc";
|
||||
import mod_library_model from "mvc/library/library-model";
|
||||
import mod_folderlist_view from "mvc/library/library-folderlist-view";
|
||||
import mod_librarylist_view from "mvc/library/library-librarylist-view";
|
||||
import mod_librarytoolbar_view from "mvc/library/library-librarytoolbar-view";
|
||||
import mod_foldertoolbar_view from "mvc/library/library-foldertoolbar-view";
|
||||
import mod_library_dataset_view from "mvc/library/library-dataset-view";
|
||||
import mod_library_library_view from "mvc/library/library-library-view";
|
||||
import mod_library_folder_view from "mvc/library/library-folder-view";
|
||||
// ============================================================================
|
||||
/**
|
||||
* The Data Libraries router. Takes care about triggering routes
|
||||
* and sends users to proper pieces of the application.
|
||||
*/
|
||||
var LibraryRouter = Backbone.Router.extend({
|
||||
initialize: function() {
|
||||
this.routesHit = 0;
|
||||
// keep count of number of routes handled by the application
|
||||
Backbone.history.on(
|
||||
"route",
|
||||
function() {
|
||||
this.routesHit++;
|
||||
},
|
||||
this
|
||||
);
|
||||
|
||||
initialize: function() {
|
||||
this.routesHit = 0;
|
||||
// keep count of number of routes handled by the application
|
||||
Backbone.history.on( 'route', function() { this.routesHit++; }, this );
|
||||
this.bind("route", this.trackPageview);
|
||||
},
|
||||
|
||||
this.bind( 'route', this.trackPageview );
|
||||
},
|
||||
routes: {
|
||||
"": "libraries",
|
||||
"page/:show_page": "libraries_page",
|
||||
"library/:library_id/permissions": "library_permissions",
|
||||
"folders/:folder_id/permissions": "folder_permissions",
|
||||
"folders/:id": "folder_content",
|
||||
"folders/:id/page/:show_page": "folder_page",
|
||||
"folders/:folder_id/datasets/:dataset_id": "dataset_detail",
|
||||
"folders/:folder_id/datasets/:dataset_id/permissions": "dataset_permissions",
|
||||
"folders/:folder_id/datasets/:dataset_id/versions/:ldda_id": "dataset_version",
|
||||
"folders/:folder_id/download/:format": "download",
|
||||
"folders/:folder_id/import/:source": "import_datasets"
|
||||
},
|
||||
|
||||
routes: {
|
||||
"" : "libraries",
|
||||
"page/:show_page" : "libraries_page",
|
||||
"library/:library_id/permissions" : "library_permissions",
|
||||
"folders/:folder_id/permissions" : "folder_permissions",
|
||||
"folders/:id" : "folder_content",
|
||||
"folders/:id/page/:show_page" : "folder_page",
|
||||
"folders/:folder_id/datasets/:dataset_id" : "dataset_detail",
|
||||
"folders/:folder_id/datasets/:dataset_id/permissions" : "dataset_permissions",
|
||||
"folders/:folder_id/datasets/:dataset_id/versions/:ldda_id" : "dataset_version",
|
||||
"folders/:folder_id/download/:format" : "download",
|
||||
"folders/:folder_id/import/:source" : "import_datasets"
|
||||
},
|
||||
/**
|
||||
* If more than one route has been hit the user did not land on current
|
||||
* page directly so we can go back safely. Otherwise go to the home page.
|
||||
* Use replaceState if available so the navigation doesn't create an
|
||||
* extra history entry
|
||||
*/
|
||||
back: function() {
|
||||
if (this.routesHit > 1) {
|
||||
window.history.back();
|
||||
} else {
|
||||
this.navigate("#", { trigger: true, replace: true });
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* If more than one route has been hit the user did not land on current
|
||||
* page directly so we can go back safely. Otherwise go to the home page.
|
||||
* Use replaceState if available so the navigation doesn't create an
|
||||
* extra history entry
|
||||
*/
|
||||
back: function() {
|
||||
if( this.routesHit > 1 ) {
|
||||
window.history.back();
|
||||
} else {
|
||||
this.navigate( '#', { trigger:true, replace:true } );
|
||||
/**
|
||||
* Track every route change as a page view in Google Analytics.
|
||||
*/
|
||||
trackPageview: function() {
|
||||
var url = Backbone.history.getFragment();
|
||||
//prepend slash
|
||||
if (!/^\//.test(url) && url != "") {
|
||||
url = `/${url}`;
|
||||
}
|
||||
if (typeof ga !== "undefined") {
|
||||
ga("send", "pageview", `${Galaxy.root}library/list${url}`);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Track every route change as a page view in Google Analytics.
|
||||
*/
|
||||
trackPageview: function () {
|
||||
var url = Backbone.history.getFragment();
|
||||
//prepend slash
|
||||
if (!/^\//.test(url) && url != "") {
|
||||
url = "/" + url;
|
||||
}
|
||||
if ( typeof ga !== 'undefined' ) {
|
||||
ga( 'send', 'pageview', Galaxy.root + 'library/list' + url );
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
/** session storage for library preferences */
|
||||
var LibraryPrefs = mod_baseMVC.SessionStorageModel.extend({
|
||||
defaults : {
|
||||
with_deleted : false,
|
||||
sort_order : 'asc',
|
||||
sort_by : 'name',
|
||||
library_page_size : 20,
|
||||
folder_page_size : 15
|
||||
defaults: {
|
||||
with_deleted: false,
|
||||
without_restricted: false,
|
||||
sort_order: "asc",
|
||||
sort_by: "name",
|
||||
library_page_size: 20,
|
||||
folder_page_size: 15
|
||||
}
|
||||
});
|
||||
|
||||
@@ -106,7 +95,6 @@ var LibraryPrefs = mod_baseMVC.SessionStorageModel.extend({
|
||||
* and defines what router should do on the route triggers.
|
||||
*/
|
||||
var GalaxyLibrary = Backbone.View.extend({
|
||||
|
||||
libraryToolbarView: null,
|
||||
libraryListView: null,
|
||||
library_router: null,
|
||||
@@ -115,119 +103,154 @@ var GalaxyLibrary = Backbone.View.extend({
|
||||
folderListView: null,
|
||||
datasetView: null,
|
||||
|
||||
initialize : function(){
|
||||
|
||||
// This should go upstream in the js app once available
|
||||
if ( window.Galaxy.config.ga_code ){
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
||||
ga('create', window.Galaxy.config.ga_code, 'auto');
|
||||
ga('send', 'pageview');
|
||||
}
|
||||
initialize: function() {
|
||||
// This should go upstream in the js app once available
|
||||
if (window.Galaxy.config.ga_code) {
|
||||
((i, s, o, g, r, a, m) => {
|
||||
i["GoogleAnalyticsObject"] = r;
|
||||
(i[r] =
|
||||
i[r] ||
|
||||
function() {
|
||||
(i[r].q = i[r].q || []).push(arguments);
|
||||
}),
|
||||
(i[r].l = 1 * new Date());
|
||||
(a = s.createElement(o)), (m = s.getElementsByTagName(o)[0]);
|
||||
a.async = 1;
|
||||
a.src = g;
|
||||
m.parentNode.insertBefore(a, m);
|
||||
})(window, document, "script", "//www.google-analytics.com/analytics.js", "ga");
|
||||
ga("create", window.Galaxy.config.ga_code, "auto");
|
||||
ga("send", "pageview");
|
||||
}
|
||||
|
||||
Galaxy.libraries = this;
|
||||
|
||||
this.preferences = new LibraryPrefs( { id: 'global-lib-prefs' } );
|
||||
this.preferences = new LibraryPrefs({ id: "global-lib-prefs" });
|
||||
|
||||
this.library_router = new LibraryRouter();
|
||||
|
||||
this.library_router.on( 'route:libraries', function() {
|
||||
if ( Galaxy.libraries.libraryToolbarView){
|
||||
Galaxy.libraries.libraryToolbarView.$el.unbind('click');
|
||||
this.library_router.on("route:libraries", () => {
|
||||
if (Galaxy.libraries.libraryToolbarView) {
|
||||
Galaxy.libraries.libraryToolbarView.$el.unbind("click");
|
||||
}
|
||||
Galaxy.libraries.libraryToolbarView = new mod_librarytoolbar_view.LibraryToolbarView();
|
||||
Galaxy.libraries.libraryListView = new mod_librarylist_view.LibraryListView();
|
||||
});
|
||||
|
||||
this.library_router.on('route:libraries_page', function( show_page ) {
|
||||
if ( Galaxy.libraries.libraryToolbarView === null ){
|
||||
this.library_router.on("route:libraries_page", show_page => {
|
||||
if (Galaxy.libraries.libraryToolbarView === null) {
|
||||
Galaxy.libraries.libraryToolbarView = new mod_librarytoolbar_view.LibraryToolbarView();
|
||||
Galaxy.libraries.libraryListView = new mod_librarylist_view.LibraryListView( { show_page: show_page } );
|
||||
Galaxy.libraries.libraryListView = new mod_librarylist_view.LibraryListView({ show_page: show_page });
|
||||
} else {
|
||||
Galaxy.libraries.libraryListView.render( { show_page: show_page } );
|
||||
Galaxy.libraries.libraryListView.render({
|
||||
show_page: show_page
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.library_router.on( 'route:folder_content', function( id ) {
|
||||
if (Galaxy.libraries.folderToolbarView){
|
||||
Galaxy.libraries.folderToolbarView.$el.unbind( 'click' );
|
||||
this.library_router.on("route:folder_content", id => {
|
||||
if (Galaxy.libraries.folderToolbarView) {
|
||||
Galaxy.libraries.folderToolbarView.$el.unbind("click");
|
||||
}
|
||||
Galaxy.libraries.folderToolbarView = new mod_foldertoolbar_view.FolderToolbarView( { id: id } );
|
||||
Galaxy.libraries.folderListView = new mod_folderlist_view.FolderListView( { id: id } );
|
||||
Galaxy.libraries.folderToolbarView = new mod_foldertoolbar_view.FolderToolbarView({ id: id });
|
||||
Galaxy.libraries.folderListView = new mod_folderlist_view.FolderListView({ id: id });
|
||||
});
|
||||
|
||||
this.library_router.on( 'route:folder_page', function( id, show_page ) {
|
||||
if ( Galaxy.libraries.folderToolbarView === null ){
|
||||
Galaxy.libraries.folderToolbarView = new mod_foldertoolbar_view.FolderToolbarView( {id: id} );
|
||||
Galaxy.libraries.folderListView = new mod_folderlist_view.FolderListView( { id: id, show_page: show_page } );
|
||||
this.library_router.on("route:folder_page", (id, show_page) => {
|
||||
if (Galaxy.libraries.folderToolbarView === null) {
|
||||
Galaxy.libraries.folderToolbarView = new mod_foldertoolbar_view.FolderToolbarView({ id: id });
|
||||
Galaxy.libraries.folderListView = new mod_folderlist_view.FolderListView({
|
||||
id: id,
|
||||
show_page: show_page
|
||||
});
|
||||
} else {
|
||||
Galaxy.libraries.folderListView.render( { id: id, show_page: parseInt( show_page ) } );
|
||||
Galaxy.libraries.folderListView.render({
|
||||
id: id,
|
||||
show_page: parseInt(show_page)
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.library_router.on( 'route:download', function( folder_id, format ) {
|
||||
if ( $( '#folder_list_body' ).find( ':checked' ).length === 0 ) {
|
||||
mod_toastr.info( 'You must select at least one dataset to download' );
|
||||
Galaxy.libraries.library_router.navigate( 'folders/' + folder_id, { trigger: true, replace: true } );
|
||||
this.library_router.on("route:download", (folder_id, format) => {
|
||||
if ($("#folder_list_body").find(":checked").length === 0) {
|
||||
mod_toastr.info("You must select at least one dataset to download");
|
||||
Galaxy.libraries.library_router.navigate(`folders/${folder_id}`, { trigger: true, replace: true });
|
||||
} else {
|
||||
Galaxy.libraries.folderToolbarView.download( folder_id, format );
|
||||
Galaxy.libraries.library_router.navigate( 'folders/' + folder_id, { trigger: false, replace: true } );
|
||||
Galaxy.libraries.folderToolbarView.download(folder_id, format);
|
||||
Galaxy.libraries.library_router.navigate(`folders/${folder_id}`, { trigger: false, replace: true });
|
||||
}
|
||||
});
|
||||
|
||||
this.library_router.on( 'route:dataset_detail', function(folder_id, dataset_id){
|
||||
if (Galaxy.libraries.datasetView){
|
||||
Galaxy.libraries.datasetView.$el.unbind('click');
|
||||
this.library_router.on("route:dataset_detail", (folder_id, dataset_id) => {
|
||||
if (Galaxy.libraries.datasetView) {
|
||||
Galaxy.libraries.datasetView.$el.unbind("click");
|
||||
}
|
||||
Galaxy.libraries.datasetView = new mod_library_dataset_view.LibraryDatasetView({id: dataset_id, show_version: false, show_permissions: false});
|
||||
Galaxy.libraries.datasetView = new mod_library_dataset_view.LibraryDatasetView({
|
||||
id: dataset_id,
|
||||
show_version: false,
|
||||
show_permissions: false
|
||||
});
|
||||
});
|
||||
|
||||
this.library_router.on( 'route:dataset_version', function(folder_id, dataset_id, ldda_id){
|
||||
if (Galaxy.libraries.datasetView){
|
||||
Galaxy.libraries.datasetView.$el.unbind('click');
|
||||
this.library_router.on("route:dataset_version", (folder_id, dataset_id, ldda_id) => {
|
||||
if (Galaxy.libraries.datasetView) {
|
||||
Galaxy.libraries.datasetView.$el.unbind("click");
|
||||
}
|
||||
Galaxy.libraries.datasetView = new mod_library_dataset_view.LibraryDatasetView({id: dataset_id, ldda_id: ldda_id, show_version: true});
|
||||
Galaxy.libraries.datasetView = new mod_library_dataset_view.LibraryDatasetView({
|
||||
id: dataset_id,
|
||||
ldda_id: ldda_id,
|
||||
show_version: true
|
||||
});
|
||||
});
|
||||
|
||||
this.library_router.on( 'route:dataset_permissions', function(folder_id, dataset_id){
|
||||
if (Galaxy.libraries.datasetView){
|
||||
Galaxy.libraries.datasetView.$el.unbind('click');
|
||||
this.library_router.on("route:dataset_permissions", (folder_id, dataset_id) => {
|
||||
if (Galaxy.libraries.datasetView) {
|
||||
Galaxy.libraries.datasetView.$el.unbind("click");
|
||||
}
|
||||
Galaxy.libraries.datasetView = new mod_library_dataset_view.LibraryDatasetView({id: dataset_id, show_permissions: true});
|
||||
Galaxy.libraries.datasetView = new mod_library_dataset_view.LibraryDatasetView({
|
||||
id: dataset_id,
|
||||
show_permissions: true
|
||||
});
|
||||
});
|
||||
|
||||
this.library_router.on( 'route:library_permissions', function(library_id){
|
||||
if (Galaxy.libraries.libraryView){
|
||||
Galaxy.libraries.libraryView.$el.unbind('click');
|
||||
this.library_router.on("route:library_permissions", library_id => {
|
||||
if (Galaxy.libraries.libraryView) {
|
||||
Galaxy.libraries.libraryView.$el.unbind("click");
|
||||
}
|
||||
Galaxy.libraries.libraryView = new mod_library_library_view.LibraryView({id: library_id, show_permissions: true});
|
||||
Galaxy.libraries.libraryView = new mod_library_library_view.LibraryView({
|
||||
id: library_id,
|
||||
show_permissions: true
|
||||
});
|
||||
});
|
||||
|
||||
this.library_router.on( 'route:folder_permissions', function(folder_id){
|
||||
if (Galaxy.libraries.folderView){
|
||||
Galaxy.libraries.folderView.$el.unbind('click');
|
||||
this.library_router.on("route:folder_permissions", folder_id => {
|
||||
if (Galaxy.libraries.folderView) {
|
||||
Galaxy.libraries.folderView.$el.unbind("click");
|
||||
}
|
||||
Galaxy.libraries.folderView = new mod_library_folder_view.FolderView({id: folder_id, show_permissions: true});
|
||||
Galaxy.libraries.folderView = new mod_library_folder_view.FolderView({
|
||||
id: folder_id,
|
||||
show_permissions: true
|
||||
});
|
||||
});
|
||||
|
||||
this.library_router.on( 'route:import_datasets', function( folder_id, source ){
|
||||
if ( Galaxy.libraries.folderToolbarView && Galaxy.libraries.folderListView ){
|
||||
Galaxy.libraries.folderToolbarView.showImportModal( { source:source } );
|
||||
this.library_router.on("route:import_datasets", (folder_id, source) => {
|
||||
if (Galaxy.libraries.folderToolbarView && Galaxy.libraries.folderListView) {
|
||||
Galaxy.libraries.folderToolbarView.showImportModal({
|
||||
source: source
|
||||
});
|
||||
} else {
|
||||
Galaxy.libraries.folderToolbarView = new mod_foldertoolbar_view.FolderToolbarView( { id: folder_id } );
|
||||
Galaxy.libraries.folderListView = new mod_folderlist_view.FolderListView( { id: folder_id } );
|
||||
Galaxy.libraries.folderToolbarView.showImportModal( { source: source } );
|
||||
Galaxy.libraries.folderToolbarView = new mod_foldertoolbar_view.FolderToolbarView({ id: folder_id });
|
||||
Galaxy.libraries.folderListView = new mod_folderlist_view.FolderListView({ id: folder_id });
|
||||
Galaxy.libraries.folderToolbarView.showImportModal({
|
||||
source: source
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Backbone.history.start({pushState: false});
|
||||
Backbone.history.start({ pushState: false });
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
export default {
|
||||
GalaxyApp: GalaxyLibrary
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,6 @@
|
||||
/**
|
||||
* @license RequireJS i18n 2.0.4 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved.
|
||||
* Available via the MIT or new BSD license.
|
||||
* see: http://github.com/requirejs/i18n for details
|
||||
* @license i18n 2.0.6 Copyright jQuery Foundation and other contributors.
|
||||
* Released under MIT license, http://github.com/requirejs/i18n/LICENSE
|
||||
*/
|
||||
/*jslint regexp: true */
|
||||
/*global require: false, navigator: false, define: false */
|
||||
@@ -11,13 +10,13 @@
|
||||
*
|
||||
* 1) A regular module can have a dependency on an i18n bundle, but the regular
|
||||
* module does not want to specify what locale to load. So it just specifies
|
||||
* the top-level bundle, like "i18n!nls/colors".
|
||||
* the top-level bundle, like 'i18n!nls/colors'.
|
||||
*
|
||||
* This plugin will load the i18n bundle at nls/colors, see that it is a root/master
|
||||
* bundle since it does not have a locale in its name. It will then try to find
|
||||
* the best match locale available in that master bundle, then request all the
|
||||
* locale pieces for that best match locale. For instance, if the locale is "en-us",
|
||||
* then the plugin will ask for the "en-us", "en" and "root" bundles to be loaded
|
||||
* locale pieces for that best match locale. For instance, if the locale is 'en-us',
|
||||
* then the plugin will ask for the 'en-us', 'en' and 'root' bundles to be loaded
|
||||
* (but only if they are specified on the master bundle).
|
||||
*
|
||||
* Once all the bundles for the locale pieces load, then it mixes in all those
|
||||
@@ -34,14 +33,14 @@
|
||||
* locale pieces into each other, then finally sets the context.defined value
|
||||
* for the nls/fr-fr/colors bundle to be that mixed in locale.
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
(function() {
|
||||
"use strict";
|
||||
|
||||
//regexp for reconstructing the master bundle name from parts of the regexp match
|
||||
//nlsRegExp.exec("foo/bar/baz/nls/en-ca/foo") gives:
|
||||
//["foo/bar/baz/nls/en-ca/foo", "foo/bar/baz/nls/", "/", "/", "en-ca", "foo"]
|
||||
//nlsRegExp.exec("foo/bar/baz/nls/foo") gives:
|
||||
//["foo/bar/baz/nls/foo", "foo/bar/baz/nls/", "/", "/", "foo", ""]
|
||||
//nlsRegExp.exec('foo/bar/baz/nls/en-ca/foo') gives:
|
||||
//['foo/bar/baz/nls/en-ca/foo', 'foo/bar/baz/nls/', '/', '/', 'en-ca', 'foo']
|
||||
//nlsRegExp.exec('foo/bar/baz/nls/foo') gives:
|
||||
//['foo/bar/baz/nls/foo', 'foo/bar/baz/nls/', '/', '/', 'foo', '']
|
||||
//so, if match[5] is blank, it means this is the top bundle definition.
|
||||
var nlsRegExp = /(^.*(^|\/)nls(\/|$))([^\/]*)\/?([^\/]*)/;
|
||||
|
||||
@@ -52,14 +51,14 @@
|
||||
if (master[locale]) {
|
||||
needed.push(locale);
|
||||
if (master[locale] === true || master[locale] === 1) {
|
||||
toLoad.push(prefix + locale + '/' + suffix);
|
||||
toLoad.push(prefix + locale + "/" + suffix);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addIfExists(req, locale, toLoad, prefix, suffix) {
|
||||
var fullName = prefix + locale + '/' + suffix;
|
||||
if (require._fileExists(req.toUrl(fullName + '.js'))) {
|
||||
var fullName = prefix + locale + "/" + suffix;
|
||||
if (require._fileExists(req.toUrl(fullName + ".js"))) {
|
||||
toLoad.push(fullName);
|
||||
}
|
||||
}
|
||||
@@ -76,7 +75,7 @@
|
||||
for (prop in source) {
|
||||
if (source.hasOwnProperty(prop) && (!target.hasOwnProperty(prop) || force)) {
|
||||
target[prop] = source[prop];
|
||||
} else if (typeof source[prop] === 'object') {
|
||||
} else if (typeof source[prop] === "object") {
|
||||
if (!target[prop] && source[prop]) {
|
||||
target[prop] = {};
|
||||
}
|
||||
@@ -85,15 +84,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
define(['module'], function (module) {
|
||||
define(["module"], function(module) {
|
||||
var masterConfig = module.config ? module.config() : {};
|
||||
|
||||
return {
|
||||
version: '2.0.4',
|
||||
version: "2.0.6",
|
||||
/**
|
||||
* Called when a dependency needs to be loaded.
|
||||
*/
|
||||
load: function (name, req, onLoad, config) {
|
||||
load: function(name, req, onLoad, config) {
|
||||
config = config || {};
|
||||
|
||||
if (config.locale) {
|
||||
@@ -108,7 +107,9 @@
|
||||
parts = locale.split("-"),
|
||||
toLoad = [],
|
||||
value = {},
|
||||
i, part, current = "";
|
||||
i,
|
||||
part,
|
||||
current = "";
|
||||
|
||||
//If match[5] is blank, it means this is the top bundle definition,
|
||||
//so it does not have to be handled. Locale-specific requests
|
||||
@@ -124,9 +125,14 @@
|
||||
locale = masterConfig.locale;
|
||||
if (!locale) {
|
||||
locale = masterConfig.locale =
|
||||
typeof navigator === "undefined" ? "root" :
|
||||
(navigator.language ||
|
||||
navigator.userLanguage || "root").toLowerCase();
|
||||
typeof navigator === "undefined"
|
||||
? "root"
|
||||
: (
|
||||
(navigator.languages && navigator.languages[0]) ||
|
||||
navigator.language ||
|
||||
navigator.userLanguage ||
|
||||
"root"
|
||||
).toLowerCase();
|
||||
}
|
||||
parts = locale.split("-");
|
||||
}
|
||||
@@ -142,12 +148,12 @@
|
||||
addIfExists(req, current, toLoad, prefix, suffix);
|
||||
}
|
||||
|
||||
req(toLoad, function () {
|
||||
req(toLoad, function() {
|
||||
onLoad();
|
||||
});
|
||||
} else {
|
||||
//First, fetch the master bundle, it knows what locales are available.
|
||||
req([masterName], function (master) {
|
||||
req([masterName], function(master) {
|
||||
//Figure out the best fit
|
||||
var needed = [],
|
||||
part;
|
||||
@@ -161,13 +167,13 @@
|
||||
}
|
||||
|
||||
//Load all the parts missing.
|
||||
req(toLoad, function () {
|
||||
req(toLoad, function() {
|
||||
var i, partBundle, part;
|
||||
for (i = needed.length - 1; i > -1 && needed[i]; i--) {
|
||||
part = needed[i];
|
||||
partBundle = master[part];
|
||||
if (partBundle === true || partBundle === 1) {
|
||||
partBundle = req(prefix + part + '/' + suffix);
|
||||
partBundle = req(prefix + part + "/" + suffix);
|
||||
}
|
||||
mixin(value, partBundle);
|
||||
}
|
||||
@@ -180,4 +186,4 @@
|
||||
}
|
||||
};
|
||||
});
|
||||
}());
|
||||
})();
|
||||
|
||||
@@ -1,98 +1,104 @@
|
||||
import _l from "utils/localization";
|
||||
/** Real-time Communication feature **/
|
||||
define(['mvc/ui/ui-modal'], function( Modal ) {
|
||||
|
||||
import Modal from "mvc/ui/ui-modal";
|
||||
var GenericNavView = Backbone.View.extend({
|
||||
|
||||
initialize: function ( ) {
|
||||
initialize: function() {
|
||||
this.modal = null;
|
||||
},
|
||||
|
||||
/** makes bootstrap modal and iframe inside it */
|
||||
makeModalIframe: function( e ) {
|
||||
// make modal
|
||||
var host = window.Galaxy.config.communication_server_host,
|
||||
port = window.Galaxy.config.communication_server_port,
|
||||
username = escape( window.Galaxy.user.attributes.username ),
|
||||
persistent_communication_rooms = escape( window.Galaxy.config.persistent_communication_rooms ),
|
||||
query_string = "?username=" + username + "&persistent_communication_rooms=" + persistent_communication_rooms,
|
||||
src = host + ":" + port + query_string,
|
||||
$el_chat_modal_header = null,
|
||||
$el_chat_modal_body = null,
|
||||
iframe_template = '<iframe class="f-iframe fade in communication-iframe" src="' + src + '"> </iframe>',
|
||||
header_template = '<i class="fa fa-comment" aria-hidden="true" title="Communicate with other users"></i>' +
|
||||
'<i class="fa fa-expand expand-compress-modal" aria-hidden="true" title="Maximize"></i>' +
|
||||
'<i class="fa fa-times close-modal" aria-hidden="true" title="Close"></i>',
|
||||
frame_height = 350,
|
||||
frame_width = 600,
|
||||
class_names = 'ui-modal chat-modal';
|
||||
|
||||
// deletes the chat modal if already present and create one
|
||||
if( $( '.chat-modal' ).length > 0 ) {
|
||||
$( '.chat-modal' ).remove();
|
||||
}
|
||||
// creates a modal
|
||||
GenericNavView.modal = new Modal.View({
|
||||
body : iframe_template,
|
||||
height : frame_height,
|
||||
width : frame_width,
|
||||
closing_events : true,
|
||||
title_separator : false,
|
||||
cls : class_names
|
||||
});
|
||||
makeModalIframe: function(e) {
|
||||
// make modal
|
||||
var host = window.Galaxy.config.communication_server_host;
|
||||
|
||||
// shows modal
|
||||
GenericNavView.modal.show();
|
||||
$el_chat_modal_header = $( '.chat-modal .modal-header' );
|
||||
$el_chat_modal_body = $( '.chat-modal .modal-body' );
|
||||
// adjusts the css of bootstrap modal for chat
|
||||
$el_chat_modal_header.addClass('modal-header-body');
|
||||
$el_chat_modal_body.addClass('modal-header-body');
|
||||
$el_chat_modal_header.find( 'h4' ).remove();
|
||||
$el_chat_modal_header.removeAttr( 'min-height padding border' );
|
||||
$el_chat_modal_header.append( header_template );
|
||||
// click event of the close button for chat
|
||||
$( '.close-modal' ).click(function( e ) {
|
||||
$( '.chat-modal' ).css( 'display', 'none' );
|
||||
});
|
||||
var port = window.Galaxy.config.communication_server_port;
|
||||
var username = escape(window.Galaxy.user.attributes.username);
|
||||
|
||||
var persistent_communication_rooms = escape(window.Galaxy.config.persistent_communication_rooms);
|
||||
|
||||
var query_string = `?username=${username}&persistent_communication_rooms=${persistent_communication_rooms}`;
|
||||
|
||||
var src = `${host}:${port}${query_string}`;
|
||||
var $el_chat_modal_header = null;
|
||||
var $el_chat_modal_body = null;
|
||||
|
||||
var iframe_template = `<iframe class="f-iframe fade in communication-iframe" src="${src}"> </iframe>`;
|
||||
|
||||
var header_template =
|
||||
'<i class="fa fa-comment" aria-hidden="true" title="Communicate with other users"></i>' +
|
||||
'<i class="fa fa-expand expand-compress-modal" aria-hidden="true" title="Maximize"></i>' +
|
||||
'<i class="fa fa-times close-modal" aria-hidden="true" title="Close"></i>';
|
||||
|
||||
var frame_height = 350;
|
||||
var frame_width = 600;
|
||||
var class_names = "ui-modal chat-modal";
|
||||
|
||||
// deletes the chat modal if already present and create one
|
||||
if ($(".chat-modal").length > 0) {
|
||||
$(".chat-modal").remove();
|
||||
}
|
||||
// creates a modal
|
||||
GenericNavView.modal = new Modal.View({
|
||||
body: iframe_template,
|
||||
height: frame_height,
|
||||
width: frame_width,
|
||||
closing_events: true,
|
||||
title_separator: false,
|
||||
cls: class_names
|
||||
});
|
||||
|
||||
// shows modal
|
||||
GenericNavView.modal.show();
|
||||
$el_chat_modal_header = $(".chat-modal .modal-header");
|
||||
$el_chat_modal_body = $(".chat-modal .modal-body");
|
||||
// adjusts the css of bootstrap modal for chat
|
||||
$el_chat_modal_header.addClass("modal-header-body");
|
||||
$el_chat_modal_body.addClass("modal-header-body");
|
||||
$el_chat_modal_header.find("h4").remove();
|
||||
$el_chat_modal_header.removeAttr("min-height padding border");
|
||||
$el_chat_modal_header.append(header_template);
|
||||
// click event of the close button for chat
|
||||
$(".close-modal").click(e => {
|
||||
$(".chat-modal").css("display", "none");
|
||||
});
|
||||
// click event of expand and compress icon
|
||||
$( '.expand-compress-modal' ).click(function( e ) {
|
||||
if( $( '.expand-compress-modal' ).hasClass( 'fa-expand' ) ) {
|
||||
$( '.chat-modal .modal-dialog' ).width( '1000px' );
|
||||
$( '.chat-modal .modal-body' ).height( '575px' );
|
||||
$( '.expand-compress-modal' ).removeClass( 'fa-expand' ).addClass( 'fa-compress' );
|
||||
$( '.expand-compress-modal' ).attr('title', 'Minimize');
|
||||
$( '.expand-compress-modal' ).css('margin-left', '96.2%');
|
||||
$(".expand-compress-modal").click(e => {
|
||||
if ($(".expand-compress-modal").hasClass("fa-expand")) {
|
||||
$(".chat-modal .modal-dialog").width("1000px");
|
||||
$(".chat-modal .modal-body").height("575px");
|
||||
$(".expand-compress-modal")
|
||||
.removeClass("fa-expand")
|
||||
.addClass("fa-compress");
|
||||
$(".expand-compress-modal").attr("title", "Minimize");
|
||||
$(".expand-compress-modal").css("margin-left", "96.2%");
|
||||
} else {
|
||||
$(".chat-modal .modal-dialog").width(`${frame_width}px`);
|
||||
$(".chat-modal .modal-body").height(`${frame_height}px`);
|
||||
$(".expand-compress-modal")
|
||||
.removeClass("fa-compress")
|
||||
.addClass("fa-expand");
|
||||
$(".expand-compress-modal").attr("title", "Maximize");
|
||||
$(".expand-compress-modal").css("margin-left", "93.2%");
|
||||
}
|
||||
else {
|
||||
$( '.chat-modal .modal-dialog' ).width( frame_width + 'px' );
|
||||
$( '.chat-modal .modal-body' ).height( frame_height + 'px' );
|
||||
$( '.expand-compress-modal' ).removeClass( 'fa-compress' ).addClass( 'fa-expand' );
|
||||
$( '.expand-compress-modal' ).attr('title', 'Maximize');
|
||||
$( '.expand-compress-modal' ).css('margin-left', '93.2%');
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
return this;
|
||||
},
|
||||
|
||||
/**renders the chat icon as a nav item*/
|
||||
render: function() {
|
||||
var self = this,
|
||||
navItem = {};
|
||||
var self = this;
|
||||
var navItem = {};
|
||||
navItem = {
|
||||
id : 'show-chat-online',
|
||||
icon : 'fa-comment-o',
|
||||
tooltip : 'Chat online',
|
||||
visible : false,
|
||||
onclick : self.makeModalIframe
|
||||
}
|
||||
id: "show-chat-online",
|
||||
icon: "fa-comment-o",
|
||||
tooltip: _l("Chat online"),
|
||||
visible: false,
|
||||
onclick: self.makeModalIframe
|
||||
};
|
||||
return navItem;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
GenericNavView : GenericNavView
|
||||
export default {
|
||||
GenericNavView: GenericNavView
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -1,92 +1,103 @@
|
||||
define([
|
||||
'utils/utils',
|
||||
'layout/menu',
|
||||
'layout/scratchbook',
|
||||
'mvc/user/user-quotameter',
|
||||
], function( Utils, Menu, Scratchbook, QuotaMeter ) {
|
||||
|
||||
import Utils from "utils/utils";
|
||||
import Menu from "layout/menu";
|
||||
import Scratchbook from "layout/scratchbook";
|
||||
import QuotaMeter from "mvc/user/user-quotameter";
|
||||
/** Masthead **/
|
||||
var View = Backbone.View.extend({
|
||||
initialize : function( options ) {
|
||||
initialize: function(options) {
|
||||
var self = this;
|
||||
this.options = options;
|
||||
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.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");
|
||||
|
||||
// build tabs
|
||||
this.collection = new Menu.Collection();
|
||||
this.collection.on( 'add', function( model ) {
|
||||
self.$navbarTabs.append( new Menu.Tab( { model : model } ).render().$el );
|
||||
}).on( 'reset', function() {
|
||||
self.$navbarTabs.empty();
|
||||
}).on( 'dispatch', function( callback ) {
|
||||
self.collection.each( function ( m ) { callback( m ) });
|
||||
}).fetch( this.options );
|
||||
this.collection
|
||||
.on("add", model => {
|
||||
self.$navbarTabs.append(new Menu.Tab({ model: model }).render().$el);
|
||||
})
|
||||
.on("reset", () => {
|
||||
self.$navbarTabs.empty();
|
||||
})
|
||||
.on("dispatch", callback => {
|
||||
self.collection.each(m => {
|
||||
callback(m);
|
||||
});
|
||||
})
|
||||
.fetch(this.options);
|
||||
|
||||
// scratchbook
|
||||
Galaxy.frame = this.frame = new Scratchbook( { collection: this.collection } );
|
||||
Galaxy.frame = this.frame = new Scratchbook({
|
||||
collection: this.collection
|
||||
});
|
||||
|
||||
// set up the quota meter (And fetch the current user data from trans)
|
||||
// add quota meter to masthead
|
||||
Galaxy.quotaMeter = this.quotaMeter = new QuotaMeter.UserQuotaMeter({
|
||||
model : Galaxy.user,
|
||||
el : this.$quoteMeter
|
||||
model: Galaxy.user,
|
||||
el: this.$quoteMeter
|
||||
});
|
||||
|
||||
// loop through beforeunload functions if the user attempts to unload the page
|
||||
$( window ).on( 'click', function( e ) {
|
||||
var $download_link = $( e.target ).closest( 'a[download]' );
|
||||
if ( $download_link.length == 1 ) {
|
||||
if( $( 'iframe[id=download]' ).length === 0 ) {
|
||||
$( 'body' ).append( $( '<iframe/>' ).attr( 'id', 'download' ).hide() );
|
||||
$(window)
|
||||
.on("click", e => {
|
||||
var $download_link = $(e.target).closest("a[download]");
|
||||
if ($download_link.length == 1) {
|
||||
if ($("iframe[id=download]").length === 0) {
|
||||
$("body").append(
|
||||
$("<iframe/>")
|
||||
.attr("id", "download")
|
||||
.hide()
|
||||
);
|
||||
}
|
||||
$("iframe[id=download]").attr("src", $download_link.attr("href"));
|
||||
e.preventDefault();
|
||||
}
|
||||
})
|
||||
.on("beforeunload", () => {
|
||||
var text = "";
|
||||
self.collection.each(model => {
|
||||
var q = model.get("onbeforeunload") && model.get("onbeforeunload")();
|
||||
q && (text += `${q} `);
|
||||
});
|
||||
if (text !== "") {
|
||||
return text;
|
||||
}
|
||||
$( 'iframe[id=download]' ).attr( 'src', $download_link.attr( 'href' ) );
|
||||
e.preventDefault();
|
||||
}
|
||||
}).on( 'beforeunload', function() {
|
||||
var text = '';
|
||||
self.collection.each( function( model ) {
|
||||
var q = model.get( 'onbeforeunload' ) && model.get( 'onbeforeunload' )();
|
||||
q && ( text += q + ' ' );
|
||||
});
|
||||
if ( text !== '' ) {
|
||||
return text;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
this.$navbarBrandTitle.html( 'Galaxy ' + ( this.options.brand && '/ ' + this.options.brand || '' ) );
|
||||
this.$navbarBrandLink.attr( 'href', this.options.logo_url );
|
||||
this.$navbarBrandImage.attr( 'src', this.options.logo_src );
|
||||
this.$navbarBrandTitle.html(`Galaxy ${(this.options.brand && `/ ${this.options.brand}`) || ""}`);
|
||||
this.$navbarBrandLink.attr("href", this.options.logo_url);
|
||||
this.$navbarBrandImage.attr("src", this.options.logo_src);
|
||||
this.quotaMeter.render();
|
||||
return this;
|
||||
},
|
||||
|
||||
/** body template */
|
||||
_template: function() {
|
||||
return '<div id="masthead" class="navbar navbar-fixed-top navbar-inverse">' +
|
||||
'<div class="navbar-header">' +
|
||||
'<div class="navbar-tabs"/>' +
|
||||
'</div>' +
|
||||
'<div class="navbar-brand">' +
|
||||
'<a class="navbar-brand-link">' +
|
||||
'<img class="navbar-brand-image"/>' +
|
||||
'<span class="navbar-brand-title"/>' +
|
||||
'</a>' +
|
||||
'</div>' +
|
||||
'<div class="quota-meter-container"/>' +
|
||||
'<div class="navbar-icons"/>' +
|
||||
'</div>';
|
||||
return (
|
||||
'<div id="masthead" class="navbar navbar-fixed-top navbar-inverse">' +
|
||||
'<div class="navbar-header">' +
|
||||
'<div class="navbar-tabs"/>' +
|
||||
"</div>" +
|
||||
'<div class="navbar-brand">' +
|
||||
'<a class="navbar-brand-link">' +
|
||||
'<img class="navbar-brand-image"/>' +
|
||||
'<span class="navbar-brand-title"/>' +
|
||||
"</a>" +
|
||||
"</div>" +
|
||||
'<div class="quota-meter-container"/>' +
|
||||
'<div class="navbar-icons"/>' +
|
||||
"</div>"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
export default {
|
||||
View: View
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
/** Masthead Collection **/
|
||||
define(['layout/generic-nav-view', 'mvc/webhooks', 'utils/localization'], function( GenericNav, Webhooks, _l ) {
|
||||
import GenericNav from "layout/generic-nav-view";
|
||||
import Webhooks from "mvc/webhooks";
|
||||
import _l from "utils/localization";
|
||||
import Utils from "utils/utils";
|
||||
var Collection = Backbone.Collection.extend({
|
||||
model: Backbone.Model.extend({
|
||||
defaults: {
|
||||
visible : true,
|
||||
target : '_parent'
|
||||
visible: true,
|
||||
target: "_parent"
|
||||
}
|
||||
}),
|
||||
fetch: function( options ){
|
||||
fetch: function(options) {
|
||||
var self = this;
|
||||
options = options || {};
|
||||
this.reset();
|
||||
@@ -22,92 +25,79 @@ var Collection = Backbone.Collection.extend({
|
||||
// Analyze data tab.
|
||||
//
|
||||
this.add({
|
||||
id : 'analysis',
|
||||
title : _l('Analyze Data'),
|
||||
url : '',
|
||||
tooltip : _l('Analysis home view')
|
||||
id: "analysis",
|
||||
title: _l("Analyze Data"),
|
||||
url: "",
|
||||
tooltip: _l("Analysis home view")
|
||||
});
|
||||
|
||||
//
|
||||
// Workflow tab.
|
||||
//
|
||||
this.add({
|
||||
id : 'workflow',
|
||||
title : _l('Workflow'),
|
||||
tooltip : _l('Chain tools into workflows'),
|
||||
disabled : !Galaxy.user.id,
|
||||
url : 'workflow',
|
||||
target : 'galaxy_main',
|
||||
onclick : function() {
|
||||
window.location = Galaxy.root + 'workflow';
|
||||
}
|
||||
id: "workflow",
|
||||
title: _l("Workflow"),
|
||||
tooltip: _l("Chain tools into workflows"),
|
||||
disabled: !Galaxy.user.id,
|
||||
url: "workflows/list"
|
||||
});
|
||||
|
||||
//
|
||||
// 'Shared Items' or Libraries tab.
|
||||
//
|
||||
this.add({
|
||||
id : 'shared',
|
||||
title : _l('Shared Data'),
|
||||
url : 'library/index',
|
||||
tooltip : _l('Access published resources'),
|
||||
menu : [{
|
||||
title : _l('Data Libraries'),
|
||||
url : 'library/list'
|
||||
},{
|
||||
title : _l('Histories'),
|
||||
url : 'history/list_published'
|
||||
},{
|
||||
title : _l('Workflows'),
|
||||
url : 'workflow/list_published'
|
||||
},{
|
||||
title : _l('Visualizations'),
|
||||
url : 'visualization/list_published'
|
||||
},{
|
||||
title : _l('Pages'),
|
||||
url : 'pages/list_published'
|
||||
}]
|
||||
});
|
||||
|
||||
//
|
||||
// Lab menu.
|
||||
//
|
||||
options.user_requests && this.add({
|
||||
id : 'lab',
|
||||
title : 'Lab',
|
||||
menu : [{
|
||||
title : 'Sequencing Requests',
|
||||
url : 'requests/index'
|
||||
},{
|
||||
title : 'Find Samples',
|
||||
url : 'requests/find_samples_index'
|
||||
},{
|
||||
title : 'Help',
|
||||
url : options.lims_doc_url
|
||||
}]
|
||||
id: "shared",
|
||||
title: _l("Shared Data"),
|
||||
url: "library/index",
|
||||
tooltip: _l("Access published resources"),
|
||||
menu: [
|
||||
{
|
||||
title: _l("Data Libraries"),
|
||||
url: "library/list"
|
||||
},
|
||||
{
|
||||
title: _l("Histories"),
|
||||
url: "histories/list_published"
|
||||
},
|
||||
{
|
||||
title: _l("Workflows"),
|
||||
url: "workflows/list_published"
|
||||
},
|
||||
{
|
||||
title: _l("Visualizations"),
|
||||
url: "visualizations/list_published"
|
||||
},
|
||||
{
|
||||
title: _l("Pages"),
|
||||
url: "pages/list_published"
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
//
|
||||
// Visualization tab.
|
||||
//
|
||||
this.add({
|
||||
id : 'visualization',
|
||||
title : _l('Visualization'),
|
||||
url : 'visualization/list',
|
||||
tooltip : _l('Visualize datasets'),
|
||||
disabled : !Galaxy.user.id,
|
||||
menu : [{
|
||||
title : _l('New Track Browser'),
|
||||
url : 'visualization/trackster',
|
||||
target : '_frame'
|
||||
},{
|
||||
title : _l('Saved Visualizations'),
|
||||
url : 'visualization/list',
|
||||
target : '_frame'
|
||||
},{
|
||||
title : _l('Interactive Environments'),
|
||||
url : 'visualization/gie_list',
|
||||
target : 'galaxy_main'
|
||||
id: "visualization",
|
||||
title: _l("Visualization"),
|
||||
url: "visualizations/list",
|
||||
tooltip: _l("Visualize datasets"),
|
||||
disabled: !Galaxy.user.id,
|
||||
menu: [
|
||||
{
|
||||
title: _l("New Track Browser"),
|
||||
url: "visualization/trackster",
|
||||
target: "_frame"
|
||||
},
|
||||
{
|
||||
title: _l("Saved Visualizations"),
|
||||
url: "visualizations/list",
|
||||
target: "_frame"
|
||||
},
|
||||
{
|
||||
title: _l("Interactive Environments"),
|
||||
url: "visualization/gie_list",
|
||||
target: "galaxy_main"
|
||||
}
|
||||
]
|
||||
});
|
||||
@@ -116,19 +106,29 @@ var Collection = Backbone.Collection.extend({
|
||||
// Webhooks
|
||||
//
|
||||
Webhooks.add({
|
||||
url: 'api/webhooks/masthead/all',
|
||||
url: "api/webhooks/masthead/all",
|
||||
callback: function(webhooks) {
|
||||
$(document).ready(function() {
|
||||
$.each(webhooks.models, function(index, model) {
|
||||
$(document).ready(() => {
|
||||
$.each(webhooks.models, (index, model) => {
|
||||
var webhook = model.toJSON();
|
||||
if (webhook.activate) {
|
||||
self.add({
|
||||
id : webhook.name,
|
||||
icon : webhook.config.icon,
|
||||
url : webhook.config.url,
|
||||
tooltip : webhook.config.tooltip,
|
||||
onclick : webhook.config.function && new Function(webhook.config.function),
|
||||
});
|
||||
var obj = {
|
||||
id: webhook.name,
|
||||
icon: webhook.config.icon,
|
||||
url: webhook.config.url,
|
||||
tooltip: webhook.config.tooltip,
|
||||
onclick: webhook.config.function && new Function(webhook.config.function)
|
||||
};
|
||||
|
||||
// Galaxy.page is undefined for data libraries, workflows pages
|
||||
if (Galaxy.page) {
|
||||
Galaxy.page.masthead.collection.add(obj);
|
||||
} else if (Galaxy.masthead) {
|
||||
Galaxy.masthead.collection.add(obj);
|
||||
}
|
||||
|
||||
// Append masthead script and styles to Galaxy main
|
||||
Utils.appendScriptStyle(webhook);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -138,292 +138,327 @@ var Collection = Backbone.Collection.extend({
|
||||
//
|
||||
// Admin.
|
||||
//
|
||||
Galaxy.user.get( 'is_admin' ) && this.add({
|
||||
id : 'admin',
|
||||
title : _l('Admin'),
|
||||
url : 'admin',
|
||||
tooltip : _l('Administer this Galaxy'),
|
||||
cls : 'admin-only'
|
||||
});
|
||||
Galaxy.user.get("is_admin") &&
|
||||
this.add({
|
||||
id: "admin",
|
||||
title: _l("Admin"),
|
||||
url: "admin",
|
||||
tooltip: _l("Administer this Galaxy"),
|
||||
cls: "admin-only"
|
||||
});
|
||||
|
||||
//
|
||||
// Help tab.
|
||||
//
|
||||
var helpTab = {
|
||||
id : 'help',
|
||||
title : _l('Help'),
|
||||
tooltip : _l('Support, contact, and community'),
|
||||
menu : [{
|
||||
title : _l('Support'),
|
||||
url : options.support_url,
|
||||
target : '_blank'
|
||||
},{
|
||||
title : _l('Search'),
|
||||
url : options.search_url,
|
||||
target : '_blank'
|
||||
},{
|
||||
title : _l('Mailing Lists'),
|
||||
url : options.mailing_lists,
|
||||
target : '_blank'
|
||||
},{
|
||||
title : _l('Videos'),
|
||||
url : options.screencasts_url,
|
||||
target : '_blank'
|
||||
},{
|
||||
title : _l('Wiki'),
|
||||
url : options.wiki_url,
|
||||
target : '_blank'
|
||||
},{
|
||||
title : _l('How to Cite Galaxy'),
|
||||
url : options.citation_url,
|
||||
target : '_blank'
|
||||
},{
|
||||
title : _l('Interactive Tours'),
|
||||
url : 'tours',
|
||||
onclick : function(){
|
||||
if (Galaxy.router){
|
||||
Galaxy.router.navigate('tours', {'trigger': true});
|
||||
} else {
|
||||
// Redirect and use clientside routing to go to tour index
|
||||
window.location = Galaxy.root + "tours";
|
||||
}
|
||||
}
|
||||
}]
|
||||
id: "help",
|
||||
title: _l("Help"),
|
||||
tooltip: _l("Support, contact, and community"),
|
||||
menu: [
|
||||
{
|
||||
title: _l("Support"),
|
||||
url: options.support_url,
|
||||
target: "_blank"
|
||||
},
|
||||
{
|
||||
title: _l("Search"),
|
||||
url: options.search_url,
|
||||
target: "_blank"
|
||||
},
|
||||
{
|
||||
title: _l("Mailing Lists"),
|
||||
url: options.mailing_lists,
|
||||
target: "_blank"
|
||||
},
|
||||
{
|
||||
title: _l("Videos"),
|
||||
url: options.screencasts_url,
|
||||
target: "_blank"
|
||||
},
|
||||
{
|
||||
title: _l("Wiki"),
|
||||
url: options.wiki_url,
|
||||
target: "_blank"
|
||||
},
|
||||
{
|
||||
title: _l("How to Cite Galaxy"),
|
||||
url: options.citation_url,
|
||||
target: "_blank"
|
||||
},
|
||||
{
|
||||
title: _l("Interactive Tours"),
|
||||
url: "tours"
|
||||
}
|
||||
]
|
||||
};
|
||||
options.terms_url && helpTab.menu.push({
|
||||
title : _l('Terms and Conditions'),
|
||||
url : options.terms_url,
|
||||
target : '_blank'
|
||||
});
|
||||
options.biostar_url && helpTab.menu.unshift({
|
||||
title : _l('Ask a question'),
|
||||
url : 'biostar/biostar_question_redirect',
|
||||
target : '_blank'
|
||||
});
|
||||
options.biostar_url && helpTab.menu.unshift({
|
||||
title : _l('Galaxy Biostar'),
|
||||
url : options.biostar_url_redirect,
|
||||
target : '_blank'
|
||||
});
|
||||
this.add( helpTab );
|
||||
options.terms_url &&
|
||||
helpTab.menu.push({
|
||||
title: _l("Terms and Conditions"),
|
||||
url: options.terms_url,
|
||||
target: "_blank"
|
||||
});
|
||||
options.biostar_url &&
|
||||
helpTab.menu.unshift({
|
||||
title: _l("Ask a question"),
|
||||
url: "biostar/biostar_question_redirect",
|
||||
target: "_blank"
|
||||
});
|
||||
options.biostar_url &&
|
||||
helpTab.menu.unshift({
|
||||
title: _l("Galaxy Biostar"),
|
||||
url: options.biostar_url_redirect,
|
||||
target: "_blank"
|
||||
});
|
||||
this.add(helpTab);
|
||||
|
||||
//
|
||||
// User tab.
|
||||
//
|
||||
if ( !Galaxy.user.id ){
|
||||
var userTab = {
|
||||
id : 'user',
|
||||
title : _l('Login or Register'),
|
||||
cls : 'loggedout-only',
|
||||
tooltip : _l('Account registration or login'),
|
||||
menu : [{
|
||||
title : _l('Login'),
|
||||
url : 'user/login',
|
||||
target : 'galaxy_main',
|
||||
noscratchbook : true
|
||||
}]
|
||||
};
|
||||
options.allow_user_creation && userTab.menu.push({
|
||||
title : _l('Register'),
|
||||
url : 'user/create',
|
||||
target : 'galaxy_main',
|
||||
noscratchbook : true
|
||||
});
|
||||
this.add( userTab );
|
||||
var userTab = {};
|
||||
if (!Galaxy.user.id) {
|
||||
if (options.allow_user_creation) {
|
||||
userTab = {
|
||||
id: "user",
|
||||
title: _l("Login or Register"),
|
||||
cls: "loggedout-only",
|
||||
tooltip: _l("Account registration or login"),
|
||||
menu: [
|
||||
{
|
||||
title: _l("Login"),
|
||||
url: "user/login",
|
||||
target: "galaxy_main",
|
||||
noscratchbook: true
|
||||
},
|
||||
{
|
||||
title: _l("Register"),
|
||||
url: "user/create",
|
||||
target: "galaxy_main",
|
||||
noscratchbook: true
|
||||
}
|
||||
]
|
||||
};
|
||||
} else {
|
||||
userTab = {
|
||||
id: "user",
|
||||
title: _l("Login"),
|
||||
cls: "loggedout-only",
|
||||
tooltip: _l("Login"),
|
||||
url: "user/login",
|
||||
target: "galaxy_main",
|
||||
noscratchbook: true
|
||||
};
|
||||
}
|
||||
} else {
|
||||
var userTab = {
|
||||
id : 'user',
|
||||
title : _l('User'),
|
||||
cls : 'loggedin-only',
|
||||
tooltip : _l('Account and saved data'),
|
||||
menu : [{
|
||||
title : _l('Logged in as') + ' ' + Galaxy.user.get( 'email' )
|
||||
},{
|
||||
title : _l('Preferences'),
|
||||
url : 'user',
|
||||
target : 'galaxy_main',
|
||||
onclick : function() {
|
||||
if ( Galaxy.router ) {
|
||||
Galaxy.router.push( 'user' );
|
||||
} else {
|
||||
window.location = Galaxy.root + 'user';
|
||||
}
|
||||
}
|
||||
},{
|
||||
title : _l('Custom Builds'),
|
||||
url : 'custom_builds',
|
||||
target : 'galaxy_main',
|
||||
onclick : function() {
|
||||
if ( Galaxy.router ) {
|
||||
Galaxy.router.push( 'custom_builds' );
|
||||
} else {
|
||||
window.location = Galaxy.root + 'custom_builds';
|
||||
}
|
||||
}
|
||||
},{
|
||||
title : _l('Logout'),
|
||||
url : 'user/logout',
|
||||
target : '_top',
|
||||
divider : true
|
||||
},{
|
||||
title : _l('Saved Histories'),
|
||||
url : 'history/list',
|
||||
target : 'galaxy_main'
|
||||
},{
|
||||
title : _l('Saved Datasets'),
|
||||
url : 'dataset/list',
|
||||
target : 'galaxy_main'
|
||||
},{
|
||||
title : _l('Saved Pages'),
|
||||
url : 'pages/list',
|
||||
target : '_top'
|
||||
}]
|
||||
userTab = {
|
||||
id: "user",
|
||||
title: _l("User"),
|
||||
cls: "loggedin-only",
|
||||
tooltip: _l("Account and saved data"),
|
||||
menu: [
|
||||
{
|
||||
title: `${_l("Logged in as")} ${Galaxy.user.get("email")}`
|
||||
},
|
||||
{
|
||||
title: _l("Preferences"),
|
||||
url: "user"
|
||||
},
|
||||
{
|
||||
title: _l("Custom Builds"),
|
||||
url: "custom_builds"
|
||||
},
|
||||
{
|
||||
title: _l("Logout"),
|
||||
url: `user/logout?session_csrf_token=${Galaxy.session_csrf_token}`,
|
||||
target: "_top",
|
||||
divider: true
|
||||
},
|
||||
{
|
||||
title: _l("Saved Histories"),
|
||||
url: "histories/list",
|
||||
target: "_top"
|
||||
},
|
||||
{
|
||||
title: _l("Saved Datasets"),
|
||||
url: "datasets/list",
|
||||
target: "_top"
|
||||
},
|
||||
{
|
||||
title: _l("Saved Pages"),
|
||||
url: "pages/list",
|
||||
target: "_top"
|
||||
}
|
||||
]
|
||||
};
|
||||
this.add( userTab );
|
||||
}
|
||||
var activeView = this.get( options.active_view );
|
||||
activeView && activeView.set( 'active', true );
|
||||
this.add(userTab);
|
||||
var activeView = this.get(options.active_view);
|
||||
activeView && activeView.set("active", true);
|
||||
return new jQuery.Deferred().resolve().promise();
|
||||
}
|
||||
});
|
||||
|
||||
/** Masthead tab **/
|
||||
var Tab = Backbone.View.extend({
|
||||
initialize: function ( options ) {
|
||||
initialize: function(options) {
|
||||
this.model = options.model;
|
||||
this.setElement( this._template() );
|
||||
this.$dropdown = this.$( '.dropdown' );
|
||||
this.$toggle = this.$( '.dropdown-toggle' );
|
||||
this.$menu = this.$( '.dropdown-menu' );
|
||||
this.$note = this.$( '.dropdown-note' );
|
||||
this.listenTo( this.model, 'change', this.render, this );
|
||||
this.setElement(this._template());
|
||||
this.$dropdown = this.$(".dropdown");
|
||||
this.$toggle = this.$(".dropdown-toggle");
|
||||
this.$menu = this.$(".dropdown-menu");
|
||||
this.$note = this.$(".dropdown-note");
|
||||
this.listenTo(this.model, "change", this.render, this);
|
||||
},
|
||||
|
||||
events: {
|
||||
'click .dropdown-toggle' : '_toggleClick'
|
||||
"click .dropdown-toggle": "_toggleClick"
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var self = this;
|
||||
$( '.tooltip' ).remove();
|
||||
this.$el.attr( 'id', this.model.id )
|
||||
.css( { visibility : this.model.get( 'visible' ) && 'visible' || 'hidden' } );
|
||||
this.model.set( 'url', this._formatUrl( this.model.get( 'url' ) ) );
|
||||
this.$note.html( this.model.get( 'note' ) || '' )
|
||||
.removeClass().addClass( 'dropdown-note' )
|
||||
.addClass( this.model.get( 'note_cls' ) )
|
||||
.css( { 'display' : this.model.get( 'show_note' ) && 'block' || 'none' } );
|
||||
this.$toggle.html( this.model.get( 'title' ) || '' )
|
||||
.removeClass().addClass( 'dropdown-toggle' )
|
||||
.addClass( this.model.get( 'cls' ) )
|
||||
.addClass( this.model.get( 'icon' ) && 'dropdown-icon fa ' + this.model.get( 'icon' ) )
|
||||
.addClass( this.model.get( 'toggle' ) && 'toggle' )
|
||||
.attr( 'target', this.model.get( 'target' ) )
|
||||
.attr( 'href', this.model.get( 'url' ) )
|
||||
.attr( 'title', this.model.get( 'tooltip' ) )
|
||||
.tooltip( 'destroy' );
|
||||
this.model.get( 'tooltip' ) && this.$toggle.tooltip( { placement: 'bottom' } );
|
||||
this.$dropdown.removeClass().addClass( 'dropdown' )
|
||||
.addClass( this.model.get( 'disabled' ) && 'disabled' )
|
||||
.addClass( this.model.get( 'active' ) && 'active' );
|
||||
if ( this.model.get( 'menu' ) && this.model.get( 'show_menu' ) ) {
|
||||
this.$menu.show();
|
||||
$( '#dd-helper' ).show().off().on( 'click', function() {
|
||||
$( '#dd-helper' ).hide();
|
||||
self.model.set( 'show_menu', false );
|
||||
$(".tooltip").remove();
|
||||
this.$el.attr("id", this.model.id).css({
|
||||
visibility: (this.model.get("visible") && "visible") || "hidden"
|
||||
});
|
||||
this.model.set("url", this._formatUrl(this.model.get("url")));
|
||||
this.$note
|
||||
.html(this.model.get("note") || "")
|
||||
.removeClass()
|
||||
.addClass("dropdown-note")
|
||||
.addClass(this.model.get("note_cls"))
|
||||
.css({
|
||||
display: (this.model.get("show_note") && "block") || "none"
|
||||
});
|
||||
this.$toggle
|
||||
.html(this.model.get("title") || "")
|
||||
.removeClass()
|
||||
.addClass("dropdown-toggle")
|
||||
.addClass(this.model.get("cls"))
|
||||
.addClass(this.model.get("icon") && `dropdown-icon fa ${this.model.get("icon")}`)
|
||||
.addClass(this.model.get("toggle") && "toggle")
|
||||
.attr("target", this.model.get("target"))
|
||||
.attr("href", this.model.get("url"))
|
||||
.attr("title", this.model.get("tooltip"))
|
||||
.tooltip("destroy");
|
||||
this.model.get("tooltip") && this.$toggle.tooltip({ placement: "bottom" });
|
||||
this.$dropdown
|
||||
.removeClass()
|
||||
.addClass("dropdown")
|
||||
.addClass(this.model.get("disabled") && "disabled")
|
||||
.addClass(this.model.get("active") && "active");
|
||||
if (this.model.get("menu") && this.model.get("show_menu")) {
|
||||
this.$menu.show();
|
||||
$("#dd-helper")
|
||||
.show()
|
||||
.off()
|
||||
.on("click", () => {
|
||||
$("#dd-helper").hide();
|
||||
self.model.set("show_menu", false);
|
||||
});
|
||||
} else {
|
||||
self.$menu.hide();
|
||||
$( '#dd-helper' ).hide();
|
||||
$("#dd-helper").hide();
|
||||
}
|
||||
this.$menu.empty().removeClass( 'dropdown-menu' );
|
||||
if ( this.model.get( 'menu' ) ) {
|
||||
_.each( this.model.get( 'menu' ), function( menuItem ) {
|
||||
self.$menu.append( self._buildMenuItem( menuItem ) );
|
||||
menuItem.divider && self.$menu.append( $( '<li/>' ).addClass( 'divider' ) );
|
||||
this.$menu.empty().removeClass("dropdown-menu");
|
||||
if (this.model.get("menu")) {
|
||||
_.each(this.model.get("menu"), menuItem => {
|
||||
self.$menu.append(self._buildMenuItem(menuItem));
|
||||
menuItem.divider && self.$menu.append($("<li/>").addClass("divider"));
|
||||
});
|
||||
self.$menu.addClass( 'dropdown-menu' );
|
||||
self.$toggle.append( $( '<b/>' ).addClass( 'caret' ) );
|
||||
self.$menu.addClass("dropdown-menu");
|
||||
self.$toggle.append($("<b/>").addClass("caret"));
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
/** Add new menu item */
|
||||
_buildMenuItem: function ( options ) {
|
||||
_buildMenuItem: function(options) {
|
||||
var self = this;
|
||||
options = _.defaults( options || {}, {
|
||||
title : '',
|
||||
url : '',
|
||||
target : '_parent',
|
||||
noscratchbook : false
|
||||
options = _.defaults(options || {}, {
|
||||
title: "",
|
||||
url: "",
|
||||
target: "_parent",
|
||||
noscratchbook: false
|
||||
});
|
||||
options.url = self._formatUrl( options.url );
|
||||
return $( '<li/>' ).append(
|
||||
$( '<a/>' ).attr( 'href', options.url )
|
||||
.attr( 'target', options.target )
|
||||
.html( options.title )
|
||||
.on( 'click', function( e ) {
|
||||
e.preventDefault();
|
||||
self.model.set( 'show_menu', false );
|
||||
if (options.onclick){
|
||||
options.onclick();
|
||||
} else {
|
||||
Galaxy.frame.add( options );
|
||||
}
|
||||
})
|
||||
options.url = self._formatUrl(options.url);
|
||||
return $("<li/>").append(
|
||||
$("<a/>")
|
||||
.attr("href", options.url)
|
||||
.attr("target", options.target)
|
||||
.html(options.title)
|
||||
.on("click", e => {
|
||||
e.preventDefault();
|
||||
self.model.set("show_menu", false);
|
||||
if (options.onclick) {
|
||||
options.onclick();
|
||||
} else {
|
||||
Galaxy.frame.add(options);
|
||||
}
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
/** Handle click event */
|
||||
_toggleClick: function( e ) {
|
||||
_toggleClick: function(e) {
|
||||
var self = this;
|
||||
var model = this.model;
|
||||
e.preventDefault();
|
||||
$( '.tooltip' ).hide();
|
||||
model.trigger( 'dispatch', function( m ) {
|
||||
model.id !== m.id && m.get( 'menu' ) && m.set( 'show_menu', false );
|
||||
$(".tooltip").hide();
|
||||
model.trigger("dispatch", m => {
|
||||
model.id !== m.id && m.get("menu") && m.set("show_menu", false);
|
||||
});
|
||||
if ( !model.get( 'disabled' ) ) {
|
||||
if ( !model.get( 'menu' ) ) {
|
||||
model.get( 'onclick' ) ? model.get( 'onclick' )() : Galaxy.frame.add( model.attributes );
|
||||
if (!model.get("disabled")) {
|
||||
if (!model.get("menu")) {
|
||||
model.get("onclick") ? model.get("onclick")() : Galaxy.frame.add(model.attributes);
|
||||
} else {
|
||||
model.set( 'show_menu', true );
|
||||
model.set("show_menu", true);
|
||||
}
|
||||
} else {
|
||||
function buildLink( label, url ) {
|
||||
return $( '<div/>' ).append( $( '<a/>' ).attr( 'href', Galaxy.root + url ).html( label ) ).html();
|
||||
function buildLink(label, url) {
|
||||
return $("<div/>")
|
||||
.append(
|
||||
$("<a/>")
|
||||
.attr("href", Galaxy.root + url)
|
||||
.html(label)
|
||||
)
|
||||
.html();
|
||||
}
|
||||
this.$toggle.popover && this.$toggle.popover( 'destroy' );
|
||||
this.$toggle.popover({
|
||||
html : true,
|
||||
placement : 'bottom',
|
||||
content : 'Please ' + buildLink( 'login', 'user/login?use_panels=True' ) + ' or ' +
|
||||
buildLink( 'register', 'user/create?use_panels=True' ) + ' to use this feature.'
|
||||
}).popover( 'show' );
|
||||
setTimeout( function() { self.$toggle.popover( 'destroy' ) }, 5000 );
|
||||
this.$toggle.popover && this.$toggle.popover("destroy");
|
||||
this.$toggle
|
||||
.popover({
|
||||
html: true,
|
||||
placement: "bottom",
|
||||
content: `Please ${buildLink("login", "user/login?use_panels=True")} or ${buildLink(
|
||||
"register",
|
||||
"user/create?use_panels=True"
|
||||
)} to use this feature.`
|
||||
})
|
||||
.popover("show");
|
||||
setTimeout(() => {
|
||||
self.$toggle.popover("destroy");
|
||||
}, 5000);
|
||||
}
|
||||
},
|
||||
|
||||
/** Url formatting */
|
||||
_formatUrl: function( url ) {
|
||||
return typeof url == 'string' && url.indexOf( '//' ) === -1 && url.charAt( 0 ) != '/' ? Galaxy.root + url : url;
|
||||
_formatUrl: function(url) {
|
||||
return typeof url == "string" && url.indexOf("//") === -1 && url.charAt(0) != "/" ? Galaxy.root + url : url;
|
||||
},
|
||||
|
||||
/** body tempate */
|
||||
_template: function () {
|
||||
return '<ul class="nav navbar-nav">' +
|
||||
'<li class="dropdown">' +
|
||||
'<a class="dropdown-toggle"/>' +
|
||||
'<ul class="dropdown-menu"/>' +
|
||||
'<div class="dropdown-note"/>' +
|
||||
'</li>' +
|
||||
'</ul>';
|
||||
_template: function() {
|
||||
return (
|
||||
'<ul class="nav navbar-nav">' +
|
||||
'<li class="dropdown">' +
|
||||
'<a class="dropdown-toggle"/>' +
|
||||
'<ul class="dropdown-menu"/>' +
|
||||
'<div class="dropdown-note"/>' +
|
||||
"</li>" +
|
||||
"</ul>"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
Collection : Collection,
|
||||
Tab : Tab
|
||||
export default {
|
||||
Collection: Collection,
|
||||
Tab: Tab
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
@@ -1,104 +1,120 @@
|
||||
define([
|
||||
'jquery',
|
||||
], function (jQuery){
|
||||
import jQuery from "jquery";
|
||||
|
||||
"use strict";
|
||||
// ============================================================================
|
||||
//TODO: (the older version) unify with ui-modal (the newer version)
|
||||
var $ = jQuery;
|
||||
|
||||
// Modal dialog boxes
|
||||
var Modal = function( options ) {
|
||||
var Modal = function(options) {
|
||||
this.$overlay = options.overlay;
|
||||
this.$dialog = options.dialog;
|
||||
this.$header = this.$dialog.find( ".modal-header" );
|
||||
this.$body = this.$dialog.find( ".modal-body" );
|
||||
this.$footer = this.$dialog.find( ".modal-footer" );
|
||||
this.$header = this.$dialog.find(".modal-header");
|
||||
this.$body = this.$dialog.find(".modal-body");
|
||||
this.$footer = this.$dialog.find(".modal-footer");
|
||||
this.$backdrop = options.backdrop;
|
||||
// Close button
|
||||
this.$header.find( ".close" ).on( "click", $.proxy( this.hide, this ) );
|
||||
this.$header.find(".close").on("click", $.proxy(this.hide, this));
|
||||
};
|
||||
|
||||
$.extend( Modal.prototype, {
|
||||
setContent: function( options ) {
|
||||
$.extend(Modal.prototype, {
|
||||
setContent: function(options) {
|
||||
this.$header.hide();
|
||||
// Title
|
||||
if ( options.title ) {
|
||||
this.$header.find( ".title" ).html( options.title );
|
||||
if (options.title) {
|
||||
this.$header.find(".title").html(options.title);
|
||||
this.$header.show();
|
||||
}
|
||||
if ( options.closeButton ) {
|
||||
this.$header.find( ".close" ).show();
|
||||
if (options.closeButton) {
|
||||
this.$header.find(".close").show();
|
||||
this.$header.show();
|
||||
} else {
|
||||
this.$header.find( ".close" ).hide();
|
||||
this.$header.find(".close").hide();
|
||||
}
|
||||
// Buttons
|
||||
this.$footer.hide();
|
||||
var $buttons = this.$footer.find( ".buttons" ).html( "" );
|
||||
if ( options.buttons ) {
|
||||
$.each( options.buttons, function( name, value ) {
|
||||
$buttons.append( $( '<button></button> ' ).text( name ).click( value ) ).append( " " );
|
||||
var $buttons = this.$footer.find(".buttons").html("");
|
||||
if (options.buttons) {
|
||||
$.each(options.buttons, (name, value) => {
|
||||
$buttons
|
||||
.append(
|
||||
$("<button></button> ")
|
||||
.text(name)
|
||||
.click(value)
|
||||
)
|
||||
.append(" ");
|
||||
});
|
||||
this.$footer.show();
|
||||
}
|
||||
var $extraButtons = this.$footer.find( ".extra_buttons" ).html( "" );
|
||||
if ( options.extra_buttons ) {
|
||||
$.each( options.extra_buttons, function( name, value ) {
|
||||
$extraButtons.append( $( '<button></button>' ).text( name ).click( value ) ).append( " " );
|
||||
var $extraButtons = this.$footer.find(".extra_buttons").html("");
|
||||
if (options.extra_buttons) {
|
||||
$.each(options.extra_buttons, (name, value) => {
|
||||
$extraButtons
|
||||
.append(
|
||||
$("<button></button>")
|
||||
.text(name)
|
||||
.click(value)
|
||||
)
|
||||
.append(" ");
|
||||
});
|
||||
this.$footer.show();
|
||||
}
|
||||
// Body
|
||||
var body = options.body;
|
||||
if ( body == "progress" ) {
|
||||
body = $("<div class='progress progress-striped active'><div class='progress-bar' style='width: 100%'></div></div>");
|
||||
if (body == "progress") {
|
||||
body = $(
|
||||
"<div class='progress progress-striped active'><div class='progress-bar' style='width: 100%'></div></div>"
|
||||
);
|
||||
}
|
||||
this.$body.html( body );
|
||||
this.$body.html(body);
|
||||
},
|
||||
show: function( options, callback ) {
|
||||
if ( ! this.$dialog.is( ":visible" ) ) {
|
||||
if ( options.backdrop) {
|
||||
this.$backdrop.addClass( "in" );
|
||||
show: function(options, callback) {
|
||||
if (!this.$dialog.is(":visible")) {
|
||||
if (options.backdrop) {
|
||||
this.$backdrop.addClass("in");
|
||||
} else {
|
||||
this.$backdrop.removeClass( "in" );
|
||||
this.$backdrop.removeClass("in");
|
||||
}
|
||||
this.$overlay.show();
|
||||
this.$dialog.show();
|
||||
this.$overlay.addClass("in");
|
||||
// Fix min-width so that modal cannot shrink considerably if new content is loaded.
|
||||
this.$body.css( "min-width", this.$body.width() );
|
||||
this.$body.css("min-width", this.$body.width());
|
||||
// Set max-height so that modal does not exceed window size and is in middle of page.
|
||||
// TODO: this could perhaps be handled better using CSS.
|
||||
this.$body.css( "max-height",
|
||||
$(window).height() -
|
||||
this.$footer.outerHeight() -
|
||||
this.$header.outerHeight() -
|
||||
parseInt( this.$dialog.css( "padding-top" ), 10 ) -
|
||||
parseInt( this.$dialog.css( "padding-bottom" ), 10 )
|
||||
);
|
||||
this.$body.css(
|
||||
"max-height",
|
||||
$(window).height() -
|
||||
this.$footer.outerHeight() -
|
||||
this.$header.outerHeight() -
|
||||
parseInt(this.$dialog.css("padding-top"), 10) -
|
||||
parseInt(this.$dialog.css("padding-bottom"), 10)
|
||||
);
|
||||
}
|
||||
// Callback on init
|
||||
if ( callback ) {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
hide: function() {
|
||||
var modal = this;
|
||||
modal.$dialog.fadeOut( function() {
|
||||
modal.$overlay.hide();
|
||||
modal.$backdrop.removeClass( "in" );
|
||||
modal.$body.children().remove();
|
||||
// Clear min-width to allow for modal to take size of new body.
|
||||
modal.$body.css( "min-width", undefined );
|
||||
});
|
||||
}
|
||||
modal.$dialog.fadeOut(() => {
|
||||
modal.$overlay.hide();
|
||||
modal.$backdrop.removeClass("in");
|
||||
modal.$body.children().remove();
|
||||
// Clear min-width to allow for modal to take size of new body.
|
||||
modal.$body.css("min-width", undefined);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
var modal;
|
||||
|
||||
$(function(){
|
||||
modal = new Modal( { overlay: $("#top-modal"), dialog: $("#top-modal-dialog"), backdrop: $("#top-modal-backdrop") } );
|
||||
$(() => {
|
||||
modal = new Modal({
|
||||
overlay: $("#top-modal"),
|
||||
dialog: $("#top-modal-dialog"),
|
||||
backdrop: $("#top-modal-backdrop")
|
||||
});
|
||||
});
|
||||
|
||||
// Backward compatibility
|
||||
@@ -106,45 +122,51 @@ function hide_modal() {
|
||||
modal.hide();
|
||||
}
|
||||
|
||||
function show_modal( title, body, buttons, extra_buttons, init_fn ) {
|
||||
modal.setContent( { title: title, body: body, buttons: buttons, extra_buttons: extra_buttons } );
|
||||
modal.show( { backdrop: true }, init_fn );
|
||||
function show_modal(title, body, buttons, extra_buttons, init_fn) {
|
||||
modal.setContent({
|
||||
title: title,
|
||||
body: body,
|
||||
buttons: buttons,
|
||||
extra_buttons: extra_buttons
|
||||
});
|
||||
modal.show({ backdrop: true }, init_fn);
|
||||
}
|
||||
|
||||
function show_message( title, body, buttons, extra_buttons, init_fn ) {
|
||||
modal.setContent( { title: title, body: body, buttons: buttons, extra_buttons: extra_buttons } );
|
||||
modal.show( { backdrop: false }, init_fn );
|
||||
function show_message(title, body, buttons, extra_buttons, init_fn) {
|
||||
modal.setContent({
|
||||
title: title,
|
||||
body: body,
|
||||
buttons: buttons,
|
||||
extra_buttons: extra_buttons
|
||||
});
|
||||
modal.show({ backdrop: false }, init_fn);
|
||||
}
|
||||
|
||||
function show_in_overlay( options ) {
|
||||
var width = options.width || '600';
|
||||
var height = options.height || '400';
|
||||
var scroll = options.scroll || 'auto';
|
||||
$("#overlay-background").bind( "click.overlay", function() {
|
||||
function show_in_overlay(options) {
|
||||
var width = options.width || "600";
|
||||
var height = options.height || "400";
|
||||
var scroll = options.scroll || "auto";
|
||||
$("#overlay-background").bind("click.overlay", () => {
|
||||
hide_modal();
|
||||
$("#overlay-background").unbind( "click.overlay" );
|
||||
$("#overlay-background").unbind("click.overlay");
|
||||
});
|
||||
modal.setContent({
|
||||
closeButton: true,
|
||||
title: " ",
|
||||
body: $(
|
||||
"<div style='margin: -5px;'><iframe style='margin: 0; padding: 0;' src='" + options.url +
|
||||
"' width='" + width +
|
||||
"' height='" + height +
|
||||
"' scrolling='" + scroll +
|
||||
"' frameborder='0'></iframe></div>"
|
||||
`<div style='margin: -5px;'><iframe style='margin: 0; padding: 0;' src='${options.url}' width='${
|
||||
width
|
||||
}' height='${height}' scrolling='${scroll}' frameborder='0'></iframe></div>`
|
||||
)
|
||||
});
|
||||
modal.show( { backdrop: true } );
|
||||
modal.show({ backdrop: true });
|
||||
}
|
||||
|
||||
|
||||
// ============================================================================
|
||||
return {
|
||||
Modal : Modal,
|
||||
hide_modal : hide_modal,
|
||||
show_modal : show_modal,
|
||||
show_message : show_message,
|
||||
show_in_overlay : show_in_overlay,
|
||||
};
|
||||
});
|
||||
export default {
|
||||
Modal: Modal,
|
||||
hide_modal: hide_modal,
|
||||
show_modal: show_modal,
|
||||
show_message: show_message,
|
||||
show_in_overlay: show_in_overlay
|
||||
};
|
||||
|
||||
@@ -1,164 +1,185 @@
|
||||
define( [ 'layout/masthead', 'layout/panel', 'mvc/ui/ui-modal' ], function( Masthead, Panel, Modal ) {
|
||||
var View = Backbone.View.extend({
|
||||
el : 'body',
|
||||
className : 'full-content',
|
||||
_panelids : [ 'left', 'right' ],
|
||||
import Masthead from "layout/masthead";
|
||||
import Panel from "layout/panel";
|
||||
import Modal from "mvc/ui/ui-modal";
|
||||
import Utils from "utils/utils";
|
||||
var View = Backbone.View.extend({
|
||||
el: "body",
|
||||
className: "full-content",
|
||||
_panelids: ["left", "right"],
|
||||
|
||||
initialize : function( options ) {
|
||||
var self = this;
|
||||
this.config = _.defaults( options.config || {}, {
|
||||
message_box_visible : false,
|
||||
message_box_content : '',
|
||||
message_box_class : 'info',
|
||||
show_inactivity_warning : false,
|
||||
inactivity_box_content : ''
|
||||
});
|
||||
initialize: function(options) {
|
||||
var self = this;
|
||||
this.config = _.defaults(options.config || {}, {
|
||||
message_box_visible: false,
|
||||
message_box_content: "",
|
||||
message_box_class: "info",
|
||||
show_inactivity_warning: false,
|
||||
inactivity_box_content: ""
|
||||
});
|
||||
|
||||
// attach global objects, build mastheads
|
||||
Galaxy.modal = this.modal = new Modal.View();
|
||||
Galaxy.display = this.display = function( view ) { self.center.display( view ) };
|
||||
Galaxy.router = this.router = options.Router && new options.Router( self, options );
|
||||
this.masthead = new Masthead.View( this.config );
|
||||
|
||||
// build page template
|
||||
this.$el.attr( 'scroll', 'no' );
|
||||
this.$el.html( this._template() );
|
||||
this.$( '#masthead' ).replaceWith( this.masthead.$el );
|
||||
this.$el.append( this.masthead.frame.$el );
|
||||
this.$el.append( this.modal.$el );
|
||||
this.$messagebox = this.$( '#messagebox' );
|
||||
this.$inactivebox = this.$( '#inactivebox' );
|
||||
|
||||
// build panels
|
||||
this.center = new Panel.CenterPanel();
|
||||
this.panels = {};
|
||||
_.each( this._panelids, function( panel_id ) {
|
||||
var panel_class_name = panel_id.charAt( 0 ).toUpperCase() + panel_id.slice( 1 );
|
||||
var panel_class = options[ panel_class_name ];
|
||||
if ( panel_class ) {
|
||||
var panel_instance = new panel_class( self, options );
|
||||
self[ panel_instance.toString() ] = panel_instance;
|
||||
self.panels[ panel_id ] = new Panel.SidePanel({
|
||||
id : panel_id,
|
||||
el : self.$( '#' + panel_id ),
|
||||
view : panel_instance
|
||||
});
|
||||
}
|
||||
});
|
||||
this.render();
|
||||
},
|
||||
|
||||
render : function() {
|
||||
// TODO: Remove this line after select2 update
|
||||
$( '.select2-hidden-accessible' ).remove();
|
||||
this.masthead.render();
|
||||
this.renderMessageBox();
|
||||
this.renderInactivityBox();
|
||||
this.renderPanels();
|
||||
this._checkCommunicationServerOnline();
|
||||
return this;
|
||||
},
|
||||
|
||||
/** Render message box */
|
||||
renderMessageBox : function() {
|
||||
if ( this.config.message_box_visible ){
|
||||
var content = this.config.message_box_content || '';
|
||||
var level = this.config.message_box_class || 'info';
|
||||
this.$el.addClass( 'has-message-box' );
|
||||
this.$messagebox
|
||||
.attr( 'class', 'panel-' + level + '-message' )
|
||||
.html( content )
|
||||
.toggle( !!content )
|
||||
.show();
|
||||
// attach global objects, build mastheads
|
||||
Galaxy.modal = this.modal = new Modal.View();
|
||||
Galaxy.display = this.display = view => {
|
||||
if (view.title) {
|
||||
Utils.setWindowTitle(view.title);
|
||||
view.allow_title_display = false;
|
||||
} else {
|
||||
this.$el.removeClass( 'has-message-box' );
|
||||
this.$messagebox.hide();
|
||||
Utils.setWindowTitle();
|
||||
view.allow_title_display = true;
|
||||
}
|
||||
return this;
|
||||
},
|
||||
self.center.display(view);
|
||||
};
|
||||
Galaxy.router = this.router = options.Router && new options.Router(self, options);
|
||||
this.masthead = new Masthead.View(this.config);
|
||||
this.center = new Panel.CenterPanel();
|
||||
|
||||
/** Render inactivity warning */
|
||||
renderInactivityBox : function() {
|
||||
if( this.config.show_inactivity_warning ){
|
||||
var content = this.config.inactivity_box_content || '';
|
||||
var verificationLink = $( '<a/>' ).attr( 'href', Galaxy.root + 'user/resend_verification' ).text( 'Resend verification' );
|
||||
this.$el.addClass( 'has-inactivity-box' );
|
||||
this.$inactivebox
|
||||
.html( content + ' ' )
|
||||
.append( verificationLink )
|
||||
.toggle( !!content )
|
||||
.show();
|
||||
} else {
|
||||
this.$el.removeClass( 'has-inactivity-box' );
|
||||
this.$inactivebox.hide();
|
||||
}
|
||||
return this;
|
||||
},
|
||||
// build page template
|
||||
this.$el.attr("scroll", "no");
|
||||
this.$el.html(this._template());
|
||||
this.$("#masthead").replaceWith(this.masthead.$el);
|
||||
this.$("#center").append(this.center.$el);
|
||||
this.$el.append(this.masthead.frame.$el);
|
||||
this.$el.append(this.modal.$el);
|
||||
this.$messagebox = this.$("#messagebox");
|
||||
this.$inactivebox = this.$("#inactivebox");
|
||||
|
||||
/** Render panels */
|
||||
renderPanels : function() {
|
||||
var self = this;
|
||||
this.center.setElement( '#center' );
|
||||
this.center.render();
|
||||
_.each( this._panelids, function( panel_id ) {
|
||||
var panel = self.panels[ panel_id ];
|
||||
if ( panel ) {
|
||||
panel.render();
|
||||
} else {
|
||||
self.center.$el.css( panel_id, 0 );
|
||||
self.$( '#' + panel_id ).hide();
|
||||
}
|
||||
});
|
||||
return this;
|
||||
},
|
||||
|
||||
/** body template */
|
||||
_template: function() {
|
||||
return [
|
||||
'<div id="everything">',
|
||||
'<div id="background"/>',
|
||||
'<div id="masthead"/>',
|
||||
'<div id="messagebox"/>',
|
||||
'<div id="inactivebox" class="panel-warning-message" />',
|
||||
'<div id="left" />',
|
||||
'<div id="center" class="inbound" />',
|
||||
'<div id="right" />',
|
||||
'</div>',
|
||||
'<div id="dd-helper" />',
|
||||
].join('');
|
||||
},
|
||||
|
||||
toString : function() { return 'PageLayoutView' },
|
||||
|
||||
/** Check if the communication server is online and show the icon otherwise hide the icon */
|
||||
_checkCommunicationServerOnline: function(){
|
||||
var host = window.Galaxy.config.communication_server_host,
|
||||
port = window.Galaxy.config.communication_server_port,
|
||||
preferences = window.Galaxy.user.attributes.preferences,
|
||||
$chat_icon_element = $( "#show-chat-online" );
|
||||
/** Check if the user has deactivated the communication in it's personal settings */
|
||||
if ( preferences && [ '1', 'true' ].indexOf( preferences.communication_server ) != -1 ) {
|
||||
// See if the configured communication server is available
|
||||
$.ajax({
|
||||
url: host + ":" + port,
|
||||
})
|
||||
.success( function( data ) {
|
||||
// enable communication only when a user is logged in
|
||||
if( window.Galaxy.user.id !== null ) {
|
||||
if( $chat_icon_element.css( "visibility") === "hidden" ) {
|
||||
$chat_icon_element.css( "visibility", "visible" );
|
||||
}
|
||||
}
|
||||
})
|
||||
.error( function( data ) {
|
||||
// hide the communication icon if the communication server is not available
|
||||
$chat_icon_element.css( "visibility", "hidden" );
|
||||
// build panels
|
||||
this.panels = {};
|
||||
_.each(this._panelids, panel_id => {
|
||||
var panel_class_name = panel_id.charAt(0).toUpperCase() + panel_id.slice(1);
|
||||
var panel_class = options[panel_class_name];
|
||||
if (panel_class) {
|
||||
var panel_instance = new panel_class(self, options);
|
||||
self[panel_instance.toString()] = panel_instance;
|
||||
self.panels[panel_id] = new Panel.SidePanel({
|
||||
id: panel_id,
|
||||
el: self.$(`#${panel_id}`),
|
||||
view: panel_instance
|
||||
});
|
||||
} else {
|
||||
$chat_icon_element.css( "visibility", "hidden" );
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
this.render();
|
||||
|
||||
return { View: View }
|
||||
});
|
||||
// start the router
|
||||
this.router &&
|
||||
Backbone.history.start({
|
||||
root: Galaxy.root,
|
||||
pushState: true
|
||||
});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
// TODO: Remove this line after select2 update
|
||||
$(".select2-hidden-accessible").remove();
|
||||
this.masthead.render();
|
||||
this.renderMessageBox();
|
||||
this.renderInactivityBox();
|
||||
this.renderPanels();
|
||||
this._checkCommunicationServerOnline();
|
||||
return this;
|
||||
},
|
||||
|
||||
/** Render message box */
|
||||
renderMessageBox: function() {
|
||||
if (this.config.message_box_visible) {
|
||||
var content = this.config.message_box_content || "";
|
||||
var level = this.config.message_box_class || "info";
|
||||
this.$el.addClass("has-message-box");
|
||||
this.$messagebox
|
||||
.attr("class", `panel-${level}-message`)
|
||||
.html(content)
|
||||
.toggle(!!content)
|
||||
.show();
|
||||
} else {
|
||||
this.$el.removeClass("has-message-box");
|
||||
this.$messagebox.hide();
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
/** Render inactivity warning */
|
||||
renderInactivityBox: function() {
|
||||
if (this.config.show_inactivity_warning) {
|
||||
var content = this.config.inactivity_box_content || "";
|
||||
var verificationLink = $("<a/>")
|
||||
.attr("href", `${Galaxy.root}user/resend_verification`)
|
||||
.text("Resend verification");
|
||||
this.$el.addClass("has-inactivity-box");
|
||||
this.$inactivebox
|
||||
.html(`${content} `)
|
||||
.append(verificationLink)
|
||||
.toggle(!!content)
|
||||
.show();
|
||||
} else {
|
||||
this.$el.removeClass("has-inactivity-box");
|
||||
this.$inactivebox.hide();
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
/** Render panels */
|
||||
renderPanels: function() {
|
||||
var self = this;
|
||||
_.each(this._panelids, panel_id => {
|
||||
var panel = self.panels[panel_id];
|
||||
if (panel) {
|
||||
panel.render();
|
||||
} else {
|
||||
self.$("#center").css(panel_id, 0);
|
||||
self.$(`#${panel_id}`).hide();
|
||||
}
|
||||
});
|
||||
return this;
|
||||
},
|
||||
|
||||
/** body template */
|
||||
_template: function() {
|
||||
return [
|
||||
'<div id="everything">',
|
||||
'<div id="background"/>',
|
||||
'<div id="masthead"/>',
|
||||
'<div id="messagebox"/>',
|
||||
'<div id="inactivebox" class="panel-warning-message" />',
|
||||
'<div id="left" />',
|
||||
'<div id="center" />',
|
||||
'<div id="right" />',
|
||||
"</div>",
|
||||
'<div id="dd-helper" />'
|
||||
].join("");
|
||||
},
|
||||
|
||||
toString: function() {
|
||||
return "PageLayoutView";
|
||||
},
|
||||
|
||||
/** Check if the communication server is online and show the icon otherwise hide the icon */
|
||||
_checkCommunicationServerOnline: function() {
|
||||
var host = window.Galaxy.config.communication_server_host;
|
||||
var port = window.Galaxy.config.communication_server_port;
|
||||
var preferences = window.Galaxy.user.attributes.preferences;
|
||||
var $chat_icon_element = $("#show-chat-online");
|
||||
/** Check if the user has deactivated the communication in it's personal settings */
|
||||
if (preferences && ["1", "true"].indexOf(preferences.communication_server) != -1) {
|
||||
// See if the configured communication server is available
|
||||
$.ajax({
|
||||
url: `${host}:${port}`
|
||||
})
|
||||
.success(data => {
|
||||
// enable communication only when a user is logged in
|
||||
if (window.Galaxy.user.id !== null) {
|
||||
if ($chat_icon_element.css("visibility") === "hidden") {
|
||||
$chat_icon_element.css("visibility", "visible");
|
||||
}
|
||||
}
|
||||
})
|
||||
.error(data => {
|
||||
// hide the communication icon if the communication server is not available
|
||||
$chat_icon_element.css("visibility", "hidden");
|
||||
});
|
||||
} else {
|
||||
$chat_icon_element.css("visibility", "hidden");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default { View: View };
|
||||
|
||||
@@ -1,268 +1,258 @@
|
||||
define( [ 'jquery', 'libs/underscore', 'libs/backbone' ], function( $, _, Backbone ) {
|
||||
"use strict";
|
||||
import $ from "jquery";
|
||||
import * as _ from "libs/underscore";
|
||||
import * as Backbone from "libs/backbone";
|
||||
|
||||
var MIN_PANEL_WIDTH = 160,
|
||||
MAX_PANEL_WIDTH = 800;
|
||||
var MIN_PANEL_WIDTH = 160;
|
||||
var MAX_PANEL_WIDTH = 800;
|
||||
|
||||
/** View for left/right panels used by Page view */
|
||||
var SidePanel = Backbone.View.extend({
|
||||
/** View for left/right panels used by Page view */
|
||||
var SidePanel = Backbone.View.extend({
|
||||
initialize: function(options) {
|
||||
this.view = options.view;
|
||||
this.hidden = false;
|
||||
this.saved_size = null;
|
||||
this.hiddenByTool = false;
|
||||
},
|
||||
|
||||
initialize: function( options ){
|
||||
this.view = options.view;
|
||||
this.hidden = false;
|
||||
this.saved_size = null;
|
||||
this.hiddenByTool = false;
|
||||
},
|
||||
$center: function() {
|
||||
return this.$el.siblings("#center");
|
||||
},
|
||||
|
||||
$center : function() {
|
||||
return this.$el.siblings( '#center' );
|
||||
},
|
||||
$toggleButton: function() {
|
||||
return this.$(".unified-panel-footer > .panel-collapse");
|
||||
},
|
||||
|
||||
$toggleButton : function() {
|
||||
return this.$( '.unified-panel-footer > .panel-collapse' );
|
||||
},
|
||||
render: function() {
|
||||
var self = this;
|
||||
var panel = this.view;
|
||||
var components = this.view.model.attributes || {};
|
||||
this.$el.html(this._templatePanel(this.id));
|
||||
_.each(components.buttons, button => {
|
||||
self.$(".panel-header-buttons").append(button.$el);
|
||||
});
|
||||
this.$el.addClass(components.cls);
|
||||
this.$(".panel-header-text").html(_.escape(components.title));
|
||||
this.$(".unified-panel-body").append(panel.$el);
|
||||
panel.render();
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var self = this;
|
||||
var panel = this.view;
|
||||
var components = this.view.model.attributes || {};
|
||||
this.$el.html( this._templatePanel( this.id ) );
|
||||
_.each( components.buttons, function( button ) {
|
||||
self.$( '.panel-header-buttons' ).append( button.$el );
|
||||
/** panel dom template. id is 'right' or 'left' */
|
||||
_templatePanel: function() {
|
||||
return [this._templateHeader(), this._templateBody(), this._templateFooter()].join("");
|
||||
},
|
||||
|
||||
/** panel dom template. id is 'right' or 'left' */
|
||||
_templateHeader: function(data) {
|
||||
return [
|
||||
'<div class="unified-panel-header" unselectable="on">',
|
||||
'<div class="unified-panel-header-inner">',
|
||||
'<div class="panel-header-buttons" style="float: right"/>',
|
||||
'<div class="panel-header-text"/>',
|
||||
"</div>",
|
||||
"</div>"
|
||||
].join("");
|
||||
},
|
||||
|
||||
/** panel dom template. id is 'right' or 'left' */
|
||||
_templateBody: function(data) {
|
||||
return '<div class="unified-panel-body"/>';
|
||||
},
|
||||
|
||||
/** panel dom template. id is 'right' or 'left' */
|
||||
_templateFooter: function(data) {
|
||||
return [
|
||||
'<div class="unified-panel-footer">',
|
||||
'<div class="panel-collapse ',
|
||||
_.escape(this.id),
|
||||
'"/>',
|
||||
'<div class="drag"/>',
|
||||
"</div>"
|
||||
].join("");
|
||||
},
|
||||
|
||||
events: {
|
||||
"mousedown .unified-panel-footer > .drag": "_mousedownDragHandler",
|
||||
"click .unified-panel-footer > .panel-collapse": "toggle"
|
||||
},
|
||||
|
||||
_mousedownDragHandler: function(ev) {
|
||||
var self = this;
|
||||
var draggingLeft = this.id === "left";
|
||||
var prevX = ev.pageX;
|
||||
|
||||
function move(e) {
|
||||
var delta = e.pageX - prevX;
|
||||
prevX = e.pageX;
|
||||
var oldWidth = self.$el.width();
|
||||
var newWidth = draggingLeft ? oldWidth + delta : oldWidth - delta;
|
||||
// Limit range
|
||||
newWidth = Math.min(MAX_PANEL_WIDTH, Math.max(MIN_PANEL_WIDTH, newWidth));
|
||||
self.resize(newWidth);
|
||||
}
|
||||
|
||||
// this is a page wide overlay that assists in capturing the move and release of the mouse
|
||||
// if not provided, progress and end wouldn't fire if the mouse moved out of the drag button area
|
||||
$("#dd-helper")
|
||||
.show()
|
||||
.on("mousemove", move)
|
||||
.one("mouseup", function(e) {
|
||||
$(this)
|
||||
.hide()
|
||||
.off("mousemove", move);
|
||||
});
|
||||
this.$el.addClass( components.cls );
|
||||
this.$( '.panel-header-text' ).html( _.escape( components.title ) );
|
||||
this.$( '.unified-panel-body' ).append( panel.$el );
|
||||
panel.render();
|
||||
},
|
||||
},
|
||||
|
||||
/** panel dom template. id is 'right' or 'left' */
|
||||
_templatePanel: function() {
|
||||
return [
|
||||
this._templateHeader(),
|
||||
this._templateBody(),
|
||||
this._templateFooter(),
|
||||
].join('');
|
||||
},
|
||||
// TODO: the following three could be simplified I think
|
||||
// if panel is 'right' (this.id), move center right newSize number of pixels
|
||||
resize: function(newSize) {
|
||||
this.$el.css("width", newSize);
|
||||
this.$center().css(this.id, newSize);
|
||||
return this;
|
||||
},
|
||||
|
||||
/** panel dom template. id is 'right' or 'left' */
|
||||
_templateHeader: function( data ) {
|
||||
return [
|
||||
'<div class="unified-panel-header" unselectable="on">',
|
||||
'<div class="unified-panel-header-inner">',
|
||||
'<div class="panel-header-buttons" style="float: right"/>',
|
||||
'<div class="panel-header-text"/>',
|
||||
'</div>',
|
||||
'</div>',
|
||||
].join('');
|
||||
},
|
||||
show: function() {
|
||||
if (!this.hidden) {
|
||||
return;
|
||||
}
|
||||
var self = this;
|
||||
var animation = {};
|
||||
var whichSide = this.id;
|
||||
animation[whichSide] = 0;
|
||||
self.$el
|
||||
.css(whichSide, -this.saved_size)
|
||||
.show()
|
||||
.animate(animation, "fast", () => {
|
||||
self.resize(self.saved_size);
|
||||
});
|
||||
self.hidden = false;
|
||||
self.$toggleButton().removeClass("hidden");
|
||||
return this;
|
||||
},
|
||||
|
||||
/** panel dom template. id is 'right' or 'left' */
|
||||
_templateBody: function( data ) {
|
||||
return '<div class="unified-panel-body"/>';
|
||||
},
|
||||
hide: function() {
|
||||
if (this.hidden) {
|
||||
return;
|
||||
}
|
||||
var animation = {};
|
||||
var whichSide = this.id;
|
||||
this.saved_size = this.$el.width();
|
||||
animation[whichSide] = -this.saved_size;
|
||||
this.$el.animate(animation, "fast");
|
||||
this.$center().css(whichSide, 0);
|
||||
this.hidden = true;
|
||||
this.$toggleButton().addClass("hidden");
|
||||
return this;
|
||||
},
|
||||
|
||||
/** panel dom template. id is 'right' or 'left' */
|
||||
_templateFooter: function( data ) {
|
||||
return [
|
||||
'<div class="unified-panel-footer">',
|
||||
'<div class="panel-collapse ', _.escape( this.id ), '"/>',
|
||||
'<div class="drag"/>',
|
||||
'</div>',
|
||||
].join('');
|
||||
},
|
||||
toggle: function(ev) {
|
||||
this.hidden ? this.show() : this.hide();
|
||||
this.hiddenByTool = false;
|
||||
return this;
|
||||
},
|
||||
|
||||
events : {
|
||||
'mousedown .unified-panel-footer > .drag' : '_mousedownDragHandler',
|
||||
'click .unified-panel-footer > .panel-collapse' : 'toggle'
|
||||
},
|
||||
|
||||
_mousedownDragHandler : function( ev ) {
|
||||
var self = this,
|
||||
draggingLeft = this.id === 'left',
|
||||
prevX = ev.pageX;
|
||||
|
||||
function move( e ){
|
||||
var delta = e.pageX - prevX;
|
||||
prevX = e.pageX;
|
||||
var oldWidth = self.$el.width(),
|
||||
newWidth = draggingLeft?( oldWidth + delta ):( oldWidth - delta );
|
||||
// Limit range
|
||||
newWidth = Math.min( MAX_PANEL_WIDTH, Math.max( MIN_PANEL_WIDTH, newWidth ) );
|
||||
self.resize( newWidth );
|
||||
// ..............................................................
|
||||
//TODO: only used in message.mako?
|
||||
/** */
|
||||
handle_minwidth_hint: function(hint) {
|
||||
var space = this.$center().width() - (this.hidden ? this.saved_size : 0);
|
||||
if (space < hint) {
|
||||
if (!this.hidden) {
|
||||
this.toggle();
|
||||
this.hiddenByTool = true;
|
||||
}
|
||||
|
||||
// this is a page wide overlay that assists in capturing the move and release of the mouse
|
||||
// if not provided, progress and end wouldn't fire if the mouse moved out of the drag button area
|
||||
$( '#dd-helper' )
|
||||
.show()
|
||||
.on( 'mousemove', move )
|
||||
.one( 'mouseup', function( e ){
|
||||
$( this ).hide().off( 'mousemove', move );
|
||||
});
|
||||
},
|
||||
|
||||
// TODO: the following three could be simplified I think
|
||||
// if panel is 'right' (this.id), move center right newSize number of pixels
|
||||
resize : function( newSize ) {
|
||||
this.$el.css( 'width', newSize );
|
||||
this.$center().css( this.id, newSize );
|
||||
return this;
|
||||
},
|
||||
|
||||
show : function(){
|
||||
if( !this.hidden ) { return }
|
||||
var self = this,
|
||||
animation = {},
|
||||
whichSide = this.id;
|
||||
animation[ whichSide ] = 0;
|
||||
self.$el
|
||||
.css( whichSide, -this.saved_size )
|
||||
.show()
|
||||
.animate( animation, "fast", function(){
|
||||
self.resize( self.saved_size );
|
||||
});
|
||||
self.hidden = false;
|
||||
self.$toggleButton().removeClass( "hidden" );
|
||||
return this;
|
||||
},
|
||||
|
||||
hide : function(){
|
||||
if( this.hidden ) { return }
|
||||
var animation = {},
|
||||
whichSide = this.id;
|
||||
this.saved_size = this.$el.width();
|
||||
animation[ whichSide ] = -this.saved_size;
|
||||
this.$el.animate( animation, "fast" );
|
||||
this.$center().css( whichSide, 0 );
|
||||
this.hidden = true;
|
||||
this.$toggleButton().addClass( "hidden" );
|
||||
return this;
|
||||
},
|
||||
|
||||
toggle: function( ev ) {
|
||||
this.hidden ? this.show() : this.hide();
|
||||
this.hiddenByTool = false;
|
||||
return this;
|
||||
},
|
||||
|
||||
// ..............................................................
|
||||
//TODO: only used in message.mako?
|
||||
/** */
|
||||
handle_minwidth_hint: function( hint ){
|
||||
var space = this.$center().width() - ( this.hidden ? this.saved_size : 0 );
|
||||
if( space < hint ){
|
||||
if( !this.hidden ){
|
||||
this.toggle();
|
||||
this.hiddenByTool = true;
|
||||
}
|
||||
} else {
|
||||
if( this.hiddenByTool ){
|
||||
this.toggle();
|
||||
this.hiddenByTool = false;
|
||||
}
|
||||
} else {
|
||||
if (this.hiddenByTool) {
|
||||
this.toggle();
|
||||
this.hiddenByTool = false;
|
||||
}
|
||||
return self;
|
||||
},
|
||||
}
|
||||
return self;
|
||||
},
|
||||
|
||||
/** */
|
||||
force_panel : function( op ){
|
||||
if( op == 'show' ){ return this.show(); }
|
||||
if( op == 'hide' ){ return this.hide(); }
|
||||
return self;
|
||||
},
|
||||
/** */
|
||||
force_panel: function(op) {
|
||||
if (op == "show") {
|
||||
return this.show();
|
||||
}
|
||||
if (op == "hide") {
|
||||
return this.hide();
|
||||
}
|
||||
return self;
|
||||
},
|
||||
|
||||
toString : function(){ return 'SidePanel(' + this.id + ')' }
|
||||
});
|
||||
toString: function() {
|
||||
return `SidePanel(${this.id})`;
|
||||
}
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// TODO: side should be defined by page - not here
|
||||
var LeftPanel = SidePanel.extend({
|
||||
id : 'left',
|
||||
});
|
||||
// ----------------------------------------------------------------------------
|
||||
// TODO: side should be defined by page - not here
|
||||
var LeftPanel = SidePanel.extend({
|
||||
id: "left"
|
||||
});
|
||||
|
||||
var RightPanel = SidePanel.extend({
|
||||
id : 'right',
|
||||
});
|
||||
var RightPanel = SidePanel.extend({
|
||||
id: "right"
|
||||
});
|
||||
|
||||
/** Center panel with the ability to switch between iframe and view */
|
||||
var CenterPanel = Backbone.View.extend({
|
||||
/** Center panel with the ability to switch between iframe and view */
|
||||
var CenterPanel = Backbone.View.extend({
|
||||
initialize: function(options) {
|
||||
this.setElement($(this.template()));
|
||||
this.$frame = this.$(".center-frame");
|
||||
this.$panel = this.$(".center-panel");
|
||||
this.$frame.on("load", _.bind(this._iframeChangeHandler, this));
|
||||
},
|
||||
|
||||
initialize : function( options ){
|
||||
/** previous view contained in the center panel - cached for removal later */
|
||||
this.prev = null;
|
||||
},
|
||||
/** Display iframe if its target url changes, hide center panel */
|
||||
_iframeChangeHandler: function(ev) {
|
||||
var iframe = ev.currentTarget;
|
||||
var location = iframe.contentWindow && iframe.contentWindow.location;
|
||||
if (location && location.host) {
|
||||
$(iframe).show();
|
||||
this.$panel.empty().hide();
|
||||
Galaxy.trigger("center-frame:load", {
|
||||
fullpath: location.pathname + location.search + location.hash,
|
||||
pathname: location.pathname,
|
||||
search: location.search,
|
||||
hash: location.hash
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
render : function(){
|
||||
this.$el.html( this.template() );
|
||||
// ?: doesn't work/listen in events map
|
||||
this.$( '#galaxy_main' ).on( 'load', _.bind( this._iframeChangeHandler, this ) );
|
||||
},
|
||||
/** Display a view in the center panel, hide iframe */
|
||||
display: function(view) {
|
||||
var contentWindow = this.$frame[0].contentWindow || {};
|
||||
var message = contentWindow.onbeforeunload && contentWindow.onbeforeunload();
|
||||
if (!message || confirm(message)) {
|
||||
contentWindow.onbeforeunload = undefined;
|
||||
this.$frame.attr("src", "about:blank").hide();
|
||||
this.$panel
|
||||
.empty()
|
||||
.scrollTop(0)
|
||||
.append(view.$el || view)
|
||||
.show();
|
||||
Galaxy.trigger("center-panel:load", view);
|
||||
}
|
||||
},
|
||||
|
||||
/** */
|
||||
_iframeChangeHandler : function( ev ){
|
||||
var iframe = ev.currentTarget;
|
||||
var location = iframe.contentWindow && iframe.contentWindow.location;
|
||||
if( location && location.host ){
|
||||
// show the iframe and hide MVCview div, remove any views in the MVCview div
|
||||
$( iframe ).show();
|
||||
if( this.prev ){
|
||||
this.prev.remove();
|
||||
}
|
||||
this.$( '#center-panel' ).hide();
|
||||
// TODO: move to Galaxy
|
||||
Galaxy.trigger( 'galaxy_main:load', {
|
||||
fullpath: location.pathname + location.search + location.hash,
|
||||
pathname: location.pathname,
|
||||
search : location.search,
|
||||
hash : location.hash
|
||||
});
|
||||
}
|
||||
},
|
||||
template: function() {
|
||||
return (
|
||||
'<div class="center-container">' +
|
||||
'<iframe id="galaxy_main" name="galaxy_main" frameborder="0" class="center-frame" />' +
|
||||
'<div class="center-panel" />' +
|
||||
"</div>"
|
||||
);
|
||||
},
|
||||
|
||||
/** */
|
||||
display: function( view ) {
|
||||
// we need to display an MVC view: hide the iframe and show the other center panel
|
||||
// first checking for any onbeforeunload handlers on the iframe
|
||||
var contentWindow = this.$( '#galaxy_main' )[ 0 ].contentWindow || {};
|
||||
var message = contentWindow.onbeforeunload && contentWindow.onbeforeunload();
|
||||
if ( !message || confirm( message ) ) {
|
||||
contentWindow.onbeforeunload = undefined;
|
||||
// remove any previous views
|
||||
if( this.prev ){
|
||||
this.prev.remove();
|
||||
}
|
||||
this.prev = view;
|
||||
this.$( '#galaxy_main' ).attr( 'src', 'about:blank' ).hide();
|
||||
this.$( '#center-panel' ).scrollTop( 0 ).append( view.$el ).show();
|
||||
Galaxy.trigger( 'center-panel:load', view );
|
||||
toString: function() {
|
||||
return "CenterPanel";
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
if( view ){
|
||||
view.remove();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
template: function(){
|
||||
return [
|
||||
//TODO: remove inline styling
|
||||
'<div style="position: absolute; width: 100%; height: 100%">',
|
||||
'<iframe name="galaxy_main" id="galaxy_main" frameborder="0" ',
|
||||
'style="position: absolute; width: 100%; height: 100%;"/>',
|
||||
'<div id="center-panel" ',
|
||||
'style="display: none; position: absolute; width: 100%; height: 100%; padding: 10px; overflow: auto;"/>',
|
||||
'</div>'
|
||||
].join('');
|
||||
},
|
||||
|
||||
toString : function(){ return 'CenterPanel' }
|
||||
});
|
||||
|
||||
return {
|
||||
SidePanel : SidePanel,
|
||||
LeftPanel : LeftPanel,
|
||||
RightPanel : RightPanel,
|
||||
CenterPanel : CenterPanel
|
||||
};
|
||||
});
|
||||
export default {
|
||||
SidePanel: SidePanel,
|
||||
LeftPanel: LeftPanel,
|
||||
RightPanel: RightPanel,
|
||||
CenterPanel: CenterPanel
|
||||
};
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import jQuery from "jquery";
|
||||
var $ = jQuery;
|
||||
import QUERY_STRING from "utils/query-string-parsing";
|
||||
import Ui from "mvc/ui/ui-misc";
|
||||
|
||||
var Router = Backbone.Router.extend({
|
||||
// TODO: not many client routes at this point - fill and remove from server.
|
||||
// since we're at root here, this may be the last to be routed entirely on the client.
|
||||
initialize: function(page, options) {
|
||||
this.page = page;
|
||||
this.options = options;
|
||||
},
|
||||
|
||||
/** helper to push a new navigation state */
|
||||
push: function(url, data) {
|
||||
data = data || {};
|
||||
data.__identifer = Math.random()
|
||||
.toString(36)
|
||||
.substr(2);
|
||||
if (!$.isEmptyObject(data)) {
|
||||
url += url.indexOf("?") == -1 ? "?" : "&";
|
||||
url += $.param(data, true);
|
||||
}
|
||||
Galaxy.params = data;
|
||||
this.navigate(url, { trigger: true });
|
||||
},
|
||||
|
||||
/** override to parse query string into obj and send to each route */
|
||||
execute: function(callback, args, name) {
|
||||
Galaxy.debug("router execute:", callback, args, name);
|
||||
var queryObj = QUERY_STRING.parse(args.pop());
|
||||
args.push(queryObj);
|
||||
if (callback) {
|
||||
if (this.authenticate(args, name)) {
|
||||
callback.apply(this, args);
|
||||
} else {
|
||||
this.access_denied();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
authenticate: function(args, name) {
|
||||
return true;
|
||||
},
|
||||
|
||||
access_denied: function() {
|
||||
this.page.display(
|
||||
new Ui.Message({
|
||||
status: "danger",
|
||||
message: "You must be logged in with proper credentials to make this request.",
|
||||
persistent: true
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default Router;
|
||||
@@ -1,190 +1,230 @@
|
||||
/** Frame manager uses the ui-frames to create the scratch book masthead icon and functionality **/
|
||||
define([ 'mvc/ui/ui-frames' ], function( Frames ) {
|
||||
return Backbone.View.extend({
|
||||
initialize : function( options ) {
|
||||
import Frames from "mvc/ui/ui-frames";
|
||||
import DATA from "mvc/dataset/data";
|
||||
import visualization from "viz/visualization";
|
||||
import trackster from "viz/trackster";
|
||||
import _l from "utils/localization";
|
||||
export default Backbone.View.extend({
|
||||
initialize: function(options) {
|
||||
var self = this;
|
||||
options = options || {};
|
||||
this.frames = new Frames.View({ visible : false });
|
||||
this.setElement( this.frames.$el );
|
||||
this.frames = new Frames.View({ visible: false });
|
||||
this.setElement(this.frames.$el);
|
||||
this.buttonActive = options.collection.add({
|
||||
id : 'enable-scratchbook',
|
||||
icon : 'fa-th',
|
||||
tooltip : 'Enable/Disable Scratchbook',
|
||||
onclick : function() {
|
||||
id: "enable-scratchbook",
|
||||
icon: "fa-th",
|
||||
tooltip: _l("Enable/Disable Scratchbook"),
|
||||
onclick: function() {
|
||||
self.active = !self.active;
|
||||
self.buttonActive.set({
|
||||
toggle : self.active,
|
||||
show_note : self.active,
|
||||
note_cls : self.active && 'fa fa-check'
|
||||
toggle: self.active,
|
||||
show_note: self.active,
|
||||
note_cls: self.active && "fa fa-check"
|
||||
});
|
||||
!self.active && self.frames.hide();
|
||||
},
|
||||
onbeforeunload : function() {
|
||||
if ( self.frames.length() > 0 ) {
|
||||
return 'You opened ' + self.frames.length() + ' frame(s) which will be lost.';
|
||||
onbeforeunload: function() {
|
||||
if (self.frames.length() > 0) {
|
||||
return `You opened ${self.frames.length()} frame(s) which will be lost.`;
|
||||
}
|
||||
}
|
||||
});
|
||||
this.buttonLoad = options.collection.add({
|
||||
id : 'show-scratchbook',
|
||||
icon : 'fa-eye',
|
||||
tooltip : 'Show/Hide Scratchbook',
|
||||
show_note : true,
|
||||
visible : false,
|
||||
onclick : function( e ) {
|
||||
id: "show-scratchbook",
|
||||
icon: "fa-eye",
|
||||
tooltip: _l("Show/Hide Scratchbook"),
|
||||
show_note: true,
|
||||
visible: false,
|
||||
onclick: function(e) {
|
||||
self.frames.visible ? self.frames.hide() : self.frames.show();
|
||||
}
|
||||
});
|
||||
this.frames.on( 'add remove', function() {
|
||||
this.visible && this.length() == 0 && this.hide();
|
||||
self.buttonLoad.set( { 'note': this.length(), 'visible': this.length() > 0 } );
|
||||
}).on( 'show hide ', function() {
|
||||
self.buttonLoad.set( { 'toggle': this.visible, 'icon': this.visible && 'fa-eye' || 'fa-eye-slash' } );
|
||||
});
|
||||
this.frames
|
||||
.on("add remove", function() {
|
||||
this.visible && this.length() == 0 && this.hide();
|
||||
self.buttonLoad.set({
|
||||
note: this.length(),
|
||||
visible: this.length() > 0
|
||||
});
|
||||
})
|
||||
.on("show hide ", function() {
|
||||
self.buttonLoad.set({
|
||||
toggle: this.visible,
|
||||
icon: (this.visible && "fa-eye") || "fa-eye-slash"
|
||||
});
|
||||
});
|
||||
this.history_cache = {};
|
||||
},
|
||||
|
||||
/** Add a dataset to the frames */
|
||||
addDataset: function( dataset_id ) {
|
||||
addDataset: function(dataset_id) {
|
||||
var self = this;
|
||||
var current_dataset = null;
|
||||
if ( Galaxy && Galaxy.currHistoryPanel ) {
|
||||
if (Galaxy && Galaxy.currHistoryPanel) {
|
||||
var history_id = Galaxy.currHistoryPanel.collection.historyId;
|
||||
this.history_cache[ history_id ] = { name: Galaxy.currHistoryPanel.model.get( 'name' ), dataset_ids: [] };
|
||||
Galaxy.currHistoryPanel.collection.each( function( model ) {
|
||||
!model.get( 'deleted' ) && model.get( 'visible' ) && self.history_cache[ history_id ].dataset_ids.push( model.get( 'id' ) );
|
||||
this.history_cache[history_id] = {
|
||||
name: Galaxy.currHistoryPanel.model.get("name"),
|
||||
dataset_ids: []
|
||||
};
|
||||
Galaxy.currHistoryPanel.collection.each(model => {
|
||||
!model.get("deleted") &&
|
||||
model.get("visible") &&
|
||||
self.history_cache[history_id].dataset_ids.push(model.get("id"));
|
||||
});
|
||||
}
|
||||
var _findDataset = function( dataset, offset ) {
|
||||
if ( dataset ) {
|
||||
var history_details = self.history_cache[ dataset.get( 'history_id' ) ];
|
||||
if ( history_details && history_details.dataset_ids ) {
|
||||
var _findDataset = (dataset, offset) => {
|
||||
if (dataset) {
|
||||
var history_details = self.history_cache[dataset.get("history_id")];
|
||||
if (history_details && history_details.dataset_ids) {
|
||||
var dataset_list = history_details.dataset_ids;
|
||||
var pos = dataset_list.indexOf( dataset.get( 'id' ) );
|
||||
if ( pos !== -1 && pos + offset >= 0 && pos + offset < dataset_list.length ) {
|
||||
return dataset_list[ pos + offset ];
|
||||
var pos = dataset_list.indexOf(dataset.get("id"));
|
||||
if (pos !== -1 && pos + offset >= 0 && pos + offset < dataset_list.length) {
|
||||
return dataset_list[pos + offset];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
var _loadDatasetOffset = function( dataset, offset, frame ) {
|
||||
var new_dataset_id = _findDataset( dataset, offset );
|
||||
if ( new_dataset_id ) {
|
||||
self._loadDataset( new_dataset_id, function( new_dataset, config ) {
|
||||
var _loadDatasetOffset = (dataset, offset, frame) => {
|
||||
var new_dataset_id = _findDataset(dataset, offset);
|
||||
if (new_dataset_id) {
|
||||
self._loadDataset(new_dataset_id, (new_dataset, config) => {
|
||||
current_dataset = new_dataset;
|
||||
frame.model.set( config );
|
||||
frame.model.set(config);
|
||||
});
|
||||
} else {
|
||||
frame.model.trigger( 'change' );
|
||||
frame.model.trigger("change");
|
||||
}
|
||||
}
|
||||
this._loadDataset( dataset_id, function( dataset, config ) {
|
||||
};
|
||||
this._loadDataset(dataset_id, (dataset, config) => {
|
||||
current_dataset = dataset;
|
||||
self.add( _.extend( { menu: [ { icon : 'fa fa-chevron-circle-left',
|
||||
tooltip : 'Previous in History',
|
||||
onclick : function( frame ) { _loadDatasetOffset( current_dataset, -1, frame ) },
|
||||
disabled : function() { return !_findDataset( current_dataset, -1 ) } },
|
||||
{ icon : 'fa fa-chevron-circle-right',
|
||||
tooltip : 'Next in History',
|
||||
onclick : function( frame ) { _loadDatasetOffset( current_dataset, 1, frame ) },
|
||||
disabled : function() { return !_findDataset( current_dataset, 1 ) } } ] }, config ) )
|
||||
self.add(
|
||||
_.extend(
|
||||
{
|
||||
menu: [
|
||||
{
|
||||
icon: "fa fa-chevron-circle-left",
|
||||
tooltip: _l("Previous in History"),
|
||||
onclick: function(frame) {
|
||||
_loadDatasetOffset(current_dataset, -1, frame);
|
||||
},
|
||||
disabled: function() {
|
||||
return !_findDataset(current_dataset, -1);
|
||||
}
|
||||
},
|
||||
{
|
||||
icon: "fa fa-chevron-circle-right",
|
||||
tooltip: _l("Next in History"),
|
||||
onclick: function(frame) {
|
||||
_loadDatasetOffset(current_dataset, 1, frame);
|
||||
},
|
||||
disabled: function() {
|
||||
return !_findDataset(current_dataset, 1);
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
config
|
||||
)
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
_loadDataset: function( dataset_id, callback ) {
|
||||
_loadDataset: function(dataset_id, callback) {
|
||||
var self = this;
|
||||
require([ 'mvc/dataset/data' ], function( DATA ) {
|
||||
var dataset = new DATA.Dataset( { id : dataset_id } );
|
||||
$.when( dataset.fetch() ).then( function() {
|
||||
var is_tabular = _.find( [ 'tabular', 'interval' ] , function( data_type ) {
|
||||
return dataset.get( 'data_type' ).indexOf( data_type ) !== -1;
|
||||
});
|
||||
var title = dataset.get( 'name' );
|
||||
var history_details = self.history_cache[ dataset.get( 'history_id' ) ];
|
||||
if ( history_details ) {
|
||||
title = history_details.name + ': ' + title;
|
||||
}
|
||||
callback( dataset, is_tabular ? {
|
||||
title : title,
|
||||
url : null,
|
||||
content : DATA.createTabularDatasetChunkedView({
|
||||
model : new DATA.TabularDataset( dataset.toJSON() ),
|
||||
embedded : true,
|
||||
height : '100%'
|
||||
}).$el
|
||||
} : {
|
||||
title : title,
|
||||
url : Galaxy.root + 'datasets/' + dataset_id + '/display/?preview=True',
|
||||
content : null
|
||||
} );
|
||||
});
|
||||
var dataset = new DATA.Dataset({ id: dataset_id });
|
||||
$.when(dataset.fetch()).then(() => {
|
||||
var is_tabular = _.find(
|
||||
["tabular", "interval"],
|
||||
data_type => dataset.get("data_type").indexOf(data_type) !== -1
|
||||
);
|
||||
var title = dataset.get("name");
|
||||
var history_details = self.history_cache[dataset.get("history_id")];
|
||||
if (history_details) {
|
||||
title = `${history_details.name}: ${title}`;
|
||||
}
|
||||
callback(
|
||||
dataset,
|
||||
is_tabular
|
||||
? {
|
||||
title: title,
|
||||
url: null,
|
||||
content: DATA.createTabularDatasetChunkedView({
|
||||
model: new DATA.TabularDataset(dataset.toJSON()),
|
||||
embedded: true,
|
||||
height: "100%"
|
||||
}).$el
|
||||
}
|
||||
: {
|
||||
title: title,
|
||||
url: `${Galaxy.root}datasets/${dataset_id}/display/?preview=True`,
|
||||
content: null
|
||||
}
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
/** Add a trackster visualization to the frames. */
|
||||
addTrackster: function(viz_id) {
|
||||
var self = this;
|
||||
require(['viz/visualization', 'viz/trackster'], function(visualization, trackster) {
|
||||
var viz = new visualization.Visualization({id: viz_id});
|
||||
$.when( viz.fetch() ).then( function() {
|
||||
var ui = new trackster.TracksterUI(Galaxy.root);
|
||||
var viz = new visualization.Visualization({ id: viz_id });
|
||||
$.when(viz.fetch()).then(() => {
|
||||
var ui = new trackster.TracksterUI(Galaxy.root);
|
||||
|
||||
// Construct frame config based on dataset's type.
|
||||
var frame_config = {
|
||||
title: viz.get('name'),
|
||||
type: 'other',
|
||||
content: function(parent_elt) {
|
||||
// Create view config.
|
||||
var view_config = {
|
||||
container: parent_elt,
|
||||
name: viz.get('title'),
|
||||
id: viz.id,
|
||||
// FIXME: this will not work with custom builds b/c the dbkey needed to be encoded.
|
||||
dbkey: viz.get('dbkey'),
|
||||
stand_alone: false
|
||||
},
|
||||
latest_revision = viz.get('latest_revision'),
|
||||
drawables = latest_revision.config.view.drawables;
|
||||
|
||||
// Set up datasets in drawables.
|
||||
_.each(drawables, function(d) {
|
||||
d.dataset = {
|
||||
hda_ldda: d.hda_ldda,
|
||||
id: d.dataset_id
|
||||
};
|
||||
});
|
||||
view = ui.create_visualization(view_config,
|
||||
latest_revision.config.viewport,
|
||||
latest_revision.config.view.drawables,
|
||||
latest_revision.config.bookmarks,
|
||||
false);
|
||||
}
|
||||
// Construct frame config based on dataset's type.
|
||||
var frame_config = {
|
||||
title: viz.get("name"),
|
||||
type: "other",
|
||||
content: function(parent_elt) {
|
||||
// Create view config.
|
||||
var view_config = {
|
||||
container: parent_elt,
|
||||
name: viz.get("title"),
|
||||
id: viz.id,
|
||||
// FIXME: this will not work with custom builds b/c the dbkey needed to be encoded.
|
||||
dbkey: viz.get("dbkey"),
|
||||
stand_alone: false
|
||||
};
|
||||
self.add(frame_config);
|
||||
});
|
||||
|
||||
var latest_revision = viz.get("latest_revision");
|
||||
var drawables = latest_revision.config.view.drawables;
|
||||
|
||||
// Set up datasets in drawables.
|
||||
_.each(drawables, d => {
|
||||
d.dataset = {
|
||||
hda_ldda: d.hda_ldda,
|
||||
id: d.dataset_id
|
||||
};
|
||||
});
|
||||
view = ui.create_visualization(
|
||||
view_config,
|
||||
latest_revision.config.viewport,
|
||||
latest_revision.config.view.drawables,
|
||||
latest_revision.config.bookmarks,
|
||||
false
|
||||
);
|
||||
}
|
||||
};
|
||||
self.add(frame_config);
|
||||
});
|
||||
},
|
||||
|
||||
/** Add and display a new frame/window based on options. */
|
||||
add: function( options ) {
|
||||
if ( options.target == '_blank' ) {
|
||||
window.open( options.url );
|
||||
} else if ( options.target == '_top' || options.target == '_parent' || options.target == '_self' ) {
|
||||
add: function(options) {
|
||||
if (options.target == "_blank") {
|
||||
window.open(options.url);
|
||||
} else if (options.target == "_top" || options.target == "_parent" || options.target == "_self") {
|
||||
window.location = options.url;
|
||||
} else if ( !this.active || options.noscratchbook ) {
|
||||
var $galaxy_main = $( window.parent.document ).find( '#galaxy_main' );
|
||||
if ( options.target == 'galaxy_main' || options.target == 'center' ) {
|
||||
if ( $galaxy_main.length === 0 ) {
|
||||
window.location = options.url + ( options.url.indexOf( '?' ) == -1 ? '?' : '&' ) + 'use_panels=True';
|
||||
} else if (!this.active || options.noscratchbook) {
|
||||
var $galaxy_main = $(window.parent.document).find("#galaxy_main");
|
||||
if (options.target == "galaxy_main" || options.target == "center") {
|
||||
if ($galaxy_main.length === 0) {
|
||||
window.location = `${options.url + (options.url.indexOf("?") == -1 ? "?" : "&")}use_panels=True`;
|
||||
} else {
|
||||
$galaxy_main.attr( 'src', options.url );
|
||||
$galaxy_main.attr("src", options.url);
|
||||
}
|
||||
} else
|
||||
window.location = options.url;
|
||||
} else window.location = options.url;
|
||||
} else {
|
||||
this.frames.add( options );
|
||||
this.frames.add(options);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Vendored
-9554
File diff suppressed because it is too large
Load Diff
@@ -1,192 +0,0 @@
|
||||
/*
|
||||
http://github.com/danpalmer/jquery.complexify.js
|
||||
|
||||
This code is distributed under the WTFPL v2:
|
||||
*/
|
||||
(function ($) {
|
||||
|
||||
$.fn.extend({
|
||||
complexify: function(options, callback) {
|
||||
|
||||
var MIN_COMPLEXITY = 49; // 12 chars with Upper, Lower and Number
|
||||
var MAX_COMPLEXITY = 120; // 25 chars, all charsets
|
||||
var CHARSETS = [
|
||||
// Commonly Used
|
||||
////////////////////
|
||||
[0x0020, 0x0020], // Space
|
||||
[0x0030, 0x0039], // Numbers
|
||||
[0x0041, 0x005A], // Uppercase
|
||||
[0x0061, 0x007A], // Lowercase
|
||||
[0x0021, 0x002F], // Punctuation
|
||||
[0x003A, 0x0040], // Punctuation
|
||||
[0x005B, 0x0060], // Punctuation
|
||||
[0x007B, 0x007E], // Punctuation
|
||||
// Everything Else
|
||||
////////////////////
|
||||
[0x0080, 0x00FF], // Latin-1 Supplement
|
||||
[0x0100, 0x017F], // Latin Extended-A
|
||||
[0x0180, 0x024F], // Latin Extended-B
|
||||
[0x0250, 0x02AF], // IPA Extensions
|
||||
[0x02B0, 0x02FF], // Spacing Modifier Letters
|
||||
[0x0300, 0x036F], // Combining Diacritical Marks
|
||||
[0x0370, 0x03FF], // Greek
|
||||
[0x0400, 0x04FF], // Cyrillic
|
||||
[0x0530, 0x058F], // Armenian
|
||||
[0x0590, 0x05FF], // Hebrew
|
||||
[0x0600, 0x06FF], // Arabic
|
||||
[0x0700, 0x074F], // Syriac
|
||||
[0x0780, 0x07BF], // Thaana
|
||||
[0x0900, 0x097F], // Devanagari
|
||||
[0x0980, 0x09FF], // Bengali
|
||||
[0x0A00, 0x0A7F], // Gurmukhi
|
||||
[0x0A80, 0x0AFF], // Gujarati
|
||||
[0x0B00, 0x0B7F], // Oriya
|
||||
[0x0B80, 0x0BFF], // Tamil
|
||||
[0x0C00, 0x0C7F], // Telugu
|
||||
[0x0C80, 0x0CFF], // Kannada
|
||||
[0x0D00, 0x0D7F], // Malayalam
|
||||
[0x0D80, 0x0DFF], // Sinhala
|
||||
[0x0E00, 0x0E7F], // Thai
|
||||
[0x0E80, 0x0EFF], // Lao
|
||||
[0x0F00, 0x0FFF], // Tibetan
|
||||
[0x1000, 0x109F], // Myanmar
|
||||
[0x10A0, 0x10FF], // Georgian
|
||||
[0x1100, 0x11FF], // Hangul Jamo
|
||||
[0x1200, 0x137F], // Ethiopic
|
||||
[0x13A0, 0x13FF], // Cherokee
|
||||
[0x1400, 0x167F], // Unified Canadian Aboriginal Syllabics
|
||||
[0x1680, 0x169F], // Ogham
|
||||
[0x16A0, 0x16FF], // Runic
|
||||
[0x1780, 0x17FF], // Khmer
|
||||
[0x1800, 0x18AF], // Mongolian
|
||||
[0x1E00, 0x1EFF], // Latin Extended Additional
|
||||
[0x1F00, 0x1FFF], // Greek Extended
|
||||
[0x2000, 0x206F], // General Punctuation
|
||||
[0x2070, 0x209F], // Superscripts and Subscripts
|
||||
[0x20A0, 0x20CF], // Currency Symbols
|
||||
[0x20D0, 0x20FF], // Combining Marks for Symbols
|
||||
[0x2100, 0x214F], // Letterlike Symbols
|
||||
[0x2150, 0x218F], // Number Forms
|
||||
[0x2190, 0x21FF], // Arrows
|
||||
[0x2200, 0x22FF], // Mathematical Operators
|
||||
[0x2300, 0x23FF], // Miscellaneous Technical
|
||||
[0x2400, 0x243F], // Control Pictures
|
||||
[0x2440, 0x245F], // Optical Character Recognition
|
||||
[0x2460, 0x24FF], // Enclosed Alphanumerics
|
||||
[0x2500, 0x257F], // Box Drawing
|
||||
[0x2580, 0x259F], // Block Elements
|
||||
[0x25A0, 0x25FF], // Geometric Shapes
|
||||
[0x2600, 0x26FF], // Miscellaneous Symbols
|
||||
[0x2700, 0x27BF], // Dingbats
|
||||
[0x2800, 0x28FF], // Braille Patterns
|
||||
[0x2E80, 0x2EFF], // CJK Radicals Supplement
|
||||
[0x2F00, 0x2FDF], // Kangxi Radicals
|
||||
[0x2FF0, 0x2FFF], // Ideographic Description Characters
|
||||
[0x3000, 0x303F], // CJK Symbols and Punctuation
|
||||
[0x3040, 0x309F], // Hiragana
|
||||
[0x30A0, 0x30FF], // Katakana
|
||||
[0x3100, 0x312F], // Bopomofo
|
||||
[0x3130, 0x318F], // Hangul Compatibility Jamo
|
||||
[0x3190, 0x319F], // Kanbun
|
||||
[0x31A0, 0x31BF], // Bopomofo Extended
|
||||
[0x3200, 0x32FF], // Enclosed CJK Letters and Months
|
||||
[0x3300, 0x33FF], // CJK Compatibility
|
||||
[0x3400, 0x4DB5], // CJK Unified Ideographs Extension A
|
||||
[0x4E00, 0x9FFF], // CJK Unified Ideographs
|
||||
[0xA000, 0xA48F], // Yi Syllables
|
||||
[0xA490, 0xA4CF], // Yi Radicals
|
||||
[0xAC00, 0xD7A3], // Hangul Syllables
|
||||
[0xD800, 0xDB7F], // High Surrogates
|
||||
[0xDB80, 0xDBFF], // High Private Use Surrogates
|
||||
[0xDC00, 0xDFFF], // Low Surrogates
|
||||
[0xE000, 0xF8FF], // Private Use
|
||||
[0xF900, 0xFAFF], // CJK Compatibility Ideographs
|
||||
[0xFB00, 0xFB4F], // Alphabetic Presentation Forms
|
||||
[0xFB50, 0xFDFF], // Arabic Presentation Forms-A
|
||||
[0xFE20, 0xFE2F], // Combining Half Marks
|
||||
[0xFE30, 0xFE4F], // CJK Compatibility Forms
|
||||
[0xFE50, 0xFE6F], // Small Form Variants
|
||||
[0xFE70, 0xFEFE], // Arabic Presentation Forms-B
|
||||
[0xFEFF, 0xFEFF], // Specials
|
||||
[0xFF00, 0xFFEF], // Halfwidth and Fullwidth Forms
|
||||
[0xFFF0, 0xFFFD] // Specials
|
||||
];
|
||||
|
||||
var defaults = {
|
||||
minimumChars: 8,
|
||||
strengthScaleFactor: 1,
|
||||
bannedPasswords: window.COMPLEXIFY_BANLIST || [],
|
||||
banMode: 'strict' // (strict|loose)
|
||||
};
|
||||
|
||||
if($.isFunction(options) && !callback) {
|
||||
callback = options;
|
||||
options = {};
|
||||
}
|
||||
|
||||
options = $.extend(defaults, options);
|
||||
|
||||
function additionalComplexityForCharset(str, charset) {
|
||||
for (var i = str.length - 1; i >= 0; i--) {
|
||||
if (charset[0] <= str.charCodeAt(i) && str.charCodeAt(i) <= charset[1]) {
|
||||
return charset[1] - charset[0] + 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function inBanlist(str) {
|
||||
if (options.banMode === 'strict') {
|
||||
for (var i = 0; i < options.bannedPasswords.length; i++) {
|
||||
if (str.toLowerCase().indexOf(options.bannedPasswords[i].toLowerCase()) !== -1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
return $.inArray(str, options.bannedPasswords) > -1 ? true : false;
|
||||
}
|
||||
}
|
||||
|
||||
function evaluateSecurity() {
|
||||
var password = $(this).val();
|
||||
var complexity = 0, valid = false;
|
||||
|
||||
// Reset complexity to 0 when banned password is found
|
||||
if (!inBanlist(password)) {
|
||||
|
||||
// Add character complexity
|
||||
for (var i = CHARSETS.length - 1; i >= 0; i--) {
|
||||
complexity += additionalComplexityForCharset(password, CHARSETS[i]);
|
||||
}
|
||||
|
||||
} else {
|
||||
complexity = 1;
|
||||
}
|
||||
|
||||
// Use natural log to produce linear scale
|
||||
complexity = Math.log(Math.pow(complexity, password.length)) * (1/options.strengthScaleFactor);
|
||||
|
||||
valid = (complexity > MIN_COMPLEXITY && password.length >= options.minimumChars);
|
||||
|
||||
// Scale to percentage, so it can be used for a progress bar
|
||||
complexity = (complexity / MAX_COMPLEXITY) * 100;
|
||||
complexity = (complexity > 100) ? 100 : complexity;
|
||||
|
||||
callback.call(this, valid, complexity);
|
||||
}
|
||||
|
||||
this.each(function () {
|
||||
if($(this).val()) {
|
||||
evaluateSecurity.apply(this);
|
||||
}
|
||||
});
|
||||
|
||||
return this.each(function () {
|
||||
$(this).bind('keyup focus input propertychange mouseup', evaluateSecurity);
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
})(jQuery);
|
||||
-883
@@ -1,883 +0,0 @@
|
||||
(function() {
|
||||
|
||||
$.jQTouch = function(options) {
|
||||
// Initialize internal jQT variables
|
||||
var $body,
|
||||
$head=$('head'),
|
||||
history=[],
|
||||
newPageCount=0,
|
||||
jQTSettings={},
|
||||
$currentPage='',
|
||||
orientation='portrait',
|
||||
touchSelectors=[],
|
||||
publicObj={},
|
||||
tapBuffer=100, // High click delay = ~350, quickest animation (slide) = 250
|
||||
extensions=$.jQTouch.prototype.extensions,
|
||||
extTapHandlers=$.jQTouch.prototype.tapHandlers,
|
||||
tapHandlers=[],
|
||||
animations=[],
|
||||
hairExtensions='',
|
||||
defaults = {
|
||||
addGlossToIcon: true,
|
||||
backSelector: '.back, .cancel, .goback',
|
||||
cacheGetRequests: true,
|
||||
debug: true,
|
||||
defaultAnimation: 'slideleft',
|
||||
fixedViewport: true,
|
||||
formSelector: 'form',
|
||||
fullScreen: true,
|
||||
fullScreenClass: 'fullscreen',
|
||||
icon: null,
|
||||
icon4: null, // available in iOS 4.2 and later
|
||||
preloadImages: false,
|
||||
starter: $(document).ready,
|
||||
startupScreen: null,
|
||||
statusBar: 'default', // other options: black-translucent, black
|
||||
submitSelector: '.submit',
|
||||
touchSelector: 'a, .touch',
|
||||
trackScrollPositions: true,
|
||||
updateHash: true,
|
||||
useAnimations: true,
|
||||
useFastTouch: true,
|
||||
useTouchScroll: true,
|
||||
animations: [ // highest to lowest priority
|
||||
{name:'cubeleft', selector:'.cubeleft, .cube', is3d: true},
|
||||
{name:'cuberight', selector:'.cuberight', is3d: true},
|
||||
{name:'dissolve', selector:'.dissolve'},
|
||||
{name:'fade', selector:'.fade'},
|
||||
{name:'flipleft', selector:'.flipleft, .flip', is3d: true},
|
||||
{name:'flipright', selector:'.flipright', is3d: true},
|
||||
{name:'pop', selector:'.pop', is3d: true},
|
||||
{name:'swapleft', selector:'.swapleft, .swap', is3d: true},
|
||||
{name:'swapright', selector:'.swapright', is3d: true},
|
||||
{name:'slidedown', selector:'.slidedown'},
|
||||
{name:'slideright', selector:'.slideright'},
|
||||
{name:'slideup', selector:'.slideup'},
|
||||
{name:'slideleft', selector:'.slideleft, .slide, #jqt > * > ul li a'}
|
||||
]
|
||||
}; // end defaults
|
||||
|
||||
function warn(message) {
|
||||
if (window.console !== undefined && jQTSettings.debug === true) {
|
||||
console.warn(message);
|
||||
}
|
||||
}
|
||||
|
||||
function addAnimation(animation) {
|
||||
if (typeof(animation.selector) === 'string' && typeof(animation.name) === 'string') {
|
||||
animations.push(animation);
|
||||
}
|
||||
}
|
||||
|
||||
function addTapHandler(tapHandler) {
|
||||
if (typeof(tapHandler.name) === 'string' && typeof(tapHandler.isSupported) === 'function' && typeof(tapHandler.fn) === 'function') {
|
||||
tapHandlers.push(tapHandler);
|
||||
}
|
||||
}
|
||||
|
||||
function addPageToHistory(page, animation) {
|
||||
history.unshift({
|
||||
page: page,
|
||||
animation: animation,
|
||||
hash: '#' + page.attr('id'),
|
||||
id: page.attr('id')
|
||||
});
|
||||
}
|
||||
|
||||
// Unfortunately, we can not assume the 'tap' event
|
||||
// is being used for links, forms, etc.
|
||||
function clickHandler(e) {
|
||||
// Figure out whether to prevent default
|
||||
var $el = $(e.target);
|
||||
|
||||
// Find the nearest tappable ancestor
|
||||
if (!$el.is(touchSelectors.join(', '))) {
|
||||
$el = $(e.target).closest(touchSelectors.join(', '));
|
||||
}
|
||||
|
||||
// Prevent default if we found an internal link
|
||||
// (relative or absolute)
|
||||
if ($el && $el.attr('href') && !$el.isExternalLink()) {
|
||||
warn('Need to prevent default click behavior.');
|
||||
e.preventDefault();
|
||||
} else {
|
||||
warn('No need to prevent default click behavior.');
|
||||
}
|
||||
|
||||
// Trigger a tap event if touchstart is not on the job
|
||||
if ($.support.touch) {
|
||||
warn('Not converting click to a tap event because touch handler is on the job.');
|
||||
} else {
|
||||
warn('Converting click event to a tap event because touch handlers are not present or off.');
|
||||
$(e.target).trigger('tap', e);
|
||||
}
|
||||
}
|
||||
|
||||
function doNavigation(fromPage, toPage, animation, goingBack) {
|
||||
|
||||
goingBack = goingBack ? goingBack : false;
|
||||
|
||||
// Error check for target page
|
||||
if (toPage === undefined || toPage.length === 0) {
|
||||
$.fn.unselect();
|
||||
warn('Target element is missing.');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Error check for fromPage === toPage
|
||||
if (toPage.hasClass('current')) {
|
||||
$.fn.unselect();
|
||||
warn('You are already on the page you are trying to navigate to.');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Collapse the keyboard
|
||||
$(':focus').trigger('blur');
|
||||
|
||||
fromPage.trigger('pageAnimationStart', { direction: 'out', back: goingBack });
|
||||
toPage.trigger('pageAnimationStart', { direction: 'in', back: goingBack });
|
||||
|
||||
if ($.support.animationEvents && animation && jQTSettings.useAnimations) {
|
||||
// Fail over to 2d animation if need be
|
||||
if (!$.support.transform3d && animation.is3d) {
|
||||
warn('Did not detect support for 3d animations, falling back to ' + jQTSettings.defaultAnimation + '.');
|
||||
animation.name = jQTSettings.defaultAnimation;
|
||||
}
|
||||
|
||||
// Reverse animation if need be
|
||||
var finalAnimationName = animation.name,
|
||||
is3d = animation.is3d ? 'animating3d' : '';
|
||||
|
||||
if (goingBack) {
|
||||
finalAnimationName = finalAnimationName.replace(/left|right|up|down|in|out/, reverseAnimation);
|
||||
}
|
||||
|
||||
warn('finalAnimationName is ' + finalAnimationName + '.');
|
||||
|
||||
// Bind internal 'cleanup' callback
|
||||
fromPage.bind('webkitAnimationEnd', navigationEndHandler);
|
||||
|
||||
// Trigger animations
|
||||
$body.addClass('animating ' + is3d);
|
||||
|
||||
var lastScroll = window.pageYOffset;
|
||||
|
||||
// Position the incoming page so toolbar is at top of
|
||||
// viewport regardless of scroll position on from page
|
||||
if (jQTSettings.trackScrollPositions === true) {
|
||||
toPage.css('top', window.pageYOffset - (toPage.data('lastScroll') || 0));
|
||||
}
|
||||
|
||||
toPage.addClass(finalAnimationName + ' in current');
|
||||
fromPage.removeClass('current').addClass(finalAnimationName + ' out inmotion');
|
||||
|
||||
if (jQTSettings.trackScrollPositions === true) {
|
||||
fromPage.data('lastScroll', lastScroll);
|
||||
$('.scroll', fromPage).each(function() {
|
||||
$(this).data('lastScroll', this.scrollTop);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
toPage.addClass('current in');
|
||||
fromPage.removeClass('current');
|
||||
navigationEndHandler();
|
||||
}
|
||||
|
||||
// Housekeeping
|
||||
$currentPage = toPage;
|
||||
if (goingBack) {
|
||||
history.shift();
|
||||
} else {
|
||||
addPageToHistory($currentPage, animation);
|
||||
}
|
||||
setHash($currentPage.attr('id'));
|
||||
|
||||
// Private navigationEnd callback
|
||||
function navigationEndHandler(event) {
|
||||
var bufferTime = tapBuffer;
|
||||
|
||||
if ($.support.animationEvents && animation && jQTSettings.useAnimations) {
|
||||
fromPage.unbind('webkitAnimationEnd', navigationEndHandler);
|
||||
fromPage.removeClass(finalAnimationName + ' out inmotion');
|
||||
if (finalAnimationName) {
|
||||
toPage.removeClass(finalAnimationName);
|
||||
}
|
||||
$body.removeClass('animating animating3d');
|
||||
if (jQTSettings.trackScrollPositions === true) {
|
||||
toPage.css('top', -toPage.data('lastScroll'));
|
||||
|
||||
// Have to make sure the scroll/style resets
|
||||
// are outside the flow of this function.
|
||||
setTimeout(function() {
|
||||
toPage.css('top', 0);
|
||||
window.scroll(0, toPage.data('lastScroll'));
|
||||
$('.scroll', toPage).each(function() {
|
||||
this.scrollTop = - $(this).data('lastScroll');
|
||||
});
|
||||
}, 0);
|
||||
}
|
||||
} else {
|
||||
fromPage.removeClass(finalAnimationName + ' out inmotion');
|
||||
if (finalAnimationName) {
|
||||
toPage.removeClass(finalAnimationName);
|
||||
}
|
||||
bufferTime += 260;
|
||||
}
|
||||
|
||||
// 'in' class is intentionally delayed,
|
||||
// as it is our ghost click hack
|
||||
setTimeout(function() {
|
||||
toPage.removeClass('in');
|
||||
window.scroll(0,0);
|
||||
}, bufferTime);
|
||||
|
||||
fromPage.unselect();
|
||||
|
||||
// Trigger custom events
|
||||
toPage.trigger('pageAnimationEnd', {
|
||||
direction:'in',
|
||||
animation: animation,
|
||||
back: goingBack
|
||||
});
|
||||
fromPage.trigger('pageAnimationEnd', {
|
||||
direction:'out',
|
||||
animation: animation,
|
||||
back: goingBack
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function reverseAnimation(animation) {
|
||||
var opposites={
|
||||
'up' : 'down',
|
||||
'down' : 'up',
|
||||
'left' : 'right',
|
||||
'right' : 'left',
|
||||
'in' : 'out',
|
||||
'out' : 'in'
|
||||
};
|
||||
|
||||
return opposites[animation] || animation;
|
||||
}
|
||||
|
||||
function getOrientation() {
|
||||
return orientation;
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
// Error checking
|
||||
if (history.length < 1) {
|
||||
warn('History is empty.');
|
||||
}
|
||||
|
||||
if (history.length === 1) {
|
||||
warn('You are on the first panel.');
|
||||
window.history.go(-1);
|
||||
}
|
||||
|
||||
var from = history[0],
|
||||
to = history[1];
|
||||
|
||||
if (doNavigation(from.page, to.page, from.animation, true)) {
|
||||
return publicObj;
|
||||
} else {
|
||||
warn('Could not go back.');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function goTo(toPage, animation) {
|
||||
var fromPage = history[0].page;
|
||||
|
||||
if (typeof animation === 'string') {
|
||||
for (var i=0, max=animations.length; i < max; i++) {
|
||||
if (animations[i].name === animation) {
|
||||
animation = animations[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof toPage === 'string') {
|
||||
var nextPage = $(toPage);
|
||||
|
||||
if (nextPage.length < 1) {
|
||||
showPageByHref(toPage, {
|
||||
animation: animation
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
toPage = nextPage;
|
||||
}
|
||||
}
|
||||
if (doNavigation(fromPage, toPage, animation)) {
|
||||
return publicObj;
|
||||
} else {
|
||||
warn('Could not animate pages.');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function hashChangeHandler(e) {
|
||||
if (location.hash === history[0].hash) {
|
||||
warn('We are on the right panel.');
|
||||
return true;
|
||||
} else if (location.hash === '') {
|
||||
goBack();
|
||||
return true;
|
||||
} else if (history[1] && location.hash === history[1].hash) {
|
||||
goBack();
|
||||
return true;
|
||||
} else {
|
||||
// Lastly, just try going to the ID...
|
||||
warn('Could not find ID in history, just forwarding to DOM element.');
|
||||
goTo($(location.hash), jQTSettings.defaultAnimation);
|
||||
}
|
||||
}
|
||||
|
||||
function initHairExtensions(options) {
|
||||
// Preload images
|
||||
if (jQTSettings.preloadImages) {
|
||||
for (var i = jQTSettings.preloadImages.length - 1; i >= 0; i--) {
|
||||
(new Image()).src = jQTSettings.preloadImages[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Set appropriate icon
|
||||
// (retina display available in iOS 4.2 and later.)
|
||||
var precomposed = (jQTSettings.addGlossToIcon) ? '' : '-precomposed';
|
||||
if (jQTSettings.icon) {
|
||||
hairExtensions += '<link rel="apple-touch-icon' + precomposed + '" href="' + jQTSettings.icon + '" />';
|
||||
}
|
||||
if (jQTSettings.icon4) {
|
||||
hairExtensions += '<link rel="apple-touch-icon' + precomposed + '" sizes="114x114" href="' + jQTSettings.icon4 + '" />';
|
||||
}
|
||||
// Set startup screen
|
||||
if (jQTSettings.startupScreen) {
|
||||
hairExtensions += '<link rel="apple-touch-startup-image" href="' + jQTSettings.startupScreen + '" />';
|
||||
}
|
||||
|
||||
// Set viewport
|
||||
if (jQTSettings.fixedViewport) {
|
||||
hairExtensions += '<meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=0"/>';
|
||||
}
|
||||
|
||||
// Set full-screen
|
||||
if (jQTSettings.fullScreen) {
|
||||
hairExtensions += '<meta name="apple-mobile-web-app-capable" content="yes" />';
|
||||
if (jQTSettings.statusBar) {
|
||||
hairExtensions += '<meta name="apple-mobile-web-app-status-bar-style" content="' + jQTSettings.statusBar + '" />';
|
||||
}
|
||||
}
|
||||
|
||||
// Attach hair extensions
|
||||
if (hairExtensions) {
|
||||
$head.prepend(hairExtensions);
|
||||
}
|
||||
}
|
||||
|
||||
function initFXExtensions() {
|
||||
// Define public jQuery functions
|
||||
$.fn.isExternalLink = function() {
|
||||
var $el = $(this);
|
||||
return ($el.attr('target') === '_blank' || $el.attr('rel') === 'external' || $el.is('a[href^="http://maps.google.com"], a[href^="mailto:"], a[href^="tel:"], a[href^="javascript:"], a[href*="youtube.com/v"], a[href*="youtube.com/watch"]'));
|
||||
};
|
||||
$.fn.makeActive = function() {
|
||||
return $(this).addClass('active');
|
||||
};
|
||||
$.fn.unselect = function(obj) {
|
||||
if (obj) {
|
||||
obj.removeClass('active');
|
||||
} else {
|
||||
$('.active').removeClass('active');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function getAnimation(el) {
|
||||
var animation;
|
||||
|
||||
for (var i=0, max=animations.length; i < max; i++) {
|
||||
if (el.is(animations[i].selector)) {
|
||||
animation = animations[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!animation) {
|
||||
warn('Animation could not be found. Using ' + jQTSettings.defaultAnimation + '.');
|
||||
animation = jQTSettings.defaultAnimation;
|
||||
}
|
||||
return animation;
|
||||
}
|
||||
|
||||
function insertPages(nodes, animation) {
|
||||
|
||||
var targetPage = null;
|
||||
|
||||
// Call dom.createElement element directly
|
||||
// instead of relying on $(nodes), to work around:
|
||||
// https://github.com/madrobby/zepto/issues/312
|
||||
var div = document.createElement('div');
|
||||
div.innerHTML = nodes;
|
||||
|
||||
$(div).children().each(function(index, node) {
|
||||
var $node = $(this);
|
||||
if (!$node.attr('id')) {
|
||||
$node.attr('id', 'page-' + (++newPageCount));
|
||||
}
|
||||
|
||||
// Remove any existing instance
|
||||
$('#' + $node.attr('id')).remove();
|
||||
|
||||
$body.append($node);
|
||||
$body.trigger('pageInserted', {page: $node});
|
||||
|
||||
if ($node.hasClass('current') || !targetPage) {
|
||||
targetPage = $node;
|
||||
}
|
||||
});
|
||||
if (targetPage !== null) {
|
||||
goTo(targetPage, animation);
|
||||
return targetPage;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function orientationChangeHandler() {
|
||||
scrollTo(0,0);
|
||||
orientation = Math.abs(window.orientation) === 90 ? 'landscape' : 'portrait';
|
||||
$body.removeClass('portrait landscape').addClass(orientation).trigger('turn', {orientation: orientation});
|
||||
}
|
||||
|
||||
function setHash(hash) {
|
||||
// Sanitize
|
||||
if (jQTSettings.updateHash) {
|
||||
location.hash = '#' + hash.replace(/^#/, '');
|
||||
}
|
||||
}
|
||||
|
||||
// Document ready stuff
|
||||
function start() {
|
||||
// Store some properties in a support object
|
||||
if (!$.support) $.support = {};
|
||||
$.support.animationEvents = (typeof window.WebKitAnimationEvent !== 'undefined');
|
||||
$.support.touch = (typeof window.TouchEvent !== 'undefined') && (window.navigator.userAgent.indexOf('Mobile') > -1) && jQTSettings.useFastTouch;
|
||||
$.support.transform3d = supportForTransform3d();
|
||||
$.support.ios5 = supportIOS5();
|
||||
|
||||
if (!$.support.touch) {
|
||||
warn('This device does not support touch interaction, or it has been deactivated by the developer. Some features might be unavailable.');
|
||||
}
|
||||
if (!$.support.transform3d) {
|
||||
warn('This device does not support 3d animation. 2d animations will be used instead.');
|
||||
}
|
||||
|
||||
// Add extensions
|
||||
for (var i=0, max=extensions.length; i < max; i++) {
|
||||
var fn = extensions[i];
|
||||
if ($.isFunction(fn)) {
|
||||
$.extend(publicObj, fn(publicObj));
|
||||
}
|
||||
}
|
||||
|
||||
// Add extensions tapHandlers
|
||||
for (var j=0, maxTapHandlers=extTapHandlers.length; j < maxTapHandlers; j++) {
|
||||
addTapHandler(extTapHandlers[j]);
|
||||
}
|
||||
// Add default tapHandlers
|
||||
addDefaultTapHandlers();
|
||||
|
||||
// Add animations
|
||||
for (var k=0, maxAnimations=defaults.animations.length; k < maxAnimations; k++) {
|
||||
var animation = defaults.animations[k];
|
||||
if (jQTSettings[animation.name + 'Selector'] !== undefined) {
|
||||
animation.selector = jQTSettings[animation.name + 'Selector'];
|
||||
}
|
||||
addAnimation(animation);
|
||||
}
|
||||
|
||||
// Create an array of stuff that needs touch event handling
|
||||
touchSelectors.push(jQTSettings.touchSelector);
|
||||
touchSelectors.push(jQTSettings.backSelector);
|
||||
touchSelectors.push(jQTSettings.submitSelector);
|
||||
$(touchSelectors.join(', ')).css('-webkit-touch-callout', 'none');
|
||||
|
||||
// Make sure we have a jqt element
|
||||
$body = $('#jqt');
|
||||
var anatomyLessons = [];
|
||||
|
||||
if ($body.length === 0) {
|
||||
warn('Could not find an element with the id "jqt", so the body id has been set to "jqt". If you are having any problems, wrapping your panels in a div with the id "jqt" might help.');
|
||||
$body = $(document.body).attr('id', 'jqt');
|
||||
}
|
||||
|
||||
// Add some specific css if need be
|
||||
if ($.support.transform3d) {
|
||||
anatomyLessons.push('supports3d');
|
||||
}
|
||||
|
||||
if (jQTSettings.useTouchScroll) {
|
||||
if ($.support.ios5) {
|
||||
anatomyLessons.push('touchscroll');
|
||||
} else {
|
||||
anatomyLessons.push('autoscroll');
|
||||
}
|
||||
}
|
||||
|
||||
if (jQTSettings.fullScreenClass && window.navigator.standalone === true) {
|
||||
anatomyLessons.push(jQTSettings.fullScreenClass, jQTSettings.statusBar);
|
||||
}
|
||||
|
||||
// Bind events
|
||||
$body
|
||||
.addClass(anatomyLessons.join(' '))
|
||||
.bind('click', clickHandler)
|
||||
.bind('orientationchange', orientationChangeHandler)
|
||||
.bind('submit', submitHandler)
|
||||
.bind('tap', tapHandler)
|
||||
.bind($.support.touch ? 'touchstart' : 'mousedown', touchStartHandler)
|
||||
.trigger('orientationchange');
|
||||
|
||||
$(window).bind('hashchange', hashChangeHandler);
|
||||
|
||||
var startHash = location.hash;
|
||||
|
||||
// Determine what the initial view should be
|
||||
if ($('#jqt > .current').length === 0) {
|
||||
$currentPage = $('#jqt > *:first-child').addClass('current');
|
||||
} else {
|
||||
$currentPage = $('#jqt > .current');
|
||||
}
|
||||
|
||||
setHash($currentPage.attr('id'));
|
||||
addPageToHistory($currentPage);
|
||||
|
||||
if ($(startHash).length === 1) {
|
||||
goTo(startHash);
|
||||
}
|
||||
}
|
||||
|
||||
function showPageByHref(href, options) {
|
||||
|
||||
var defaults = {
|
||||
data: null,
|
||||
method: 'GET',
|
||||
animation: null,
|
||||
callback: null,
|
||||
$referrer: null
|
||||
};
|
||||
|
||||
var settings = $.extend({}, defaults, options);
|
||||
|
||||
if (href !== '#') {
|
||||
$.ajax({
|
||||
url: href,
|
||||
data: settings.data,
|
||||
type: settings.method,
|
||||
success: function (data) {
|
||||
var firstPage = insertPages(data, settings.animation);
|
||||
if (firstPage) {
|
||||
if (settings.method === 'GET' && jQTSettings.cacheGetRequests === true && settings.$referrer) {
|
||||
settings.$referrer.attr('href', '#' + firstPage.attr('id'));
|
||||
}
|
||||
if (settings.callback) {
|
||||
settings.callback(true);
|
||||
}
|
||||
}
|
||||
},
|
||||
error: function (data) {
|
||||
if (settings.$referrer) {
|
||||
settings.$referrer.unselect();
|
||||
}
|
||||
if (settings.callback) {
|
||||
settings.callback(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (settings.$referrer) {
|
||||
settings.$referrer.unselect();
|
||||
}
|
||||
}
|
||||
|
||||
function submitHandler(e, callback) {
|
||||
|
||||
$(':focus').trigger('blur');
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
var $form = (typeof(e)==='string') ? $(e).eq(0) : (e.target ? $(e.target) : $(e));
|
||||
|
||||
if ($form.length && $form.is(jQTSettings.formSelector) && $form.attr('action')) {
|
||||
showPageByHref($form.attr('action'), {
|
||||
data: $form.serialize(),
|
||||
method: $form.attr('method') || 'POST',
|
||||
animation: getAnimation($form),
|
||||
callback: callback
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function submitParentForm($el) {
|
||||
|
||||
var $form = $el.closest('form');
|
||||
if ($form.length === 0) {
|
||||
warn('No parent form found.');
|
||||
} else {
|
||||
warn('About to submit parent form.');
|
||||
$form.trigger('submit');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function supportForTransform3d() {
|
||||
|
||||
var head, body, style, div, result;
|
||||
|
||||
head = document.getElementsByTagName('head')[0];
|
||||
body = document.body;
|
||||
|
||||
style = document.createElement('style');
|
||||
style.textContent = '@media (transform-3d),(-o-transform-3d),(-moz-transform-3d),(-webkit-transform-3d){#jqt-3dtest{height:3px}}';
|
||||
|
||||
div = document.createElement('div');
|
||||
div.id = 'jqt-3dtest';
|
||||
|
||||
// Add to the page
|
||||
head.appendChild(style);
|
||||
body.appendChild(div);
|
||||
|
||||
// Check the result
|
||||
result = div.offsetHeight === 3;
|
||||
|
||||
// Clean up
|
||||
style.parentNode.removeChild(style);
|
||||
div.parentNode.removeChild(div);
|
||||
|
||||
// Pass back result
|
||||
warn('Support for 3d transforms: ' + result + '.');
|
||||
return result;
|
||||
}
|
||||
|
||||
function supportIOS5() {
|
||||
var support = false;
|
||||
var REGEX_IOS_VERSION = /OS (\d+)(_\d+)* like Mac OS X/i;
|
||||
|
||||
var agentString = window.navigator.userAgent;
|
||||
if (REGEX_IOS_VERSION.test(agentString)) {
|
||||
support = (REGEX_IOS_VERSION.exec(agentString)[1] >= 5);
|
||||
}
|
||||
return support;
|
||||
}
|
||||
|
||||
function touchStartHandler(e) {
|
||||
|
||||
var $el = $(e.target),
|
||||
selectors = touchSelectors.join(', ');
|
||||
|
||||
// Find the nearest tappable ancestor
|
||||
if (!$el.is(selectors)) {
|
||||
$el = $el.closest(selectors);
|
||||
}
|
||||
|
||||
// Make sure we have a tappable element
|
||||
if ($el.length && $el.attr('href')) {
|
||||
$el.addClass('active');
|
||||
}
|
||||
|
||||
// Remove our active class if we move
|
||||
$el.on($.support.touch ? 'touchmove' : 'mousemove', function() {
|
||||
$el.removeClass('active');
|
||||
});
|
||||
|
||||
$el.on('touchend', function() {
|
||||
$el.unbind('touchmove mousemove');
|
||||
});
|
||||
}
|
||||
|
||||
function tapHandler(e) {
|
||||
|
||||
if (e.isDefaultPrevented()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Grab the target element
|
||||
var $el = $(e.target);
|
||||
|
||||
// Find the nearest tappable ancestor
|
||||
if (!$el.is(touchSelectors.join(', '))) {
|
||||
$el = $el.closest(touchSelectors.join(', '));
|
||||
}
|
||||
|
||||
// Make sure we have a tappable element
|
||||
if (!$el.length || !$el.attr('href')) {
|
||||
warn('Could not find a link related to tapped element.');
|
||||
return true;
|
||||
}
|
||||
|
||||
// Init some vars
|
||||
var target = $el.attr('target'),
|
||||
hash = $el.prop('hash'),
|
||||
href = $el.attr('href');
|
||||
|
||||
var params = {
|
||||
e: e,
|
||||
$el: $el,
|
||||
target: target,
|
||||
hash: hash,
|
||||
href: href,
|
||||
jQTSettings: jQTSettings
|
||||
};
|
||||
|
||||
// Loop thru all handlers
|
||||
for (var i=0, len=tapHandlers.length; i<len; i++) {
|
||||
var handler = tapHandlers[i];
|
||||
var supported = handler.isSupported(e, params);
|
||||
if (supported) {
|
||||
var flag = handler.fn(e, params);
|
||||
return flag;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addDefaultTapHandlers() {
|
||||
addTapHandler({
|
||||
name: 'external-link',
|
||||
isSupported: function(e, params) {
|
||||
return params.$el.isExternalLink();
|
||||
},
|
||||
fn: function(e, params) {
|
||||
params.$el.unselect();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
addTapHandler({
|
||||
name: 'back-selector',
|
||||
isSupported: function(e, params) {
|
||||
return params.$el.is(params.jQTSettings.backSelector);
|
||||
},
|
||||
fn: function(e, params) {
|
||||
// User clicked or tapped a back button
|
||||
goBack(params.hash);
|
||||
}
|
||||
});
|
||||
addTapHandler({
|
||||
name: 'submit-selector',
|
||||
isSupported: function(e, params) {
|
||||
return params.$el.is(params.jQTSettings.submitSelector);
|
||||
},
|
||||
fn: function(e, params) {
|
||||
// User clicked or tapped a submit element
|
||||
submitParentForm(params.$el);
|
||||
}
|
||||
});
|
||||
addTapHandler({
|
||||
name: 'webapp',
|
||||
isSupported: function(e, params) {
|
||||
return params.target === '_webapp';
|
||||
},
|
||||
fn: function(e, params) {
|
||||
// User clicked or tapped an internal link, fullscreen mode
|
||||
window.location = params.href;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
addTapHandler({
|
||||
name: 'no-op',
|
||||
isSupported: function(e, params) {
|
||||
return params.href === '#';
|
||||
},
|
||||
fn: function(e, params) {
|
||||
// Allow tap on item with no href
|
||||
params.$el.unselect();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
addTapHandler({
|
||||
name: 'standard',
|
||||
isSupported: function(e, params) {
|
||||
return params.hash && params.hash !== '#';
|
||||
},
|
||||
fn: function(e, params) {
|
||||
var animation = getAnimation(params.$el);
|
||||
// Internal href
|
||||
params.$el.addClass('active');
|
||||
goTo(
|
||||
$(params.hash).data('referrer', params.$el),
|
||||
animation,
|
||||
params.$el.hasClass('reverse')
|
||||
);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
addTapHandler({
|
||||
name: 'external',
|
||||
isSupported: function(e, params) {
|
||||
return true;
|
||||
},
|
||||
fn: function(e, params) {
|
||||
var animation = getAnimation(params.$el);
|
||||
|
||||
// External href
|
||||
params.$el.addClass('loading active');
|
||||
showPageByHref(params.$el.attr('href'), {
|
||||
animation: animation,
|
||||
callback: function() {
|
||||
params.$el.removeClass('loading');
|
||||
setTimeout($.fn.unselect, 250, params.$el);
|
||||
},
|
||||
$referrer: params.$el
|
||||
});
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Get the party started
|
||||
jQTSettings = $.extend({}, defaults, options);
|
||||
|
||||
initHairExtensions(options);
|
||||
|
||||
initFXExtensions();
|
||||
|
||||
// Expose public methods and properties
|
||||
publicObj = {
|
||||
addAnimation: addAnimation,
|
||||
animations: animations,
|
||||
getOrientation: getOrientation,
|
||||
goBack: goBack,
|
||||
insertPages: insertPages,
|
||||
goTo: goTo,
|
||||
history: history,
|
||||
settings: jQTSettings,
|
||||
submitForm: submitHandler
|
||||
};
|
||||
|
||||
// must be called publicObj assignement to prevent timing problem with extension loading.
|
||||
jQTSettings.starter(start);
|
||||
|
||||
return publicObj;
|
||||
};
|
||||
|
||||
$.jQTouch.prototype.extensions = [];
|
||||
$.jQTouch.prototype.tapHandlers = [];
|
||||
|
||||
// Extensions directly manipulate the jQTouch object,
|
||||
// before it's initialized
|
||||
$.jQTouch.addExtension = function(extension) {
|
||||
$.jQTouch.prototype.extensions.push(extension);
|
||||
};
|
||||
|
||||
// Experimental tap handlers that can bypass
|
||||
// default jQTouch tap handling
|
||||
$.jQTouch.addTapHandler = function(extension) {
|
||||
$.jQTouch.prototype.tapHandlers.push(extension);
|
||||
};
|
||||
|
||||
})(); // Double closure, ALL THE WAY ACROSS THE SKY
|
||||
@@ -1,97 +0,0 @@
|
||||
/**
|
||||
* Cookie plugin
|
||||
*
|
||||
* Copyright (c) 2006 Klaus Hartl (stilbuero.de)
|
||||
* Dual licensed under the MIT and GPL licenses:
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Create a cookie with the given name and value and other optional parameters.
|
||||
*
|
||||
* @example $.cookie('the_cookie', 'the_value');
|
||||
* @desc Set the value of a cookie.
|
||||
* @example $.cookie('the_cookie', 'the_value', { expires: 7, path: '/', domain: 'jquery.com', secure: true });
|
||||
* @desc Create a cookie with all available options.
|
||||
* @example $.cookie('the_cookie', 'the_value');
|
||||
* @desc Create a session cookie.
|
||||
* @example $.cookie('the_cookie', null);
|
||||
* @desc Delete a cookie by passing null as value. Keep in mind that you have to use the same path and domain
|
||||
* used when the cookie was set.
|
||||
*
|
||||
* @param String name The name of the cookie.
|
||||
* @param String value The value of the cookie.
|
||||
* @param Object options An object literal containing key/value pairs to provide optional cookie attributes.
|
||||
* @option Number|Date expires Either an integer specifying the expiration date from now on in days or a Date object.
|
||||
* If a negative value is specified (e.g. a date in the past), the cookie will be deleted.
|
||||
* If set to null or omitted, the cookie will be a session cookie and will not be retained
|
||||
* when the the browser exits.
|
||||
* @option String path The value of the path atribute of the cookie (default: path of page that created the cookie).
|
||||
* @option String domain The value of the domain attribute of the cookie (default: domain of page that created the cookie).
|
||||
* @option Boolean secure If true, the secure attribute of the cookie will be set and the cookie transmission will
|
||||
* require a secure protocol (like HTTPS).
|
||||
* @type undefined
|
||||
*
|
||||
* @name $.cookie
|
||||
* @cat Plugins/Cookie
|
||||
* @author Klaus Hartl/klaus.hartl@stilbuero.de
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get the value of a cookie with the given name.
|
||||
*
|
||||
* @example $.cookie('the_cookie');
|
||||
* @desc Get the value of a cookie.
|
||||
*
|
||||
* @param String name The name of the cookie.
|
||||
* @return The value of the cookie.
|
||||
* @type String
|
||||
*
|
||||
* @name $.cookie
|
||||
* @cat Plugins/Cookie
|
||||
* @author Klaus Hartl/klaus.hartl@stilbuero.de
|
||||
*/
|
||||
jQuery.cookie = function(name, value, options) {
|
||||
if (typeof value != 'undefined') { // name and value given, set cookie
|
||||
options = options || {};
|
||||
if (value === null) {
|
||||
value = '';
|
||||
options = $.extend({}, options); // clone object since it's unexpected behavior if the expired property were changed
|
||||
options.expires = -1;
|
||||
}
|
||||
var expires = '';
|
||||
if (options.expires && (typeof options.expires == 'number' || options.expires.toUTCString)) {
|
||||
var date;
|
||||
if (typeof options.expires == 'number') {
|
||||
date = new Date();
|
||||
date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000));
|
||||
} else {
|
||||
date = options.expires;
|
||||
}
|
||||
expires = '; expires=' + date.toUTCString(); // use expires attribute, max-age is not supported by IE
|
||||
}
|
||||
// NOTE Needed to parenthesize options.path and options.domain
|
||||
// in the following expressions, otherwise they evaluate to undefined
|
||||
// in the packed version for some reason...
|
||||
var path = options.path ? '; path=' + (options.path) : '';
|
||||
var domain = options.domain ? '; domain=' + (options.domain) : '';
|
||||
var secure = options.secure ? '; secure' : '';
|
||||
document.cookie = [name, '=', encodeURIComponent(value), expires, path, domain, secure].join('');
|
||||
} else { // only name given, get cookie
|
||||
var cookieValue = null;
|
||||
if (document.cookie && document.cookie != '') {
|
||||
var cookies = document.cookie.split(';');
|
||||
for (var i = 0; i < cookies.length; i++) {
|
||||
var cookie = jQuery.trim(cookies[i]);
|
||||
// Does this cookie string begin with the name we want?
|
||||
if (cookie.substring(0, name.length + 1) == (name + '=')) {
|
||||
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return cookieValue;
|
||||
}
|
||||
};
|
||||
-11008
File diff suppressed because it is too large
Load Diff
@@ -1,717 +0,0 @@
|
||||
/*!
|
||||
* jQuery Migrate - v1.4.0 - 2016-02-26
|
||||
* Copyright jQuery Foundation and other contributors
|
||||
*/
|
||||
(function( jQuery, window, undefined ) {
|
||||
// See http://bugs.jquery.com/ticket/13335
|
||||
// "use strict";
|
||||
|
||||
|
||||
jQuery.migrateVersion = "1.4.0";
|
||||
|
||||
|
||||
var warnedAbout = {};
|
||||
|
||||
// List of warnings already given; public read only
|
||||
jQuery.migrateWarnings = [];
|
||||
|
||||
// Set to true to prevent console output; migrateWarnings still maintained
|
||||
// jQuery.migrateMute = false;
|
||||
|
||||
// Show a message on the console so devs know we're active
|
||||
if ( window.console && window.console.log ) {
|
||||
window.console.log( "JQMIGRATE: Migrate is installed" +
|
||||
( jQuery.migrateMute ? "" : " with logging active" ) +
|
||||
", version " + jQuery.migrateVersion );
|
||||
}
|
||||
|
||||
// Set to false to disable traces that appear with warnings
|
||||
if ( jQuery.migrateTrace === undefined ) {
|
||||
jQuery.migrateTrace = true;
|
||||
}
|
||||
|
||||
// Forget any warnings we've already given; public
|
||||
jQuery.migrateReset = function() {
|
||||
warnedAbout = {};
|
||||
jQuery.migrateWarnings.length = 0;
|
||||
};
|
||||
|
||||
function migrateWarn( msg) {
|
||||
var console = window.console;
|
||||
if ( !warnedAbout[ msg ] ) {
|
||||
warnedAbout[ msg ] = true;
|
||||
jQuery.migrateWarnings.push( msg );
|
||||
if ( console && console.warn && !jQuery.migrateMute ) {
|
||||
console.warn( "JQMIGRATE: " + msg );
|
||||
if ( jQuery.migrateTrace && console.trace ) {
|
||||
console.trace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function migrateWarnProp( obj, prop, value, msg ) {
|
||||
if ( Object.defineProperty ) {
|
||||
// On ES5 browsers (non-oldIE), warn if the code tries to get prop;
|
||||
// allow property to be overwritten in case some other plugin wants it
|
||||
try {
|
||||
Object.defineProperty( obj, prop, {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
get: function() {
|
||||
migrateWarn( msg );
|
||||
return value;
|
||||
},
|
||||
set: function( newValue ) {
|
||||
migrateWarn( msg );
|
||||
value = newValue;
|
||||
}
|
||||
});
|
||||
return;
|
||||
} catch( err ) {
|
||||
// IE8 is a dope about Object.defineProperty, can't warn there
|
||||
}
|
||||
}
|
||||
|
||||
// Non-ES5 (or broken) browser; just set the property
|
||||
jQuery._definePropertyBroken = true;
|
||||
obj[ prop ] = value;
|
||||
}
|
||||
|
||||
if ( document.compatMode === "BackCompat" ) {
|
||||
// jQuery has never supported or tested Quirks Mode
|
||||
migrateWarn( "jQuery is not compatible with Quirks Mode" );
|
||||
}
|
||||
|
||||
|
||||
var attrFn = jQuery( "<input/>", { size: 1 } ).attr("size") && jQuery.attrFn,
|
||||
oldAttr = jQuery.attr,
|
||||
valueAttrGet = jQuery.attrHooks.value && jQuery.attrHooks.value.get ||
|
||||
function() { return null; },
|
||||
valueAttrSet = jQuery.attrHooks.value && jQuery.attrHooks.value.set ||
|
||||
function() { return undefined; },
|
||||
rnoType = /^(?:input|button)$/i,
|
||||
rnoAttrNodeType = /^[238]$/,
|
||||
rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,
|
||||
ruseDefault = /^(?:checked|selected)$/i;
|
||||
|
||||
// jQuery.attrFn
|
||||
migrateWarnProp( jQuery, "attrFn", attrFn || {}, "jQuery.attrFn is deprecated" );
|
||||
|
||||
jQuery.attr = function( elem, name, value, pass ) {
|
||||
var lowerName = name.toLowerCase(),
|
||||
nType = elem && elem.nodeType;
|
||||
|
||||
if ( pass ) {
|
||||
// Since pass is used internally, we only warn for new jQuery
|
||||
// versions where there isn't a pass arg in the formal params
|
||||
if ( oldAttr.length < 4 ) {
|
||||
migrateWarn("jQuery.fn.attr( props, pass ) is deprecated");
|
||||
}
|
||||
if ( elem && !rnoAttrNodeType.test( nType ) &&
|
||||
(attrFn ? name in attrFn : jQuery.isFunction(jQuery.fn[name])) ) {
|
||||
return jQuery( elem )[ name ]( value );
|
||||
}
|
||||
}
|
||||
|
||||
// Warn if user tries to set `type`, since it breaks on IE 6/7/8; by checking
|
||||
// for disconnected elements we don't warn on $( "<button>", { type: "button" } ).
|
||||
if ( name === "type" && value !== undefined && rnoType.test( elem.nodeName ) && elem.parentNode ) {
|
||||
migrateWarn("Can't change the 'type' of an input or button in IE 6/7/8");
|
||||
}
|
||||
|
||||
// Restore boolHook for boolean property/attribute synchronization
|
||||
if ( !jQuery.attrHooks[ lowerName ] && rboolean.test( lowerName ) ) {
|
||||
jQuery.attrHooks[ lowerName ] = {
|
||||
get: function( elem, name ) {
|
||||
// Align boolean attributes with corresponding properties
|
||||
// Fall back to attribute presence where some booleans are not supported
|
||||
var attrNode,
|
||||
property = jQuery.prop( elem, name );
|
||||
return property === true || typeof property !== "boolean" &&
|
||||
( attrNode = elem.getAttributeNode(name) ) && attrNode.nodeValue !== false ?
|
||||
|
||||
name.toLowerCase() :
|
||||
undefined;
|
||||
},
|
||||
set: function( elem, value, name ) {
|
||||
var propName;
|
||||
if ( value === false ) {
|
||||
// Remove boolean attributes when set to false
|
||||
jQuery.removeAttr( elem, name );
|
||||
} else {
|
||||
// value is true since we know at this point it's type boolean and not false
|
||||
// Set boolean attributes to the same name and set the DOM property
|
||||
propName = jQuery.propFix[ name ] || name;
|
||||
if ( propName in elem ) {
|
||||
// Only set the IDL specifically if it already exists on the element
|
||||
elem[ propName ] = true;
|
||||
}
|
||||
|
||||
elem.setAttribute( name, name.toLowerCase() );
|
||||
}
|
||||
return name;
|
||||
}
|
||||
};
|
||||
|
||||
// Warn only for attributes that can remain distinct from their properties post-1.9
|
||||
if ( ruseDefault.test( lowerName ) ) {
|
||||
migrateWarn( "jQuery.fn.attr('" + lowerName + "') might use property instead of attribute" );
|
||||
}
|
||||
}
|
||||
|
||||
return oldAttr.call( jQuery, elem, name, value );
|
||||
};
|
||||
|
||||
// attrHooks: value
|
||||
jQuery.attrHooks.value = {
|
||||
get: function( elem, name ) {
|
||||
var nodeName = ( elem.nodeName || "" ).toLowerCase();
|
||||
if ( nodeName === "button" ) {
|
||||
return valueAttrGet.apply( this, arguments );
|
||||
}
|
||||
if ( nodeName !== "input" && nodeName !== "option" ) {
|
||||
migrateWarn("jQuery.fn.attr('value') no longer gets properties");
|
||||
}
|
||||
return name in elem ?
|
||||
elem.value :
|
||||
null;
|
||||
},
|
||||
set: function( elem, value ) {
|
||||
var nodeName = ( elem.nodeName || "" ).toLowerCase();
|
||||
if ( nodeName === "button" ) {
|
||||
return valueAttrSet.apply( this, arguments );
|
||||
}
|
||||
if ( nodeName !== "input" && nodeName !== "option" ) {
|
||||
migrateWarn("jQuery.fn.attr('value', val) no longer sets properties");
|
||||
}
|
||||
// Does not return so that setAttribute is also used
|
||||
elem.value = value;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
var matched, browser,
|
||||
oldInit = jQuery.fn.init,
|
||||
oldParseJSON = jQuery.parseJSON,
|
||||
rspaceAngle = /^\s*</,
|
||||
rattrHash = /\[\s*\w+\s*[~|^$*]?=\s*(?![\s'"])[^#\]]*#/,
|
||||
// Note: XSS check is done below after string is trimmed
|
||||
rquickExpr = /^([^<]*)(<[\w\W]+>)([^>]*)$/;
|
||||
|
||||
// $(html) "looks like html" rule change
|
||||
jQuery.fn.init = function( selector, context, rootjQuery ) {
|
||||
var match, ret;
|
||||
|
||||
if ( selector && typeof selector === "string" && !jQuery.isPlainObject( context ) &&
|
||||
(match = rquickExpr.exec( jQuery.trim( selector ) )) && match[ 0 ] ) {
|
||||
// This is an HTML string according to the "old" rules; is it still?
|
||||
if ( !rspaceAngle.test( selector ) ) {
|
||||
migrateWarn("$(html) HTML strings must start with '<' character");
|
||||
}
|
||||
if ( match[ 3 ] ) {
|
||||
migrateWarn("$(html) HTML text after last tag is ignored");
|
||||
}
|
||||
|
||||
// Consistently reject any HTML-like string starting with a hash (#9521)
|
||||
// Note that this may break jQuery 1.6.x code that otherwise would work.
|
||||
if ( match[ 0 ].charAt( 0 ) === "#" ) {
|
||||
migrateWarn("HTML string cannot start with a '#' character");
|
||||
jQuery.error("JQMIGRATE: Invalid selector string (XSS)");
|
||||
}
|
||||
// Now process using loose rules; let pre-1.8 play too
|
||||
if ( context && context.context ) {
|
||||
// jQuery object as context; parseHTML expects a DOM object
|
||||
context = context.context;
|
||||
}
|
||||
if ( jQuery.parseHTML ) {
|
||||
return oldInit.call( this,
|
||||
jQuery.parseHTML( match[ 2 ], context && context.ownerDocument ||
|
||||
context || document, true ), context, rootjQuery );
|
||||
}
|
||||
}
|
||||
|
||||
if ( selector === "#" ) {
|
||||
|
||||
// jQuery( "#" ) is a bogus ID selector, but it returned an empty set before jQuery 3.0
|
||||
migrateWarn( "jQuery( '#' ) is not a valid selector" );
|
||||
selector = [];
|
||||
|
||||
} else if ( rattrHash.test( selector ) ) {
|
||||
|
||||
// The nonstandard and undocumented unquoted-hash was removed in jQuery 1.12.0
|
||||
// Note that this doesn't actually fix the selector due to potential false positives
|
||||
migrateWarn( "Attribute selectors with '#' must be quoted: '" + selector + "'" );
|
||||
}
|
||||
|
||||
ret = oldInit.apply( this, arguments );
|
||||
|
||||
// Fill in selector and context properties so .live() works
|
||||
if ( selector && selector.selector !== undefined ) {
|
||||
// A jQuery object, copy its properties
|
||||
ret.selector = selector.selector;
|
||||
ret.context = selector.context;
|
||||
|
||||
} else {
|
||||
ret.selector = typeof selector === "string" ? selector : "";
|
||||
if ( selector ) {
|
||||
ret.context = selector.nodeType? selector : context || document;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
};
|
||||
jQuery.fn.init.prototype = jQuery.fn;
|
||||
|
||||
// Let $.parseJSON(falsy_value) return null
|
||||
jQuery.parseJSON = function( json ) {
|
||||
if ( !json ) {
|
||||
migrateWarn("jQuery.parseJSON requires a valid JSON string");
|
||||
return null;
|
||||
}
|
||||
return oldParseJSON.apply( this, arguments );
|
||||
};
|
||||
|
||||
jQuery.uaMatch = function( ua ) {
|
||||
ua = ua.toLowerCase();
|
||||
|
||||
var match = /(chrome)[ \/]([\w.]+)/.exec( ua ) ||
|
||||
/(webkit)[ \/]([\w.]+)/.exec( ua ) ||
|
||||
/(opera)(?:.*version|)[ \/]([\w.]+)/.exec( ua ) ||
|
||||
/(msie) ([\w.]+)/.exec( ua ) ||
|
||||
ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec( ua ) ||
|
||||
[];
|
||||
|
||||
return {
|
||||
browser: match[ 1 ] || "",
|
||||
version: match[ 2 ] || "0"
|
||||
};
|
||||
};
|
||||
|
||||
// Don't clobber any existing jQuery.browser in case it's different
|
||||
if ( !jQuery.browser ) {
|
||||
matched = jQuery.uaMatch( navigator.userAgent );
|
||||
browser = {};
|
||||
|
||||
if ( matched.browser ) {
|
||||
browser[ matched.browser ] = true;
|
||||
browser.version = matched.version;
|
||||
}
|
||||
|
||||
// Chrome is Webkit, but Webkit is also Safari.
|
||||
if ( browser.chrome ) {
|
||||
browser.webkit = true;
|
||||
} else if ( browser.webkit ) {
|
||||
browser.safari = true;
|
||||
}
|
||||
|
||||
jQuery.browser = browser;
|
||||
}
|
||||
|
||||
// Warn if the code tries to get jQuery.browser
|
||||
migrateWarnProp( jQuery, "browser", jQuery.browser, "jQuery.browser is deprecated" );
|
||||
|
||||
// jQuery.boxModel deprecated in 1.3, jQuery.support.boxModel deprecated in 1.7
|
||||
jQuery.boxModel = jQuery.support.boxModel = (document.compatMode === "CSS1Compat");
|
||||
migrateWarnProp( jQuery, "boxModel", jQuery.boxModel, "jQuery.boxModel is deprecated" );
|
||||
migrateWarnProp( jQuery.support, "boxModel", jQuery.support.boxModel, "jQuery.support.boxModel is deprecated" );
|
||||
|
||||
jQuery.sub = function() {
|
||||
function jQuerySub( selector, context ) {
|
||||
return new jQuerySub.fn.init( selector, context );
|
||||
}
|
||||
jQuery.extend( true, jQuerySub, this );
|
||||
jQuerySub.superclass = this;
|
||||
jQuerySub.fn = jQuerySub.prototype = this();
|
||||
jQuerySub.fn.constructor = jQuerySub;
|
||||
jQuerySub.sub = this.sub;
|
||||
jQuerySub.fn.init = function init( selector, context ) {
|
||||
var instance = jQuery.fn.init.call( this, selector, context, rootjQuerySub );
|
||||
return instance instanceof jQuerySub ?
|
||||
instance :
|
||||
jQuerySub( instance );
|
||||
};
|
||||
jQuerySub.fn.init.prototype = jQuerySub.fn;
|
||||
var rootjQuerySub = jQuerySub(document);
|
||||
migrateWarn( "jQuery.sub() is deprecated" );
|
||||
return jQuerySub;
|
||||
};
|
||||
|
||||
// The number of elements contained in the matched element set
|
||||
jQuery.fn.size = function() {
|
||||
migrateWarn( "jQuery.fn.size() is deprecated; use the .length property" );
|
||||
return this.length;
|
||||
};
|
||||
|
||||
|
||||
var internalSwapCall = false;
|
||||
|
||||
// If this version of jQuery has .swap(), don't false-alarm on internal uses
|
||||
if ( jQuery.swap ) {
|
||||
jQuery.each( [ "height", "width", "reliableMarginRight" ], function( _, name ) {
|
||||
var oldHook = jQuery.cssHooks[ name ] && jQuery.cssHooks[ name ].get;
|
||||
|
||||
if ( oldHook ) {
|
||||
jQuery.cssHooks[ name ].get = function() {
|
||||
var ret;
|
||||
|
||||
internalSwapCall = true;
|
||||
ret = oldHook.apply( this, arguments );
|
||||
internalSwapCall = false;
|
||||
return ret;
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
jQuery.swap = function( elem, options, callback, args ) {
|
||||
var ret, name,
|
||||
old = {};
|
||||
|
||||
if ( !internalSwapCall ) {
|
||||
migrateWarn( "jQuery.swap() is undocumented and deprecated" );
|
||||
}
|
||||
|
||||
// Remember the old values, and insert the new ones
|
||||
for ( name in options ) {
|
||||
old[ name ] = elem.style[ name ];
|
||||
elem.style[ name ] = options[ name ];
|
||||
}
|
||||
|
||||
ret = callback.apply( elem, args || [] );
|
||||
|
||||
// Revert the old values
|
||||
for ( name in options ) {
|
||||
elem.style[ name ] = old[ name ];
|
||||
}
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
|
||||
// Ensure that $.ajax gets the new parseJSON defined in core.js
|
||||
jQuery.ajaxSetup({
|
||||
converters: {
|
||||
"text json": jQuery.parseJSON
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
var oldFnData = jQuery.fn.data;
|
||||
|
||||
jQuery.fn.data = function( name ) {
|
||||
var ret, evt,
|
||||
elem = this[0];
|
||||
|
||||
// Handles 1.7 which has this behavior and 1.8 which doesn't
|
||||
if ( elem && name === "events" && arguments.length === 1 ) {
|
||||
ret = jQuery.data( elem, name );
|
||||
evt = jQuery._data( elem, name );
|
||||
if ( ( ret === undefined || ret === evt ) && evt !== undefined ) {
|
||||
migrateWarn("Use of jQuery.fn.data('events') is deprecated");
|
||||
return evt;
|
||||
}
|
||||
}
|
||||
return oldFnData.apply( this, arguments );
|
||||
};
|
||||
|
||||
|
||||
var rscriptType = /\/(java|ecma)script/i;
|
||||
|
||||
// Since jQuery.clean is used internally on older versions, we only shim if it's missing
|
||||
if ( !jQuery.clean ) {
|
||||
jQuery.clean = function( elems, context, fragment, scripts ) {
|
||||
// Set context per 1.8 logic
|
||||
context = context || document;
|
||||
context = !context.nodeType && context[0] || context;
|
||||
context = context.ownerDocument || context;
|
||||
|
||||
migrateWarn("jQuery.clean() is deprecated");
|
||||
|
||||
var i, elem, handleScript, jsTags,
|
||||
ret = [];
|
||||
|
||||
jQuery.merge( ret, jQuery.buildFragment( elems, context ).childNodes );
|
||||
|
||||
// Complex logic lifted directly from jQuery 1.8
|
||||
if ( fragment ) {
|
||||
// Special handling of each script element
|
||||
handleScript = function( elem ) {
|
||||
// Check if we consider it executable
|
||||
if ( !elem.type || rscriptType.test( elem.type ) ) {
|
||||
// Detach the script and store it in the scripts array (if provided) or the fragment
|
||||
// Return truthy to indicate that it has been handled
|
||||
return scripts ?
|
||||
scripts.push( elem.parentNode ? elem.parentNode.removeChild( elem ) : elem ) :
|
||||
fragment.appendChild( elem );
|
||||
}
|
||||
};
|
||||
|
||||
for ( i = 0; (elem = ret[i]) != null; i++ ) {
|
||||
// Check if we're done after handling an executable script
|
||||
if ( !( jQuery.nodeName( elem, "script" ) && handleScript( elem ) ) ) {
|
||||
// Append to fragment and handle embedded scripts
|
||||
fragment.appendChild( elem );
|
||||
if ( typeof elem.getElementsByTagName !== "undefined" ) {
|
||||
// handleScript alters the DOM, so use jQuery.merge to ensure snapshot iteration
|
||||
jsTags = jQuery.grep( jQuery.merge( [], elem.getElementsByTagName("script") ), handleScript );
|
||||
|
||||
// Splice the scripts into ret after their former ancestor and advance our index beyond them
|
||||
ret.splice.apply( ret, [i + 1, 0].concat( jsTags ) );
|
||||
i += jsTags.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
};
|
||||
}
|
||||
|
||||
var eventAdd = jQuery.event.add,
|
||||
eventRemove = jQuery.event.remove,
|
||||
eventTrigger = jQuery.event.trigger,
|
||||
oldToggle = jQuery.fn.toggle,
|
||||
oldLive = jQuery.fn.live,
|
||||
oldDie = jQuery.fn.die,
|
||||
oldLoad = jQuery.fn.load,
|
||||
ajaxEvents = "ajaxStart|ajaxStop|ajaxSend|ajaxComplete|ajaxError|ajaxSuccess",
|
||||
rajaxEvent = new RegExp( "\\b(?:" + ajaxEvents + ")\\b" ),
|
||||
rhoverHack = /(?:^|\s)hover(\.\S+|)\b/,
|
||||
hoverHack = function( events ) {
|
||||
if ( typeof( events ) !== "string" || jQuery.event.special.hover ) {
|
||||
return events;
|
||||
}
|
||||
if ( rhoverHack.test( events ) ) {
|
||||
migrateWarn("'hover' pseudo-event is deprecated, use 'mouseenter mouseleave'");
|
||||
}
|
||||
return events && events.replace( rhoverHack, "mouseenter$1 mouseleave$1" );
|
||||
};
|
||||
|
||||
// Event props removed in 1.9, put them back if needed; no practical way to warn them
|
||||
if ( jQuery.event.props && jQuery.event.props[ 0 ] !== "attrChange" ) {
|
||||
jQuery.event.props.unshift( "attrChange", "attrName", "relatedNode", "srcElement" );
|
||||
}
|
||||
|
||||
// Undocumented jQuery.event.handle was "deprecated" in jQuery 1.7
|
||||
if ( jQuery.event.dispatch ) {
|
||||
migrateWarnProp( jQuery.event, "handle", jQuery.event.dispatch, "jQuery.event.handle is undocumented and deprecated" );
|
||||
}
|
||||
|
||||
// Support for 'hover' pseudo-event and ajax event warnings
|
||||
jQuery.event.add = function( elem, types, handler, data, selector ){
|
||||
if ( elem !== document && rajaxEvent.test( types ) ) {
|
||||
migrateWarn( "AJAX events should be attached to document: " + types );
|
||||
}
|
||||
eventAdd.call( this, elem, hoverHack( types || "" ), handler, data, selector );
|
||||
};
|
||||
jQuery.event.remove = function( elem, types, handler, selector, mappedTypes ){
|
||||
eventRemove.call( this, elem, hoverHack( types ) || "", handler, selector, mappedTypes );
|
||||
};
|
||||
|
||||
jQuery.each( [ "load", "unload", "error" ], function( _, name ) {
|
||||
|
||||
jQuery.fn[ name ] = function() {
|
||||
var args = Array.prototype.slice.call( arguments, 0 );
|
||||
|
||||
// If this is an ajax load() the first arg should be the string URL;
|
||||
// technically this could also be the "Anything" arg of the event .load()
|
||||
// which just goes to show why this dumb signature has been deprecated!
|
||||
// jQuery custom builds that exclude the Ajax module justifiably die here.
|
||||
if ( name === "load" && typeof args[ 0 ] === "string" ) {
|
||||
return oldLoad.apply( this, args );
|
||||
}
|
||||
|
||||
migrateWarn( "jQuery.fn." + name + "() is deprecated" );
|
||||
|
||||
args.splice( 0, 0, name );
|
||||
if ( arguments.length ) {
|
||||
return this.bind.apply( this, args );
|
||||
}
|
||||
|
||||
// Use .triggerHandler here because:
|
||||
// - load and unload events don't need to bubble, only applied to window or image
|
||||
// - error event should not bubble to window, although it does pre-1.7
|
||||
// See http://bugs.jquery.com/ticket/11820
|
||||
this.triggerHandler.apply( this, args );
|
||||
return this;
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
jQuery.fn.toggle = function( fn, fn2 ) {
|
||||
|
||||
// Don't mess with animation or css toggles
|
||||
if ( !jQuery.isFunction( fn ) || !jQuery.isFunction( fn2 ) ) {
|
||||
return oldToggle.apply( this, arguments );
|
||||
}
|
||||
migrateWarn("jQuery.fn.toggle(handler, handler...) is deprecated");
|
||||
|
||||
// Save reference to arguments for access in closure
|
||||
var args = arguments,
|
||||
guid = fn.guid || jQuery.guid++,
|
||||
i = 0,
|
||||
toggler = function( event ) {
|
||||
// Figure out which function to execute
|
||||
var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i;
|
||||
jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 );
|
||||
|
||||
// Make sure that clicks stop
|
||||
event.preventDefault();
|
||||
|
||||
// and execute the function
|
||||
return args[ lastToggle ].apply( this, arguments ) || false;
|
||||
};
|
||||
|
||||
// link all the functions, so any of them can unbind this click handler
|
||||
toggler.guid = guid;
|
||||
while ( i < args.length ) {
|
||||
args[ i++ ].guid = guid;
|
||||
}
|
||||
|
||||
return this.click( toggler );
|
||||
};
|
||||
|
||||
jQuery.fn.live = function( types, data, fn ) {
|
||||
migrateWarn("jQuery.fn.live() is deprecated");
|
||||
if ( oldLive ) {
|
||||
return oldLive.apply( this, arguments );
|
||||
}
|
||||
jQuery( this.context ).on( types, this.selector, data, fn );
|
||||
return this;
|
||||
};
|
||||
|
||||
jQuery.fn.die = function( types, fn ) {
|
||||
migrateWarn("jQuery.fn.die() is deprecated");
|
||||
if ( oldDie ) {
|
||||
return oldDie.apply( this, arguments );
|
||||
}
|
||||
jQuery( this.context ).off( types, this.selector || "**", fn );
|
||||
return this;
|
||||
};
|
||||
|
||||
// Turn global events into document-triggered events
|
||||
jQuery.event.trigger = function( event, data, elem, onlyHandlers ){
|
||||
if ( !elem && !rajaxEvent.test( event ) ) {
|
||||
migrateWarn( "Global events are undocumented and deprecated" );
|
||||
}
|
||||
return eventTrigger.call( this, event, data, elem || document, onlyHandlers );
|
||||
};
|
||||
jQuery.each( ajaxEvents.split("|"),
|
||||
function( _, name ) {
|
||||
jQuery.event.special[ name ] = {
|
||||
setup: function() {
|
||||
var elem = this;
|
||||
|
||||
// The document needs no shimming; must be !== for oldIE
|
||||
if ( elem !== document ) {
|
||||
jQuery.event.add( document, name + "." + jQuery.guid, function() {
|
||||
jQuery.event.trigger( name, Array.prototype.slice.call( arguments, 1 ), elem, true );
|
||||
});
|
||||
jQuery._data( this, name, jQuery.guid++ );
|
||||
}
|
||||
return false;
|
||||
},
|
||||
teardown: function() {
|
||||
if ( this !== document ) {
|
||||
jQuery.event.remove( document, name + "." + jQuery._data( this, name ) );
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
jQuery.event.special.ready = {
|
||||
setup: function() {
|
||||
if ( this === document ) {
|
||||
migrateWarn( "'ready' event is deprecated" );
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var oldSelf = jQuery.fn.andSelf || jQuery.fn.addBack,
|
||||
oldFind = jQuery.fn.find;
|
||||
|
||||
jQuery.fn.andSelf = function() {
|
||||
migrateWarn("jQuery.fn.andSelf() replaced by jQuery.fn.addBack()");
|
||||
return oldSelf.apply( this, arguments );
|
||||
};
|
||||
|
||||
jQuery.fn.find = function( selector ) {
|
||||
var ret = oldFind.apply( this, arguments );
|
||||
ret.context = this.context;
|
||||
ret.selector = this.selector ? this.selector + " " + selector : selector;
|
||||
return ret;
|
||||
};
|
||||
|
||||
|
||||
// jQuery 1.6 did not support Callbacks, do not warn there
|
||||
if ( jQuery.Callbacks ) {
|
||||
|
||||
var oldDeferred = jQuery.Deferred,
|
||||
tuples = [
|
||||
// action, add listener, callbacks, .then handlers, final state
|
||||
[ "resolve", "done", jQuery.Callbacks("once memory"),
|
||||
jQuery.Callbacks("once memory"), "resolved" ],
|
||||
[ "reject", "fail", jQuery.Callbacks("once memory"),
|
||||
jQuery.Callbacks("once memory"), "rejected" ],
|
||||
[ "notify", "progress", jQuery.Callbacks("memory"),
|
||||
jQuery.Callbacks("memory") ]
|
||||
];
|
||||
|
||||
jQuery.Deferred = function( func ) {
|
||||
var deferred = oldDeferred(),
|
||||
promise = deferred.promise();
|
||||
|
||||
deferred.pipe = promise.pipe = function( /* fnDone, fnFail, fnProgress */ ) {
|
||||
var fns = arguments;
|
||||
|
||||
migrateWarn( "deferred.pipe() is deprecated" );
|
||||
|
||||
return jQuery.Deferred(function( newDefer ) {
|
||||
jQuery.each( tuples, function( i, tuple ) {
|
||||
var fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];
|
||||
// deferred.done(function() { bind to newDefer or newDefer.resolve })
|
||||
// deferred.fail(function() { bind to newDefer or newDefer.reject })
|
||||
// deferred.progress(function() { bind to newDefer or newDefer.notify })
|
||||
deferred[ tuple[1] ](function() {
|
||||
var returned = fn && fn.apply( this, arguments );
|
||||
if ( returned && jQuery.isFunction( returned.promise ) ) {
|
||||
returned.promise()
|
||||
.done( newDefer.resolve )
|
||||
.fail( newDefer.reject )
|
||||
.progress( newDefer.notify );
|
||||
} else {
|
||||
newDefer[ tuple[ 0 ] + "With" ](
|
||||
this === promise ? newDefer.promise() : this,
|
||||
fn ? [ returned ] : arguments
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
fns = null;
|
||||
}).promise();
|
||||
|
||||
};
|
||||
|
||||
deferred.isResolved = function() {
|
||||
migrateWarn( "deferred.isResolved is deprecated" );
|
||||
return deferred.state() === "resolved";
|
||||
};
|
||||
|
||||
deferred.isRejected = function() {
|
||||
migrateWarn( "deferred.isRejected is deprecated" );
|
||||
return deferred.state() === "rejected";
|
||||
};
|
||||
|
||||
if ( func ) {
|
||||
func.call( deferred, deferred );
|
||||
}
|
||||
|
||||
return deferred;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
})( jQuery, window );
|
||||
@@ -1,125 +0,0 @@
|
||||
/*! Copyright (c) 2013 Brandon Aaron (http://brandonaaron.net)
|
||||
* Licensed under the MIT License (LICENSE.txt).
|
||||
*
|
||||
* Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers.
|
||||
* Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix.
|
||||
* Thanks to: Seamus Leahy for adding deltaX and deltaY
|
||||
*
|
||||
* Version: 3.1.3
|
||||
*
|
||||
* Requires: 1.2.2+
|
||||
*/
|
||||
|
||||
(function (factory) {
|
||||
// GALAXY HACK
|
||||
// (JG): Galaxy's mixing of a global jQuery and require modules doesn't work with
|
||||
// the logic below. Instead, do the right thing for this configuration without any checks.
|
||||
factory(jQuery);
|
||||
// END HACK
|
||||
|
||||
/*
|
||||
if ( typeof define === 'function' && define.amd ) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define(['jquery'], factory);
|
||||
} else if (typeof exports === 'object') {
|
||||
// Node/CommonJS style for Browserify
|
||||
module.exports = factory;
|
||||
} else {
|
||||
// Browser globals
|
||||
factory(jQuery);
|
||||
}
|
||||
*/
|
||||
}(function ($) {
|
||||
|
||||
var toFix = ['wheel', 'mousewheel', 'DOMMouseScroll', 'MozMousePixelScroll'];
|
||||
var toBind = 'onwheel' in document || document.documentMode >= 9 ? ['wheel'] : ['mousewheel', 'DomMouseScroll', 'MozMousePixelScroll'];
|
||||
var lowestDelta, lowestDeltaXY;
|
||||
|
||||
if ( $.event.fixHooks ) {
|
||||
for ( var i = toFix.length; i; ) {
|
||||
$.event.fixHooks[ toFix[--i] ] = $.event.mouseHooks;
|
||||
}
|
||||
}
|
||||
|
||||
$.event.special.mousewheel = {
|
||||
setup: function() {
|
||||
if ( this.addEventListener ) {
|
||||
for ( var i = toBind.length; i; ) {
|
||||
this.addEventListener( toBind[--i], handler, false );
|
||||
}
|
||||
} else {
|
||||
this.onmousewheel = handler;
|
||||
}
|
||||
},
|
||||
|
||||
teardown: function() {
|
||||
if ( this.removeEventListener ) {
|
||||
for ( var i = toBind.length; i; ) {
|
||||
this.removeEventListener( toBind[--i], handler, false );
|
||||
}
|
||||
} else {
|
||||
this.onmousewheel = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$.fn.extend({
|
||||
mousewheel: function(fn) {
|
||||
return fn ? this.bind("mousewheel", fn) : this.trigger("mousewheel");
|
||||
},
|
||||
|
||||
unmousewheel: function(fn) {
|
||||
return this.unbind("mousewheel", fn);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function handler(event) {
|
||||
var orgEvent = event || window.event,
|
||||
args = [].slice.call(arguments, 1),
|
||||
delta = 0,
|
||||
deltaX = 0,
|
||||
deltaY = 0,
|
||||
absDelta = 0,
|
||||
absDeltaXY = 0,
|
||||
fn;
|
||||
event = $.event.fix(orgEvent);
|
||||
event.type = "mousewheel";
|
||||
|
||||
// Old school scrollwheel delta
|
||||
if ( orgEvent.wheelDelta ) { delta = orgEvent.wheelDelta; }
|
||||
if ( orgEvent.detail ) { delta = orgEvent.detail * -1; }
|
||||
|
||||
// New school wheel delta (wheel event)
|
||||
if ( orgEvent.deltaY ) {
|
||||
deltaY = orgEvent.deltaY * -1;
|
||||
delta = deltaY;
|
||||
}
|
||||
if ( orgEvent.deltaX ) {
|
||||
deltaX = orgEvent.deltaX;
|
||||
delta = deltaX * -1;
|
||||
}
|
||||
|
||||
// Webkit
|
||||
if ( orgEvent.wheelDeltaY !== undefined ) { deltaY = orgEvent.wheelDeltaY; }
|
||||
if ( orgEvent.wheelDeltaX !== undefined ) { deltaX = orgEvent.wheelDeltaX * -1; }
|
||||
|
||||
// Look for lowest delta to normalize the delta values
|
||||
absDelta = Math.abs(delta);
|
||||
if ( !lowestDelta || absDelta < lowestDelta ) { lowestDelta = absDelta; }
|
||||
absDeltaXY = Math.max(Math.abs(deltaY), Math.abs(deltaX));
|
||||
if ( !lowestDeltaXY || absDeltaXY < lowestDeltaXY ) { lowestDeltaXY = absDeltaXY; }
|
||||
|
||||
// Get a whole value for the deltas
|
||||
fn = delta > 0 ? 'floor' : 'ceil';
|
||||
delta = Math[fn](delta / lowestDelta);
|
||||
deltaX = Math[fn](deltaX / lowestDeltaXY);
|
||||
deltaY = Math[fn](deltaY / lowestDeltaXY);
|
||||
|
||||
// Add event and delta to the front of the arguments
|
||||
args.unshift(event, delta, deltaX, deltaY);
|
||||
|
||||
return ($.event.dispatch || $.event.handle).apply(this, args);
|
||||
}
|
||||
|
||||
}));
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,79 +1,80 @@
|
||||
define([
|
||||
"mvc/base-mvc",
|
||||
"utils/localization",
|
||||
"ui/editable-text",
|
||||
], function( baseMVC, _l ){
|
||||
import baseMVC from "mvc/base-mvc";
|
||||
import _l from "utils/localization";
|
||||
import "ui/editable-text";
|
||||
// =============================================================================
|
||||
/** A view on any model that has a 'annotation' attribute
|
||||
*/
|
||||
var AnnotationEditor = Backbone.View
|
||||
.extend( baseMVC.LoggableMixin )
|
||||
.extend( baseMVC.HiddenUntilActivatedViewMixin ).extend({
|
||||
var AnnotationEditor = Backbone.View.extend(baseMVC.LoggableMixin)
|
||||
.extend(baseMVC.HiddenUntilActivatedViewMixin)
|
||||
.extend({
|
||||
tagName: "div",
|
||||
className: "annotation-display",
|
||||
|
||||
tagName : 'div',
|
||||
className : 'annotation-display',
|
||||
/** Set up listeners, parse options */
|
||||
initialize: function(options) {
|
||||
options = options || {};
|
||||
this.tooltipConfig = options.tooltipConfig || {
|
||||
placement: "bottom"
|
||||
};
|
||||
//console.debug( this, options );
|
||||
// only listen to the model only for changes to annotations
|
||||
this.listenTo(this.model, "change:annotation", function() {
|
||||
this.render();
|
||||
});
|
||||
this.hiddenUntilActivated(options.$activator, options);
|
||||
},
|
||||
|
||||
/** Set up listeners, parse options */
|
||||
initialize : function( options ){
|
||||
options = options || {};
|
||||
this.tooltipConfig = options.tooltipConfig || { placement: 'bottom' };
|
||||
//console.debug( this, options );
|
||||
// only listen to the model only for changes to annotations
|
||||
this.listenTo( this.model, 'change:annotation', function(){
|
||||
this.render();
|
||||
});
|
||||
this.hiddenUntilActivated( options.$activator, options );
|
||||
},
|
||||
/** Build the DOM elements, call select to on the created input, and set up behaviors */
|
||||
render: function() {
|
||||
var view = this;
|
||||
this.$el.html(this._template());
|
||||
|
||||
/** Build the DOM elements, call select to on the created input, and set up behaviors */
|
||||
render : function(){
|
||||
var view = this;
|
||||
this.$el.html( this._template() );
|
||||
|
||||
//TODO: handle empties better
|
||||
this.$annotation().make_text_editable({
|
||||
use_textarea: true,
|
||||
on_finish: function( newAnnotation ){
|
||||
view.$annotation().text( newAnnotation );
|
||||
view.model.save({ annotation: newAnnotation }, { silent: true })
|
||||
.fail( function(){
|
||||
view.$annotation().text( view.model.previous( 'annotation' ) );
|
||||
//TODO: handle empties better
|
||||
this.$annotation().make_text_editable({
|
||||
use_textarea: true,
|
||||
on_finish: function(newAnnotation) {
|
||||
view.$annotation().text(newAnnotation);
|
||||
view.model.save({ annotation: newAnnotation }, { silent: true }).fail(() => {
|
||||
view.$annotation().text(view.model.previous("annotation"));
|
||||
});
|
||||
}
|
||||
});
|
||||
return this;
|
||||
},
|
||||
}
|
||||
});
|
||||
return this;
|
||||
},
|
||||
|
||||
/** @returns {String} the html text used to build the view's DOM */
|
||||
_template : function(){
|
||||
var annotation = this.model.get( 'annotation' );
|
||||
return [
|
||||
//TODO: make prompt optional
|
||||
'<label class="prompt">', _l( 'Annotation' ), '</label>',
|
||||
// set up initial tags by adding as CSV to input vals (necc. to init select2)
|
||||
'<div class="annotation">',
|
||||
_.escape( annotation ),
|
||||
'</div>'
|
||||
].join( '' );
|
||||
},
|
||||
/** @returns {String} the html text used to build the view's DOM */
|
||||
_template: function() {
|
||||
var annotation = this.model.get("annotation");
|
||||
return [
|
||||
//TODO: make prompt optional
|
||||
'<label class="prompt">',
|
||||
_l("Annotation"),
|
||||
"</label>",
|
||||
// set up initial tags by adding as CSV to input vals (necc. to init select2)
|
||||
'<div class="annotation">',
|
||||
_.escape(annotation),
|
||||
"</div>"
|
||||
].join("");
|
||||
},
|
||||
|
||||
/** @returns {jQuery} the main element for this view */
|
||||
$annotation : function(){
|
||||
return this.$el.find( '.annotation' );
|
||||
},
|
||||
/** @returns {jQuery} the main element for this view */
|
||||
$annotation: function() {
|
||||
return this.$el.find(".annotation");
|
||||
},
|
||||
|
||||
/** shut down event listeners and remove this view's DOM */
|
||||
remove : function(){
|
||||
this.$annotation.off();
|
||||
this.stopListening( this.model );
|
||||
Backbone.View.prototype.remove.call( this );
|
||||
},
|
||||
/** shut down event listeners and remove this view's DOM */
|
||||
remove: function() {
|
||||
this.$annotation.off();
|
||||
this.stopListening(this.model);
|
||||
Backbone.View.prototype.remove.call(this);
|
||||
},
|
||||
|
||||
/** string rep */
|
||||
toString : function(){ return [ 'AnnotationEditor(', this.model + '', ')' ].join(''); }
|
||||
});
|
||||
/** string rep */
|
||||
toString: function() {
|
||||
return ["AnnotationEditor(", `${this.model}`, ")"].join("");
|
||||
}
|
||||
});
|
||||
// =============================================================================
|
||||
return {
|
||||
AnnotationEditor : AnnotationEditor
|
||||
export default {
|
||||
AnnotationEditor: AnnotationEditor
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
define([
|
||||
'libs/underscore',
|
||||
'libs/backbone',
|
||||
'utils/add-logging',
|
||||
'utils/localization'
|
||||
], function( _, Backbone, addLogging, _l ){
|
||||
'use strict';
|
||||
import * as _ from "libs/underscore";
|
||||
import * as Backbone from "libs/backbone";
|
||||
import addLogging from "utils/add-logging";
|
||||
import _l from "utils/localization";
|
||||
|
||||
//==============================================================================
|
||||
/** @class Mixin to add logging capabilities to an object.
|
||||
@@ -28,49 +25,49 @@ define([
|
||||
* _.extend( MyModel.prototype, LoggableMixin )
|
||||
*
|
||||
*/
|
||||
var LoggableMixin = /** @lends LoggableMixin# */{
|
||||
var LoggableMixin = /** @lends LoggableMixin# */ {
|
||||
// replace null with console (if available) to see all logs for a particular view/model
|
||||
/** The logging object whose log function will be used to output
|
||||
* messages. Null will supress all logging. Commonly set to console.
|
||||
*/
|
||||
logger : null,
|
||||
logger: null,
|
||||
/** @type {String} a namespace for filtering/focusing log output */
|
||||
_logNamespace : '.',
|
||||
|
||||
_logNamespace: "."
|
||||
};
|
||||
addLogging( LoggableMixin );
|
||||
|
||||
addLogging(LoggableMixin);
|
||||
|
||||
//==============================================================================
|
||||
/** Backbone model that syncs to the browser's sessionStorage API.
|
||||
* This all largely happens behind the scenes and no special calls are required.
|
||||
*/
|
||||
var SessionStorageModel = Backbone.Model.extend({
|
||||
initialize : function( initialAttrs ){
|
||||
initialize: function(initialAttrs) {
|
||||
// check for sessionStorage and error if no id is provided
|
||||
this._checkEnabledSessionStorage();
|
||||
if( !initialAttrs.id ){
|
||||
throw new Error( 'SessionStorageModel requires an id in the initial attributes' );
|
||||
if (!initialAttrs.id) {
|
||||
throw new Error("SessionStorageModel requires an id in the initial attributes");
|
||||
}
|
||||
this.id = initialAttrs.id;
|
||||
|
||||
// load existing from storage (if any), clear any attrs set by bbone before init is called,
|
||||
// layer initial over existing and defaults, and save
|
||||
var existing = ( !this.isNew() )?( this._read( this ) ):( {} );
|
||||
var existing = !this.isNew() ? this._read(this) : {};
|
||||
this.clear({ silent: true });
|
||||
this.save( _.extend( {}, this.defaults, existing, initialAttrs ), { silent: true });
|
||||
this.save(_.extend({}, this.defaults, existing, initialAttrs), {
|
||||
silent: true
|
||||
});
|
||||
|
||||
// save on any change to it immediately
|
||||
this.on( 'change', function(){
|
||||
this.on("change", function() {
|
||||
this.save();
|
||||
});
|
||||
},
|
||||
|
||||
_checkEnabledSessionStorage : function(){
|
||||
_checkEnabledSessionStorage: function() {
|
||||
try {
|
||||
return window.sessionStorage.length >= 0;
|
||||
} catch( err ){
|
||||
alert( 'Please enable cookies in your browser for this Galaxy site' );
|
||||
} catch (err) {
|
||||
alert("Please enable cookies in your browser for this Galaxy site");
|
||||
return false;
|
||||
}
|
||||
},
|
||||
@@ -78,36 +75,48 @@ var SessionStorageModel = Backbone.Model.extend({
|
||||
/** override of bbone sync to save to sessionStorage rather than REST
|
||||
* bbone options (success, errror, etc.) should still apply
|
||||
*/
|
||||
sync : function( method, model, options ){
|
||||
if( !options.silent ){
|
||||
model.trigger( 'request', model, {}, options );
|
||||
sync: function(method, model, options) {
|
||||
if (!options.silent) {
|
||||
model.trigger("request", model, {}, options);
|
||||
}
|
||||
var returned = {};
|
||||
switch( method ){
|
||||
case 'create' : returned = this._create( model ); break;
|
||||
case 'read' : returned = this._read( model ); break;
|
||||
case 'update' : returned = this._update( model ); break;
|
||||
case 'delete' : returned = this._delete( model ); break;
|
||||
switch (method) {
|
||||
case "create":
|
||||
returned = this._create(model);
|
||||
break;
|
||||
case "read":
|
||||
returned = this._read(model);
|
||||
break;
|
||||
case "update":
|
||||
returned = this._update(model);
|
||||
break;
|
||||
case "delete":
|
||||
returned = this._delete(model);
|
||||
break;
|
||||
}
|
||||
if( returned !== undefined || returned !== null ){
|
||||
if( options.success ){ options.success(); }
|
||||
if (returned !== undefined || returned !== null) {
|
||||
if (options.success) {
|
||||
options.success();
|
||||
}
|
||||
} else {
|
||||
if( options.error ){ options.error(); }
|
||||
if (options.error) {
|
||||
options.error();
|
||||
}
|
||||
}
|
||||
return returned;
|
||||
},
|
||||
|
||||
/** set storage to the stringified item */
|
||||
_create : function( model ){
|
||||
_create: function(model) {
|
||||
try {
|
||||
var json = model.toJSON(),
|
||||
set = sessionStorage.setItem( model.id, JSON.stringify( json ) );
|
||||
return ( set === null )?( set ):( json );
|
||||
// DOMException is thrown in Safari if in private browsing mode and sessionStorage is attempted:
|
||||
// http://stackoverflow.com/questions/14555347
|
||||
// TODO: this could probably use a more general soln - like detecting priv. mode + safari => non-ajaxing Model
|
||||
} catch( err ){
|
||||
if( !( ( err instanceof DOMException ) && ( navigator.userAgent.indexOf("Safari") > -1 ) ) ){
|
||||
var json = model.toJSON();
|
||||
var set = sessionStorage.setItem(model.id, JSON.stringify(json));
|
||||
return set === null ? set : json;
|
||||
// DOMException is thrown in Safari if in private browsing mode and sessionStorage is attempted:
|
||||
// http://stackoverflow.com/questions/14555347
|
||||
// TODO: this could probably use a more general soln - like detecting priv. mode + safari => non-ajaxing Model
|
||||
} catch (err) {
|
||||
if (!(err instanceof DOMException && navigator.userAgent.indexOf("Safari") > -1)) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
@@ -115,37 +124,35 @@ var SessionStorageModel = Backbone.Model.extend({
|
||||
},
|
||||
|
||||
/** read and parse json from storage */
|
||||
_read : function( model ){
|
||||
return JSON.parse( sessionStorage.getItem( model.id ) );
|
||||
_read: function(model) {
|
||||
return JSON.parse(sessionStorage.getItem(model.id));
|
||||
},
|
||||
|
||||
/** set storage to the item (alias to create) */
|
||||
_update : function( model ){
|
||||
return model._create( model );
|
||||
_update: function(model) {
|
||||
return model._create(model);
|
||||
},
|
||||
|
||||
/** remove the item from storage */
|
||||
_delete : function( model ){
|
||||
return sessionStorage.removeItem( model.id );
|
||||
_delete: function(model) {
|
||||
return sessionStorage.removeItem(model.id);
|
||||
},
|
||||
|
||||
/** T/F whether sessionStorage contains the model's id (data is present) */
|
||||
isNew : function(){
|
||||
return !sessionStorage.hasOwnProperty( this.id );
|
||||
isNew: function() {
|
||||
return !sessionStorage.hasOwnProperty(this.id);
|
||||
},
|
||||
|
||||
_log : function(){
|
||||
return JSON.stringify( this.toJSON(), null, ' ' );
|
||||
_log: function() {
|
||||
return JSON.stringify(this.toJSON(), null, " ");
|
||||
},
|
||||
toString : function(){
|
||||
return 'SessionStorageModel(' + this.id + ')';
|
||||
toString: function() {
|
||||
return `SessionStorageModel(${this.id})`;
|
||||
}
|
||||
|
||||
});
|
||||
(function(){
|
||||
SessionStorageModel.prototype = _.omit( SessionStorageModel.prototype, 'url', 'urlRoot' );
|
||||
}());
|
||||
|
||||
(() => {
|
||||
SessionStorageModel.prototype = _.omit(SessionStorageModel.prototype, "url", "urlRoot");
|
||||
})();
|
||||
|
||||
//==============================================================================
|
||||
/** Function that allows mixing of hashs into bbone MVC while showing the mixins first
|
||||
@@ -156,14 +163,13 @@ var SessionStorageModel = Backbone.Model.extend({
|
||||
*
|
||||
* NOTE: this does not combine any hashes (like events, etc.) and you're expected to handle that
|
||||
*/
|
||||
function mixin( mixinHash1, /* mixinHash2, etc: ... variadic */ propsHash ){
|
||||
var args = Array.prototype.slice.call( arguments, 0 ),
|
||||
lastArg = args.pop();
|
||||
args.unshift( lastArg );
|
||||
return _.defaults.apply( _, args );
|
||||
function mixin(mixinHash1, /* mixinHash2, etc: ... variadic */ propsHash) {
|
||||
var args = Array.prototype.slice.call(arguments, 0);
|
||||
var lastArg = args.pop();
|
||||
args.unshift(lastArg);
|
||||
return _.defaults.apply(_, args);
|
||||
}
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** A mixin for models that allow T/F/Matching to their attributes - useful when
|
||||
* searching or filtering collections of models.
|
||||
@@ -173,9 +179,8 @@ function mixin( mixinHash1, /* mixinHash2, etc: ... variadic */ propsHash ){
|
||||
* and see readonly-history-view.searchHdas for how user input is connected to the filtering
|
||||
*/
|
||||
var SearchableModelMixin = {
|
||||
|
||||
/** what attributes of an HDA will be used in a text search */
|
||||
searchAttributes : [
|
||||
searchAttributes: [
|
||||
// override
|
||||
],
|
||||
|
||||
@@ -183,44 +188,53 @@ var SearchableModelMixin = {
|
||||
* attribute specifiers ('name="bler"') in a term, allow passing in aliases for the
|
||||
* following attr keys.
|
||||
*/
|
||||
searchAliases : {
|
||||
searchAliases: {
|
||||
// override
|
||||
},
|
||||
|
||||
/** search the attribute with key attrKey for the string searchFor; T/F if found */
|
||||
searchAttribute : function( attrKey, searchFor ){
|
||||
var attrVal = this.get( attrKey );
|
||||
searchAttribute: function(attrKey, searchFor) {
|
||||
var attrVal = this.get(attrKey);
|
||||
//this.debug( 'searchAttribute', attrKey, attrVal, searchFor );
|
||||
// bail if empty searchFor or unsearchable values
|
||||
if( !searchFor
|
||||
|| ( attrVal === undefined || attrVal === null ) ){
|
||||
if (!searchFor || (attrVal === undefined || attrVal === null)) {
|
||||
return false;
|
||||
}
|
||||
// pass to sep. fn for deep search of array attributes
|
||||
if( _.isArray( attrVal ) ){ return this._searchArrayAttribute( attrVal, searchFor ); }
|
||||
return ( attrVal.toString().toLowerCase().indexOf( searchFor.toLowerCase() ) !== -1 );
|
||||
if (_.isArray(attrVal)) {
|
||||
return this._searchArrayAttribute(attrVal, searchFor);
|
||||
}
|
||||
return (
|
||||
attrVal
|
||||
.toString()
|
||||
.toLowerCase()
|
||||
.indexOf(searchFor.toLowerCase()) !== -1
|
||||
);
|
||||
},
|
||||
|
||||
/** deep(er) search for array attributes; T/F if found */
|
||||
_searchArrayAttribute : function( array, searchFor ){
|
||||
_searchArrayAttribute: function(array, searchFor) {
|
||||
//this.debug( '_searchArrayAttribute', array, searchFor );
|
||||
searchFor = searchFor.toLowerCase();
|
||||
//precondition: searchFor has already been validated as non-empty string
|
||||
//precondition: assumes only 1 level array
|
||||
//TODO: could possibly break up searchFor more (CSV...)
|
||||
return _.any( array, function( elem ){
|
||||
return ( elem.toString().toLowerCase().indexOf( searchFor.toLowerCase() ) !== -1 );
|
||||
});
|
||||
return _.any(
|
||||
array,
|
||||
elem =>
|
||||
elem
|
||||
.toString()
|
||||
.toLowerCase()
|
||||
.indexOf(searchFor.toLowerCase()) !== -1
|
||||
);
|
||||
},
|
||||
|
||||
/** search all searchAttributes for the string searchFor,
|
||||
* returning a list of keys of attributes that contain searchFor
|
||||
*/
|
||||
search : function( searchFor ){
|
||||
search: function(searchFor) {
|
||||
var model = this;
|
||||
return _.filter( this.searchAttributes, function( key ){
|
||||
return model.searchAttribute( key, searchFor );
|
||||
});
|
||||
return _.filter(this.searchAttributes, key => model.searchAttribute(key, searchFor));
|
||||
},
|
||||
|
||||
/** alias of search, but returns a boolean; accepts attribute specifiers where
|
||||
@@ -231,17 +245,17 @@ var SearchableModelMixin = {
|
||||
* @param {String} term plain text or ATTR_SPECIFIER sep. key=val pair
|
||||
* @returns {Boolean} was term found in (any) attribute(s)
|
||||
*/
|
||||
matches : function( term ){
|
||||
var ATTR_SPECIFIER = '=',
|
||||
split = term.split( ATTR_SPECIFIER );
|
||||
matches: function(term) {
|
||||
var ATTR_SPECIFIER = "=";
|
||||
var split = term.split(ATTR_SPECIFIER);
|
||||
// attribute is specified - search only that
|
||||
if( split.length >= 2 ){
|
||||
if (split.length >= 2) {
|
||||
var attrKey = split[0];
|
||||
attrKey = this.searchAliases[ attrKey ] || attrKey;
|
||||
return this.searchAttribute( attrKey, split[1] );
|
||||
attrKey = this.searchAliases[attrKey] || attrKey;
|
||||
return this.searchAttribute(attrKey, split[1]);
|
||||
}
|
||||
// no attribute is specified - search all attributes in searchAttributes
|
||||
return !!this.search( term ).length;
|
||||
return !!this.search(term).length;
|
||||
},
|
||||
|
||||
/** an implicit AND search for all terms; IOW, a model must match all terms given
|
||||
@@ -250,18 +264,17 @@ var SearchableModelMixin = {
|
||||
* an HDA would have to have attributes containing blah AND bler AND a genome_build == hg19
|
||||
* To include whitespace in terms: wrap the term in double quotations (name="blah bler").
|
||||
*/
|
||||
matchesAll : function( terms ){
|
||||
matchesAll: function(terms) {
|
||||
var model = this;
|
||||
// break the terms up by whitespace and filter out the empty strings
|
||||
terms = terms.match( /(".*"|\w*=".*"|\S*)/g ).filter( function( s ){ return !!s; });
|
||||
return _.all( terms, function( term ){
|
||||
term = term.replace( /"/g, '' );
|
||||
return model.matches( term );
|
||||
terms = terms.match(/(".*"|\w*=".*"|\S*)/g).filter(s => !!s);
|
||||
return _.all(terms, term => {
|
||||
term = term.replace(/"/g, "");
|
||||
return model.matches(term);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** A view that renders hidden and shows when some activator is clicked.
|
||||
* options:
|
||||
@@ -276,74 +289,73 @@ var SearchableModelMixin = {
|
||||
* instance vars:
|
||||
* view.hidden {boolean} is the view in the hidden state
|
||||
*/
|
||||
var HiddenUntilActivatedViewMixin = /** @lends hiddenUntilActivatedMixin# */{
|
||||
//TODO: since this is a mixin, consider moving toggle, hidden into HUAVOptions
|
||||
var HiddenUntilActivatedViewMixin = /** @lends hiddenUntilActivatedMixin# */ {
|
||||
//TODO: since this is a mixin, consider moving toggle, hidden into HUAVOptions
|
||||
|
||||
/** call this in your initialize to set up the mixin
|
||||
* @param {jQuery} $activator the 'button' that's clicked to show/hide the view
|
||||
* @param {Object} hash with mixin options
|
||||
*/
|
||||
hiddenUntilActivated : function( $activator, options ){
|
||||
hiddenUntilActivated: function($activator, options) {
|
||||
// call this in your view's initialize fn
|
||||
options = options || {};
|
||||
//TODO: flesh out options - show them all here
|
||||
//TODO: flesh out options - show them all here
|
||||
this.HUAVOptions = {
|
||||
$elementShown : this.$el,
|
||||
showFn : jQuery.prototype.toggle,
|
||||
showSpeed : 'fast'
|
||||
$elementShown: this.$el,
|
||||
showFn: jQuery.prototype.toggle,
|
||||
showSpeed: "fast"
|
||||
};
|
||||
_.extend( this.HUAVOptions, options || {});
|
||||
_.extend(this.HUAVOptions, options || {});
|
||||
/** has this been shown already (and onshowFirstTime called)? */
|
||||
this.HUAVOptions.hasBeenShown = this.HUAVOptions.$elementShown.is( ':visible' );
|
||||
this.HUAVOptions.hasBeenShown = this.HUAVOptions.$elementShown.is(":visible");
|
||||
this.hidden = this.isHidden();
|
||||
|
||||
if( $activator ){
|
||||
if ($activator) {
|
||||
var mixin = this;
|
||||
$activator.on( 'click', function( ev ){
|
||||
mixin.toggle( mixin.HUAVOptions.showSpeed );
|
||||
$activator.on("click", ev => {
|
||||
mixin.toggle(mixin.HUAVOptions.showSpeed);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
//TODO:?? remove? use .hidden?
|
||||
//TODO:?? remove? use .hidden?
|
||||
/** returns T/F if the view is hidden */
|
||||
isHidden : function(){
|
||||
return ( this.HUAVOptions.$elementShown.is( ':hidden' ) );
|
||||
isHidden: function() {
|
||||
return this.HUAVOptions.$elementShown.is(":hidden");
|
||||
},
|
||||
|
||||
/** toggle the hidden state, show/hide $elementShown, call onshow/hide, trigger events */
|
||||
toggle : function(){
|
||||
//TODO: more specific name - toggle is too general
|
||||
toggle: function() {
|
||||
//TODO: more specific name - toggle is too general
|
||||
// can be called manually as well with normal toggle arguments
|
||||
//TODO: better as a callback (when the show/hide is actually done)
|
||||
// show
|
||||
if( this.hidden ){
|
||||
if (this.hidden) {
|
||||
// fire the optional fns on the first/each showing - good for render()
|
||||
if( !this.HUAVOptions.hasBeenShown ){
|
||||
if( _.isFunction( this.HUAVOptions.onshowFirstTime ) ){
|
||||
if (!this.HUAVOptions.hasBeenShown) {
|
||||
if (_.isFunction(this.HUAVOptions.onshowFirstTime)) {
|
||||
this.HUAVOptions.hasBeenShown = true;
|
||||
this.HUAVOptions.onshowFirstTime.call( this );
|
||||
this.HUAVOptions.onshowFirstTime.call(this);
|
||||
}
|
||||
}
|
||||
if( _.isFunction( this.HUAVOptions.onshow ) ){
|
||||
this.HUAVOptions.onshow.call( this );
|
||||
this.trigger( 'hiddenUntilActivated:shown', this );
|
||||
if (_.isFunction(this.HUAVOptions.onshow)) {
|
||||
this.HUAVOptions.onshow.call(this);
|
||||
this.trigger("hiddenUntilActivated:shown", this);
|
||||
}
|
||||
this.hidden = false;
|
||||
|
||||
// hide
|
||||
// hide
|
||||
} else {
|
||||
if( _.isFunction( this.HUAVOptions.onhide ) ){
|
||||
this.HUAVOptions.onhide.call( this );
|
||||
this.trigger( 'hiddenUntilActivated:hidden', this );
|
||||
if (_.isFunction(this.HUAVOptions.onhide)) {
|
||||
this.HUAVOptions.onhide.call(this);
|
||||
this.trigger("hiddenUntilActivated:hidden", this);
|
||||
}
|
||||
this.hidden = true;
|
||||
}
|
||||
return this.HUAVOptions.showFn.apply( this.HUAVOptions.$elementShown, arguments );
|
||||
return this.HUAVOptions.showFn.apply(this.HUAVOptions.$elementShown, arguments);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** Mixin for views that can be dragged and dropped
|
||||
* Allows for the drag behavior to be turned on/off, setting/removing jQuery event
|
||||
@@ -352,23 +364,22 @@ var HiddenUntilActivatedViewMixin = /** @lends hiddenUntilActivatedMixin# */{
|
||||
* Override '$dragHandle' to define the draggable DOM sub-element.
|
||||
*/
|
||||
var DraggableViewMixin = {
|
||||
|
||||
/** set up instance vars to track whether this view is currently draggable */
|
||||
initialize : function( attributes ){
|
||||
initialize: function(attributes) {
|
||||
/** is the body of this hda view expanded/not? */
|
||||
this.draggable = attributes.draggable || false;
|
||||
this.draggable = attributes.draggable || false;
|
||||
},
|
||||
|
||||
/** what part of the view's DOM triggers the dragging */
|
||||
$dragHandle : function(){
|
||||
//TODO: make abstract/general - move this to listItem
|
||||
$dragHandle: function() {
|
||||
//TODO: make abstract/general - move this to listItem
|
||||
// override to the element you want to be your view's handle
|
||||
return this.$( '.title-bar' );
|
||||
return this.$(".title-bar");
|
||||
},
|
||||
|
||||
/** toggle whether this view is draggable */
|
||||
toggleDraggable : function(){
|
||||
if( this.draggable ){
|
||||
toggleDraggable: function() {
|
||||
if (this.draggable) {
|
||||
this.draggableOff();
|
||||
} else {
|
||||
this.draggableOn();
|
||||
@@ -376,46 +387,49 @@ var DraggableViewMixin = {
|
||||
},
|
||||
|
||||
/** allow the view to be dragged, set up event handlers */
|
||||
draggableOn : function(){
|
||||
draggableOn: function() {
|
||||
this.draggable = true;
|
||||
this.dragStartHandler = _.bind( this._dragStartHandler, this );
|
||||
this.dragEndHandler = _.bind( this._dragEndHandler, this );
|
||||
this.dragStartHandler = _.bind(this._dragStartHandler, this);
|
||||
this.dragEndHandler = _.bind(this._dragEndHandler, this);
|
||||
|
||||
var handle = this.$dragHandle().attr( 'draggable', true ).get(0);
|
||||
handle.addEventListener( 'dragstart', this.dragStartHandler, false );
|
||||
handle.addEventListener( 'dragend', this.dragEndHandler, false );
|
||||
var handle = this.$dragHandle()
|
||||
.attr("draggable", true)
|
||||
.get(0);
|
||||
handle.addEventListener("dragstart", this.dragStartHandler, false);
|
||||
handle.addEventListener("dragend", this.dragEndHandler, false);
|
||||
},
|
||||
|
||||
/** turn of view dragging and remove event listeners */
|
||||
draggableOff : function(){
|
||||
draggableOff: function() {
|
||||
this.draggable = false;
|
||||
var handle = this.$dragHandle().attr( 'draggable', false ).get(0);
|
||||
handle.removeEventListener( 'dragstart', this.dragStartHandler, false );
|
||||
handle.removeEventListener( 'dragend', this.dragEndHandler, false );
|
||||
var handle = this.$dragHandle()
|
||||
.attr("draggable", false)
|
||||
.get(0);
|
||||
handle.removeEventListener("dragstart", this.dragStartHandler, false);
|
||||
handle.removeEventListener("dragend", this.dragEndHandler, false);
|
||||
},
|
||||
|
||||
/** sets the dataTransfer data to the model's toJSON
|
||||
* @fires draggable:dragstart (bbone event) which is passed the event and this view
|
||||
*/
|
||||
_dragStartHandler : function( event ){
|
||||
event.dataTransfer.effectAllowed = 'move';
|
||||
_dragStartHandler: function(event) {
|
||||
event.dataTransfer.effectAllowed = "move";
|
||||
//ASSUMES: this.model
|
||||
//TODO: all except IE: should be 'application/json', IE: must be 'text'
|
||||
event.dataTransfer.setData( 'text', JSON.stringify( this.model.toJSON() ) );
|
||||
this.trigger( 'draggable:dragstart', event, this );
|
||||
event.dataTransfer.setData("text", JSON.stringify(this.model.toJSON()));
|
||||
this.trigger("draggable:dragstart", event, this);
|
||||
return false;
|
||||
},
|
||||
|
||||
/** handle the dragend
|
||||
* @fires draggable:dragend (bbone event) which is passed the event and this view
|
||||
*/
|
||||
_dragEndHandler : function( event ){
|
||||
this.trigger( 'draggable:dragend', event, this );
|
||||
_dragEndHandler: function(event) {
|
||||
this.trigger("draggable:dragend", event, this);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** Mixin that allows a view to be selected (gen. from a list).
|
||||
* Selection controls ($selector) may be hidden/shown/toggled.
|
||||
@@ -426,31 +440,32 @@ var DraggableViewMixin = {
|
||||
* Both events are passed the view and the (jQuery) event.
|
||||
*/
|
||||
var SelectableViewMixin = {
|
||||
|
||||
/** Set up instance state vars for whether the selector is shown and whether the view has been selected */
|
||||
initialize : function( attributes ){
|
||||
initialize: function(attributes) {
|
||||
/** is the view currently in selection mode? */
|
||||
this.selectable = attributes.selectable || false;
|
||||
/** is the view currently selected? */
|
||||
this.selected = attributes.selected || false;
|
||||
this.selected = attributes.selected || false;
|
||||
},
|
||||
|
||||
/** $el sub-element where the selector is rendered and what can be clicked to select. */
|
||||
$selector : function(){
|
||||
return this.$( '.selector' );
|
||||
$selector: function() {
|
||||
return this.$(".selector");
|
||||
},
|
||||
|
||||
/** How the selector is rendered - defaults to font-awesome checkbox */
|
||||
_renderSelected : function(){
|
||||
_renderSelected: function() {
|
||||
// override
|
||||
this.$selector().find( 'span' )
|
||||
.toggleClass( 'fa-check-square-o', this.selected ).toggleClass( 'fa-square-o', !this.selected );
|
||||
this.$selector()
|
||||
.find("span")
|
||||
.toggleClass("fa-check-square-o", this.selected)
|
||||
.toggleClass("fa-square-o", !this.selected);
|
||||
},
|
||||
|
||||
/** Toggle whether the selector is shown */
|
||||
toggleSelector : function(){
|
||||
//TODO: use this.selectable
|
||||
if( !this.$selector().is( ':visible' ) ){
|
||||
toggleSelector: function() {
|
||||
//TODO: use this.selectable
|
||||
if (!this.$selector().is(":visible")) {
|
||||
this.showSelector();
|
||||
} else {
|
||||
this.hideSelector();
|
||||
@@ -461,14 +476,14 @@ var SelectableViewMixin = {
|
||||
* @param {Number} a jQuery fx speed
|
||||
* @fires: selectable which is passed true (IOW, the selector is shown) and the view
|
||||
*/
|
||||
showSelector : function( speed ){
|
||||
speed = speed !== undefined? speed : this.fxSpeed;
|
||||
showSelector: function(speed) {
|
||||
speed = speed !== undefined ? speed : this.fxSpeed;
|
||||
// make sure selected state is represented properly
|
||||
this.selectable = true;
|
||||
this.trigger( 'selectable', true, this );
|
||||
this.trigger("selectable", true, this);
|
||||
this._renderSelected();
|
||||
if( speed ){
|
||||
this.$selector().show( speed );
|
||||
if (speed) {
|
||||
this.$selector().show(speed);
|
||||
} else {
|
||||
this.$selector().show();
|
||||
}
|
||||
@@ -478,24 +493,24 @@ var SelectableViewMixin = {
|
||||
* @param {Number} a jQuery fx speed
|
||||
* @fires: selectable which is passed false (IOW, the selector is not shown) and the view
|
||||
*/
|
||||
hideSelector : function( speed ){
|
||||
speed = speed !== undefined? speed : this.fxSpeed;
|
||||
hideSelector: function(speed) {
|
||||
speed = speed !== undefined ? speed : this.fxSpeed;
|
||||
// reverse the process from showSelect
|
||||
this.selectable = false;
|
||||
this.trigger( 'selectable', false, this );
|
||||
if( speed ){
|
||||
this.$selector().hide( speed );
|
||||
this.trigger("selectable", false, this);
|
||||
if (speed) {
|
||||
this.$selector().hide(speed);
|
||||
} else {
|
||||
this.$selector().hide();
|
||||
}
|
||||
},
|
||||
|
||||
/** Toggle whether the view is selected */
|
||||
toggleSelect : function( event ){
|
||||
if( this.selected ){
|
||||
this.deselect( event );
|
||||
toggleSelect: function(event) {
|
||||
if (this.selected) {
|
||||
this.deselect(event);
|
||||
} else {
|
||||
this.select( event );
|
||||
this.select(event);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -503,10 +518,10 @@ var SelectableViewMixin = {
|
||||
* @param {Event} a jQuery event that caused the selection
|
||||
* @fires: selected which is passed the view and the DOM event that triggered it (optionally)
|
||||
*/
|
||||
select : function( event ){
|
||||
select: function(event) {
|
||||
// switch icon, set selected, and trigger event
|
||||
if( !this.selected ){
|
||||
this.trigger( 'selected', this, event );
|
||||
if (!this.selected) {
|
||||
this.trigger("selected", this, event);
|
||||
this.selected = true;
|
||||
this._renderSelected();
|
||||
}
|
||||
@@ -517,10 +532,10 @@ var SelectableViewMixin = {
|
||||
* @param {Event} a jQuery event that caused the selection
|
||||
* @fires: de-selected which is passed the view and the DOM event that triggered it (optionally)
|
||||
*/
|
||||
deselect : function( event ){
|
||||
deselect: function(event) {
|
||||
// switch icon, set selected, and trigger event
|
||||
if( this.selected ){
|
||||
this.trigger( 'de-selected', this, event );
|
||||
if (this.selected) {
|
||||
this.trigger("de-selected", this, event);
|
||||
this.selected = false;
|
||||
this._renderSelected();
|
||||
}
|
||||
@@ -528,7 +543,6 @@ var SelectableViewMixin = {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** Return an underscore template fn from an array of strings.
|
||||
* @param {String[]} template the template strings to compile into the underscore template fn
|
||||
@@ -552,40 +566,37 @@ var SelectableViewMixin = {
|
||||
* Meant to be called in a View's definition in order to compile only once.
|
||||
*
|
||||
*/
|
||||
function wrapTemplate( template, jsonNamespace ){
|
||||
jsonNamespace = jsonNamespace || 'model';
|
||||
var templateFn = _.template( template.join( '' ) );
|
||||
return function( json, view ){
|
||||
var templateVars = { view : view || {}, _l : _l };
|
||||
templateVars[ jsonNamespace ] = json || {};
|
||||
return templateFn( templateVars );
|
||||
function wrapTemplate(template, jsonNamespace) {
|
||||
jsonNamespace = jsonNamespace || "model";
|
||||
var templateFn = _.template(template.join(""));
|
||||
return (json, view) => {
|
||||
var templateVars = { view: view || {}, _l: _l };
|
||||
templateVars[jsonNamespace] = json || {};
|
||||
return templateFn(templateVars);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** Return a comparator function for sorted Collections */
|
||||
function buildComparator( attribute_name, options ){
|
||||
function buildComparator(attribute_name, options) {
|
||||
options = options || {};
|
||||
var ascending = options.ascending? 1 : -1;
|
||||
return function __comparator( a, b ){
|
||||
a = a.get( attribute_name );
|
||||
b = b.get( attribute_name );
|
||||
return ( a < b? -1 : ( a > b? 1 : 0 ) ) * ascending;
|
||||
var ascending = options.ascending ? 1 : -1;
|
||||
return function __comparator(a, b) {
|
||||
a = a.get(attribute_name);
|
||||
b = b.get(attribute_name);
|
||||
return (a < b ? -1 : a > b ? 1 : 0) * ascending;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
//==============================================================================
|
||||
return {
|
||||
LoggableMixin : LoggableMixin,
|
||||
SessionStorageModel : SessionStorageModel,
|
||||
mixin : mixin,
|
||||
SearchableModelMixin : SearchableModelMixin,
|
||||
HiddenUntilActivatedViewMixin : HiddenUntilActivatedViewMixin,
|
||||
DraggableViewMixin : DraggableViewMixin,
|
||||
SelectableViewMixin : SelectableViewMixin,
|
||||
wrapTemplate : wrapTemplate,
|
||||
buildComparator : buildComparator,
|
||||
};
|
||||
});
|
||||
export default {
|
||||
LoggableMixin: LoggableMixin,
|
||||
SessionStorageModel: SessionStorageModel,
|
||||
mixin: mixin,
|
||||
SearchableModelMixin: SearchableModelMixin,
|
||||
HiddenUntilActivatedViewMixin: HiddenUntilActivatedViewMixin,
|
||||
DraggableViewMixin: DraggableViewMixin,
|
||||
SelectableViewMixin: SelectableViewMixin,
|
||||
wrapTemplate: wrapTemplate,
|
||||
buildComparator: buildComparator
|
||||
};
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
define([
|
||||
'libs/underscore',
|
||||
'libs/backbone',
|
||||
'mvc/base-mvc',
|
||||
], function( _, Backbone, BASE_MVC ){
|
||||
'use strict';
|
||||
import * as _ from "libs/underscore";
|
||||
import * as Backbone from "libs/backbone";
|
||||
import BASE_MVC from "mvc/base-mvc";
|
||||
|
||||
//=============================================================================
|
||||
/**
|
||||
@@ -11,34 +8,33 @@ define([
|
||||
* @type {Backbone.Collection}
|
||||
*/
|
||||
var ControlledFetchCollection = Backbone.Collection.extend({
|
||||
|
||||
/** call setOrder on initialization to build the comparator based on options */
|
||||
initialize : function( models, options ){
|
||||
Backbone.Collection.prototype.initialize.call( this, models, options );
|
||||
this.setOrder( options.order || this.order, { silent: true });
|
||||
initialize: function(models, options) {
|
||||
Backbone.Collection.prototype.initialize.call(this, models, options);
|
||||
this.setOrder(options.order || this.order, { silent: true });
|
||||
},
|
||||
|
||||
/** set up to track order changes and re-sort when changed */
|
||||
_setUpListeners : function(){
|
||||
_setUpListeners: function() {
|
||||
return this.on({
|
||||
'changed-order' : this.sort
|
||||
"changed-order": this.sort
|
||||
});
|
||||
},
|
||||
|
||||
/** override to provide order and offsets based on instance vars, set limit if passed,
|
||||
* and set allFetched/fire 'all-fetched' when xhr returns
|
||||
*/
|
||||
fetch : function( options ){
|
||||
options = this._buildFetchOptions( options );
|
||||
// console.log( 'fetch options:', options );
|
||||
return Backbone.Collection.prototype.fetch.call( this, options );
|
||||
fetch: function(options) {
|
||||
options = this._buildFetchOptions(options);
|
||||
Galaxy.debug("fetch options:", options);
|
||||
return Backbone.Collection.prototype.fetch.call(this, options);
|
||||
},
|
||||
|
||||
/** build ajax data/parameters from options */
|
||||
_buildFetchOptions : function( options ){
|
||||
_buildFetchOptions: function(options) {
|
||||
// note: we normally want options passed in to override the defaults built here
|
||||
// so most of these fns will generate defaults
|
||||
options = _.clone( options ) || {};
|
||||
options = _.clone(options) || {};
|
||||
var self = this;
|
||||
|
||||
// jquery ajax option; allows multiple q/qv for filters (instead of 'q[]')
|
||||
@@ -51,191 +47,206 @@ var ControlledFetchCollection = Backbone.Collection.extend({
|
||||
// to the pagination options too
|
||||
// (i.e. this.on( 'sync', function( options ){ if( options.limit ){ ... } }))
|
||||
// however, when we send to xhr/jquery we copy them to data also so that they become API query params
|
||||
options.data = options.data || self._buildFetchData( options );
|
||||
// console.log( 'data:', options.data );
|
||||
options.data = options.data || self._buildFetchData(options);
|
||||
Galaxy.debug("data:", options.data);
|
||||
|
||||
// options.data.filters --> options.data.q, options.data.qv
|
||||
var filters = this._buildFetchFilters( options );
|
||||
// console.log( 'filters:', filters );
|
||||
if( !_.isEmpty( filters ) ){
|
||||
_.extend( options.data, this._fetchFiltersToAjaxData( filters ) );
|
||||
var filters = this._buildFetchFilters(options);
|
||||
Galaxy.debug("filters:", filters);
|
||||
if (!_.isEmpty(filters)) {
|
||||
_.extend(options.data, this._fetchFiltersToAjaxData(filters));
|
||||
}
|
||||
// console.log( 'data:', options.data );
|
||||
Galaxy.debug("data:", options.data);
|
||||
return options;
|
||||
},
|
||||
|
||||
/** Build the dictionary to send to fetch's XHR as data */
|
||||
_buildFetchData : function( options ){
|
||||
_buildFetchData: function(options) {
|
||||
var defaults = {};
|
||||
if( this.order ){ defaults.order = this.order; }
|
||||
return _.defaults( _.pick( options, this._fetchParams ), defaults );
|
||||
if (this.order) {
|
||||
defaults.order = this.order;
|
||||
}
|
||||
return _.defaults(_.pick(options, this._fetchParams), defaults);
|
||||
},
|
||||
|
||||
/** These attribute keys are valid params to fetch/API-index */
|
||||
_fetchParams : [
|
||||
_fetchParams: [
|
||||
/** model dependent string to control the order of models returned */
|
||||
'order',
|
||||
"order",
|
||||
/** limit the number of models returned from a fetch */
|
||||
'limit',
|
||||
"limit",
|
||||
/** skip this number of models when fetching */
|
||||
'offset',
|
||||
"offset",
|
||||
/** what series of attributes to return (model dependent) */
|
||||
'view',
|
||||
"view",
|
||||
/** individual keys to return for the models (see api/histories.index) */
|
||||
'keys'
|
||||
"keys"
|
||||
],
|
||||
|
||||
/** add any needed filters here based on collection state */
|
||||
_buildFetchFilters : function( options ){
|
||||
_buildFetchFilters: function(options) {
|
||||
// override
|
||||
return _.clone( options.filters || {} );
|
||||
return _.clone(options.filters || {});
|
||||
},
|
||||
|
||||
/** Convert dictionary filters to qqv style arrays */
|
||||
_fetchFiltersToAjaxData : function( filters ){
|
||||
_fetchFiltersToAjaxData: function(filters) {
|
||||
// return as a map so ajax.data can extend from it
|
||||
var filterMap = {
|
||||
q : [],
|
||||
qv : []
|
||||
q: [],
|
||||
qv: []
|
||||
};
|
||||
_.each( filters, function( v, k ){
|
||||
_.each(filters, (v, k) => {
|
||||
// don't send if filter value is empty
|
||||
if( v === undefined || v === '' ){ return; }
|
||||
if (v === undefined || v === "") {
|
||||
return;
|
||||
}
|
||||
// json to python
|
||||
if( v === true ){ v = 'True'; }
|
||||
if( v === false ){ v = 'False'; }
|
||||
if( v === null ){ v = 'None'; }
|
||||
if (v === true) {
|
||||
v = "True";
|
||||
}
|
||||
if (v === false) {
|
||||
v = "False";
|
||||
}
|
||||
if (v === null) {
|
||||
v = "None";
|
||||
}
|
||||
// map to k/v arrays (q/qv)
|
||||
filterMap.q.push( k );
|
||||
filterMap.qv.push( v );
|
||||
filterMap.q.push(k);
|
||||
filterMap.qv.push(v);
|
||||
});
|
||||
return filterMap;
|
||||
},
|
||||
|
||||
/** override to reset allFetched flag to false */
|
||||
reset : function( models, options ){
|
||||
reset: function(models, options) {
|
||||
this.allFetched = false;
|
||||
return Backbone.Collection.prototype.reset.call( this, models, options );
|
||||
return Backbone.Collection.prototype.reset.call(this, models, options);
|
||||
},
|
||||
|
||||
// ........................................................................ order
|
||||
order : null,
|
||||
order: null,
|
||||
|
||||
/** @type {Object} map of collection available sorting orders containing comparator fns */
|
||||
comparators : {
|
||||
'update_time' : BASE_MVC.buildComparator( 'update_time', { ascending: false }),
|
||||
'update_time-asc' : BASE_MVC.buildComparator( 'update_time', { ascending: true }),
|
||||
'create_time' : BASE_MVC.buildComparator( 'create_time', { ascending: false }),
|
||||
'create_time-asc' : BASE_MVC.buildComparator( 'create_time', { ascending: true }),
|
||||
comparators: {
|
||||
update_time: BASE_MVC.buildComparator("update_time", {
|
||||
ascending: false
|
||||
}),
|
||||
"update_time-asc": BASE_MVC.buildComparator("update_time", {
|
||||
ascending: true
|
||||
}),
|
||||
create_time: BASE_MVC.buildComparator("create_time", {
|
||||
ascending: false
|
||||
}),
|
||||
"create_time-asc": BASE_MVC.buildComparator("create_time", {
|
||||
ascending: true
|
||||
})
|
||||
},
|
||||
|
||||
/** set the order and comparator for this collection then sort with the new order
|
||||
* @event 'changed-order' passed the new order and the collection
|
||||
*/
|
||||
setOrder : function( order, options ){
|
||||
setOrder: function(order, options) {
|
||||
options = options || {};
|
||||
var collection = this;
|
||||
var comparator = collection.comparators[ order ];
|
||||
if( _.isUndefined( comparator ) ){ throw new Error( 'unknown order: ' + order ); }
|
||||
var comparator = collection.comparators[order];
|
||||
if (_.isUndefined(comparator)) {
|
||||
throw new Error(`unknown order: ${order}`);
|
||||
}
|
||||
// if( _.isUndefined( comparator ) ){ return; }
|
||||
if( comparator === collection.comparator ){ return; }
|
||||
if (comparator === collection.comparator) {
|
||||
return;
|
||||
}
|
||||
|
||||
var oldOrder = collection.order;
|
||||
collection.order = order;
|
||||
collection.comparator = comparator;
|
||||
|
||||
if( !options.silent ){
|
||||
collection.trigger( 'changed-order', options );
|
||||
if (!options.silent) {
|
||||
collection.trigger("changed-order", options);
|
||||
}
|
||||
return collection;
|
||||
},
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
//=============================================================================
|
||||
/**
|
||||
*
|
||||
*/
|
||||
var PaginatedCollection = ControlledFetchCollection.extend({
|
||||
|
||||
/** @type {Number} limit used for each page's fetch */
|
||||
limitPerPage : 500,
|
||||
limitPerPage: 500,
|
||||
|
||||
initialize : function( models, options ){
|
||||
ControlledFetchCollection.prototype.initialize.call( this, models, options );
|
||||
initialize: function(models, options) {
|
||||
ControlledFetchCollection.prototype.initialize.call(this, models, options);
|
||||
this.currentPage = options.currentPage || 0;
|
||||
},
|
||||
|
||||
getTotalItemCount : function(){
|
||||
getTotalItemCount: function() {
|
||||
return this.length;
|
||||
},
|
||||
|
||||
shouldPaginate : function(){
|
||||
shouldPaginate: function() {
|
||||
return this.getTotalItemCount() >= this.limitPerPage;
|
||||
},
|
||||
|
||||
getLastPage : function(){
|
||||
return Math.floor( this.getTotalItemCount() / this.limitPerPage );
|
||||
getLastPage: function() {
|
||||
return Math.floor(this.getTotalItemCount() / this.limitPerPage);
|
||||
},
|
||||
|
||||
getPageCount : function(){
|
||||
getPageCount: function() {
|
||||
return this.getLastPage() + 1;
|
||||
},
|
||||
|
||||
getPageLimitOffset : function( pageNum ){
|
||||
pageNum = this.constrainPageNum( pageNum );
|
||||
getPageLimitOffset: function(pageNum) {
|
||||
pageNum = this.constrainPageNum(pageNum);
|
||||
return {
|
||||
limit : this.limitPerPage,
|
||||
limit: this.limitPerPage,
|
||||
offset: pageNum * this.limitPerPage
|
||||
};
|
||||
},
|
||||
|
||||
constrainPageNum : function( pageNum ){
|
||||
return Math.max( 0, Math.min( pageNum, this.getLastPage() ));
|
||||
constrainPageNum: function(pageNum) {
|
||||
return Math.max(0, Math.min(pageNum, this.getLastPage()));
|
||||
},
|
||||
|
||||
/** fetch the next page of data */
|
||||
fetchPage : function( pageNum, options ){
|
||||
fetchPage: function(pageNum, options) {
|
||||
var self = this;
|
||||
pageNum = self.constrainPageNum( pageNum );
|
||||
pageNum = self.constrainPageNum(pageNum);
|
||||
self.currentPage = pageNum;
|
||||
options = _.defaults( options || {}, self.getPageLimitOffset( pageNum ) );
|
||||
options = _.defaults(options || {}, self.getPageLimitOffset(pageNum));
|
||||
|
||||
self.trigger( 'fetching-more' );
|
||||
return self.fetch( options )
|
||||
.always( function(){
|
||||
self.trigger( 'fetching-more-done' );
|
||||
});
|
||||
self.trigger("fetching-more");
|
||||
return self.fetch(options).always(() => {
|
||||
self.trigger("fetching-more-done");
|
||||
});
|
||||
},
|
||||
|
||||
fetchCurrentPage : function( options ){
|
||||
return this.fetchPage( this.currentPage, options );
|
||||
fetchCurrentPage: function(options) {
|
||||
return this.fetchPage(this.currentPage, options);
|
||||
},
|
||||
|
||||
fetchPrevPage : function( options ){
|
||||
return this.fetchPage( this.currentPage - 1, options );
|
||||
fetchPrevPage: function(options) {
|
||||
return this.fetchPage(this.currentPage - 1, options);
|
||||
},
|
||||
|
||||
fetchNextPage : function( options ){
|
||||
return this.fetchPage( this.currentPage + 1, options );
|
||||
},
|
||||
fetchNextPage: function(options) {
|
||||
return this.fetchPage(this.currentPage + 1, options);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
//=============================================================================
|
||||
/**
|
||||
* A Collection that will load more elements without reseting.
|
||||
*/
|
||||
var InfinitelyScrollingCollection = ControlledFetchCollection.extend({
|
||||
|
||||
/** @type {Number} limit used for the first fetch (or a reset) */
|
||||
limitOnFirstFetch : null,
|
||||
limitOnFirstFetch: null,
|
||||
/** @type {Number} limit used for each subsequent fetch */
|
||||
limitPerFetch : 100,
|
||||
limitPerFetch: 100,
|
||||
|
||||
initialize : function( models, options ){
|
||||
ControlledFetchCollection.prototype.initialize.call( this, models, options );
|
||||
initialize: function(models, options) {
|
||||
ControlledFetchCollection.prototype.initialize.call(this, models, options);
|
||||
/** @type {Integer} number of contents to return from the first fetch */
|
||||
this.limitOnFirstFetch = options.limitOnFirstFetch || this.limitOnFirstFetch;
|
||||
/** @type {Integer} limit for every fetch after the first */
|
||||
@@ -247,81 +258,87 @@ var InfinitelyScrollingCollection = ControlledFetchCollection.extend({
|
||||
},
|
||||
|
||||
/** build ajax data/parameters from options */
|
||||
_buildFetchOptions : function( options ){
|
||||
_buildFetchOptions: function(options) {
|
||||
// options (options for backbone.fetch and jquery.ajax generally)
|
||||
// backbone option; false here to make fetching an addititive process
|
||||
options.remove = options.remove || false;
|
||||
return ControlledFetchCollection.prototype._buildFetchOptions.call( this, options );
|
||||
return ControlledFetchCollection.prototype._buildFetchOptions.call(this, options);
|
||||
},
|
||||
|
||||
/** fetch the first 'page' of data */
|
||||
fetchFirst : function( options ){
|
||||
// console.log( 'ControlledFetchCollection.fetchFirst:', options );
|
||||
options = options? _.clone( options ) : {};
|
||||
fetchFirst: function(options) {
|
||||
Galaxy.debug("ControlledFetchCollection.fetchFirst:", options);
|
||||
options = options ? _.clone(options) : {};
|
||||
this.allFetched = false;
|
||||
this.lastFetched = 0;
|
||||
return this.fetchMore( _.defaults( options, {
|
||||
reset : true,
|
||||
limit : this.limitOnFirstFetch,
|
||||
}));
|
||||
return this.fetchMore(
|
||||
_.defaults(options, {
|
||||
reset: true,
|
||||
limit: this.limitOnFirstFetch
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
/** fetch the next page of data */
|
||||
fetchMore : function( options ){
|
||||
// console.log( 'ControlledFetchCollection.fetchMore:', options );
|
||||
options = _.clone( options || {} );
|
||||
fetchMore: function(options) {
|
||||
Galaxy.debug("ControlledFetchCollection.fetchMore:", options);
|
||||
options = _.clone(options || {});
|
||||
var collection = this;
|
||||
|
||||
// console.log( 'fetchMore, options.reset:', options.reset );
|
||||
if( ( !options.reset && collection.allFetched ) ){
|
||||
Galaxy.debug("fetchMore, options.reset:", options.reset);
|
||||
if (!options.reset && collection.allFetched) {
|
||||
return jQuery.when();
|
||||
}
|
||||
|
||||
// TODO: this fails in the edge case where
|
||||
// the first fetch offset === limit (limit 4, offset 4, collection.length 4)
|
||||
options.offset = options.reset? 0 : ( options.offset || collection.lastFetched );
|
||||
var limit = options.limit = options.limit || collection.limitPerFetch || null;
|
||||
// console.log( 'fetchMore, limit:', limit, 'offset:', options.offset );
|
||||
if (options.reset) {
|
||||
options.offset = 0;
|
||||
} else if (options.offset === undefined) {
|
||||
options.offset = collection.lastFetched;
|
||||
}
|
||||
var limit = (options.limit = options.limit || collection.limitPerFetch || null);
|
||||
Galaxy.debug("fetchMore, limit:", limit, "offset:", options.offset);
|
||||
|
||||
collection.trigger( 'fetching-more' );
|
||||
return collection.fetch( options )
|
||||
.always( function(){
|
||||
collection.trigger( 'fetching-more-done' );
|
||||
})
|
||||
// maintain allFetched flag and trigger if all were fetched this time
|
||||
.done( function _postFetchMore( fetchedData ){
|
||||
var numFetched = _.isArray( fetchedData )? fetchedData.length : 0;
|
||||
collection.lastFetched += numFetched;
|
||||
// console.log( 'fetchMore, lastFetched:', collection.lastFetched );
|
||||
// anything less than a full page means we got all there is to get
|
||||
if( !limit || numFetched < limit ){
|
||||
collection.allFetched = true;
|
||||
collection.trigger( 'all-fetched', this );
|
||||
}
|
||||
}
|
||||
collection.trigger("fetching-more");
|
||||
return (
|
||||
collection
|
||||
.fetch(options)
|
||||
.always(() => {
|
||||
collection.trigger("fetching-more-done");
|
||||
})
|
||||
// maintain allFetched flag and trigger if all were fetched this time
|
||||
.done(function _postFetchMore(fetchedData) {
|
||||
var numFetched = _.isArray(fetchedData) ? fetchedData.length : 0;
|
||||
collection.lastFetched += numFetched;
|
||||
Galaxy.debug("fetchMore, lastFetched:", collection.lastFetched);
|
||||
// anything less than a full page means we got all there is to get
|
||||
if (!limit || numFetched < limit) {
|
||||
collection.allFetched = true;
|
||||
collection.trigger("all-fetched", this);
|
||||
}
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
/** fetch all the collection */
|
||||
fetchAll : function( options ){
|
||||
fetchAll: function(options) {
|
||||
// whitelist options to prevent allowing limit/offset/filters
|
||||
// (use vanilla fetch instead)
|
||||
options = options || {};
|
||||
var self = this;
|
||||
options = _.pick( options, 'silent' );
|
||||
options = _.pick(options, "silent");
|
||||
options.filters = {};
|
||||
return self.fetch( options ).done( function( fetchData ){
|
||||
return self.fetch(options).done(() => {
|
||||
self.allFetched = true;
|
||||
self.trigger( 'all-fetched', self );
|
||||
self.trigger("all-fetched", self);
|
||||
});
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
//==============================================================================
|
||||
return {
|
||||
ControlledFetchCollection : ControlledFetchCollection,
|
||||
PaginatedCollection : PaginatedCollection,
|
||||
InfinitelyScrollingCollection : InfinitelyScrollingCollection,
|
||||
};
|
||||
});
|
||||
export default {
|
||||
ControlledFetchCollection: ControlledFetchCollection,
|
||||
PaginatedCollection: PaginatedCollection,
|
||||
InfinitelyScrollingCollection: InfinitelyScrollingCollection
|
||||
};
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
define([
|
||||
"libs/bibtex",
|
||||
"mvc/base-mvc",
|
||||
"utils/localization"
|
||||
], function( parseBibtex, baseMVC, _l ){
|
||||
/* global Backbone */
|
||||
// we use amd here to require, but bibtex uses a global or commonjs pattern.
|
||||
// webpack will load via commonjs and plain requirejs will load as global. Check both
|
||||
parseBibtex = parseBibtex || window.BibtexParser;
|
||||
|
||||
var logNamespace = 'citation';
|
||||
//==============================================================================
|
||||
/** @class model for tool citations.
|
||||
* @name Citation
|
||||
* @augments Backbone.Model
|
||||
*/
|
||||
var Citation = Backbone.Model.extend( baseMVC.LoggableMixin ).extend({
|
||||
_logNamespace : logNamespace,
|
||||
|
||||
defaults : {
|
||||
content: ''
|
||||
},
|
||||
|
||||
initialize: function() {
|
||||
var parsed;
|
||||
try {
|
||||
// TODO: to model.parse/.validate
|
||||
parsed = parseBibtex( this.attributes.content );
|
||||
} catch( err ){
|
||||
return;
|
||||
}
|
||||
// bibtex returns successfully parsed in .entries and any parsing errors in .errors
|
||||
if( parsed.errors.length ){
|
||||
// the gen. form of these errors seems to be [ line, col, char, error message ]
|
||||
var errors = parsed.errors.reduce( function( all, current ){ return all + '; ' + current; });
|
||||
// throw new Error( 'Error parsing bibtex: ' + errors );
|
||||
this.log( 'Error parsing bibtex: ' + errors );
|
||||
}
|
||||
|
||||
this._fields = {};
|
||||
this.entry = _.first( parsed.entries );
|
||||
if( this.entry ){
|
||||
var rawFields = this.entry.Fields;
|
||||
for( var key in rawFields ){
|
||||
var value = rawFields[ key ];
|
||||
var lowerKey = key.toLowerCase();
|
||||
this._fields[ lowerKey ] = value;
|
||||
}
|
||||
}
|
||||
},
|
||||
entryType: function() {
|
||||
return this.entry? this.entry.EntryType : undefined;
|
||||
},
|
||||
fields: function() {
|
||||
return this._fields;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** @class Backbone collection of citations.
|
||||
*/
|
||||
var BaseCitationCollection = Backbone.Collection.extend( baseMVC.LoggableMixin ).extend( {
|
||||
_logNamespace : logNamespace,
|
||||
|
||||
/** root api url */
|
||||
urlRoot : Galaxy.root + 'api',
|
||||
partial : true, // Assume some tools in history/workflow may not be properly annotated yet.
|
||||
model : Citation,
|
||||
} );
|
||||
|
||||
var HistoryCitationCollection = BaseCitationCollection.extend( {
|
||||
/** complete api url */
|
||||
url : function() {
|
||||
return this.urlRoot + '/histories/' + this.history_id + '/citations';
|
||||
}
|
||||
} );
|
||||
|
||||
var ToolCitationCollection = BaseCitationCollection.extend( {
|
||||
/** complete api url */
|
||||
url : function() {
|
||||
return this.urlRoot + '/tools/' + this.tool_id + '/citations';
|
||||
},
|
||||
partial : false, // If a tool has citations, assume they are complete.
|
||||
} );
|
||||
|
||||
|
||||
//==============================================================================
|
||||
|
||||
return {
|
||||
Citation : Citation,
|
||||
HistoryCitationCollection : HistoryCitationCollection,
|
||||
ToolCitationCollection: ToolCitationCollection
|
||||
};
|
||||
|
||||
|
||||
});
|
||||
@@ -1,188 +0,0 @@
|
||||
define([
|
||||
"mvc/base-mvc",
|
||||
"mvc/citation/citation-model",
|
||||
"utils/localization"
|
||||
], function( baseMVC, citationModel, _l ){
|
||||
|
||||
var CitationView = Backbone.View.extend({
|
||||
tagName: 'div',
|
||||
className: 'citations',
|
||||
render: function() {
|
||||
this.$el.append( "<p>" + this.formattedReference() + "</p>" );
|
||||
return this;
|
||||
},
|
||||
formattedReference: function() {
|
||||
var model = this.model;
|
||||
var entryType = model.entryType();
|
||||
var fields = model.fields();
|
||||
|
||||
var ref = "";
|
||||
// Code inspired by...
|
||||
// https://github.com/vkaravir/bib-publication-list/blob/master/src/bib-publication-list.js
|
||||
var authorsAndYear = this._asSentence( (fields.author ? fields.author : "") + (fields.year ? (" (" + fields.year + ")") : "") ) + " ";
|
||||
var title = fields.title || "";
|
||||
var pages = fields.pages ? ("pp. " + fields.pages) : "";
|
||||
var address = fields.address;
|
||||
if( entryType == "article" ) {
|
||||
var volume = (fields.volume ? fields.volume : "") +
|
||||
(fields.number ? ( " (" + fields.number + ")" ) : "") +
|
||||
(pages ? ", " + pages : "");
|
||||
ref = authorsAndYear + this._asSentence(title) +
|
||||
(fields.journal ? ("In <em>" + fields.journal + ", ") : "") +
|
||||
this._asSentence(volume) +
|
||||
this._asSentence(fields.address) +
|
||||
"<\/em>";
|
||||
} else if( entryType == "inproceedings" || entryType == "proceedings" ) {
|
||||
ref = authorsAndYear +
|
||||
this._asSentence(title) +
|
||||
(fields.booktitle ? ("In <em>" + fields.booktitle + ", ") : "") +
|
||||
(pages ? pages : "") +
|
||||
(address ? ", " + address : "") +
|
||||
".<\/em>";
|
||||
} else if( entryType == "mastersthesis" || entryType == "phdthesis" ) {
|
||||
ref = authorsAndYear + this._asSentence(title) +
|
||||
(fields.howpublished ? fields.howpublished + ". " : "") +
|
||||
(fields.note ? fields.note + "." : "");
|
||||
} else if( entryType == "techreport" ) {
|
||||
ref = authorsAndYear + this._asSentence(title) +
|
||||
this._asSentence(fields.institution) +
|
||||
this._asSentence(fields.number) +
|
||||
this._asSentence(fields.type);
|
||||
} else if( entryType == "book" || entryType == "inbook" || entryType == "incollection" ) {
|
||||
ref = authorsAndYear + " " + this._formatBookInfo(fields);
|
||||
} else {
|
||||
ref = authorsAndYear + " " + this._asSentence(title) +
|
||||
this._asSentence(fields.howpublished) +
|
||||
this._asSentence(fields.note);
|
||||
}
|
||||
var doiUrl = "";
|
||||
if( fields.doi ) {
|
||||
doiUrl = 'http://dx.doi.org/' + fields.doi;
|
||||
ref += '[<a href="' + doiUrl + '" target="_blank">doi:' + fields.doi + "</a>]";
|
||||
}
|
||||
var url = fields.url || doiUrl;
|
||||
if( url ) {
|
||||
ref += '[<a href="' + url + '" target="_blank">Link</a>]';
|
||||
}
|
||||
return ref;
|
||||
},
|
||||
_formatBookInfo: function(fields) {
|
||||
var info = "";
|
||||
if( fields.chapter ) {
|
||||
info += fields.chapter + " in ";
|
||||
}
|
||||
if( fields.title ) {
|
||||
info += "<em>" + fields.title + "<\/em>";
|
||||
}
|
||||
if( fields.editor ) {
|
||||
info += ", Edited by " + fields.editor + ", ";
|
||||
}
|
||||
if( fields.publisher) {
|
||||
info += ", " + fields.publisher;
|
||||
}
|
||||
if( fields.pages ) {
|
||||
info += ", pp. " + fields.pages + "";
|
||||
}
|
||||
if( fields.series ) {
|
||||
info += ", <em>" + fields.series + "<\/em>";
|
||||
}
|
||||
if( fields.volume ) {
|
||||
info += ", Vol." + fields.volume;
|
||||
}
|
||||
if( fields.issn ) {
|
||||
info += ", ISBN: " + fields.issn;
|
||||
}
|
||||
return info + ".";
|
||||
},
|
||||
_asSentence: function(str) {
|
||||
return (str && str.trim()) ? str + ". " : "";
|
||||
}
|
||||
});
|
||||
|
||||
var CitationListView = Backbone.View.extend({
|
||||
el: '#citations',
|
||||
/**
|
||||
* Set up view.
|
||||
*/
|
||||
initialize: function() {
|
||||
this.listenTo( this.collection, 'add', this.renderCitation );
|
||||
},
|
||||
|
||||
events: {
|
||||
'click .citations-to-bibtex': 'showBibtex',
|
||||
'click .citations-to-formatted': 'showFormatted'
|
||||
},
|
||||
|
||||
renderCitation: function( citation ) {
|
||||
var citationView = new CitationView( { model: citation } );
|
||||
this.$(".citations-formatted").append( citationView.render().el );
|
||||
var rawTextarea = this.$(".citations-bibtex-text");
|
||||
rawTextarea.val( rawTextarea.val() + "\n\r" + citation.attributes.content );
|
||||
},
|
||||
|
||||
render: function() {
|
||||
this.$el.html(this.citationsElement());
|
||||
this.collection.each(function( item ){
|
||||
this.renderCitation( item );
|
||||
}, this);
|
||||
this.showFormatted();
|
||||
},
|
||||
|
||||
showBibtex: function() {
|
||||
this.$(".citations-to-formatted").show();
|
||||
this.$(".citations-to-bibtex").hide();
|
||||
this.$(".citations-bibtex").show();
|
||||
this.$(".citations-formatted").hide();
|
||||
this.$(".citations-bibtex-text").select();
|
||||
},
|
||||
|
||||
showFormatted: function() {
|
||||
this.$(".citations-to-formatted").hide();
|
||||
this.$(".citations-to-bibtex").show();
|
||||
this.$(".citations-bibtex").hide();
|
||||
this.$(".citations-formatted").show();
|
||||
},
|
||||
|
||||
partialWarningElement: function() {
|
||||
if( this.collection.partial ) {
|
||||
return [
|
||||
'<div style="padding:5px 10px">',
|
||||
'<b>Warning: This is a experimental feature.</b> Most Galaxy tools will not annotate',
|
||||
' citations explicitly at this time. When writing up your analysis, please manually',
|
||||
' review your histories and find all references',
|
||||
' that should be cited in order to completely describe your work. Also, please remember to',
|
||||
' <a href="https://galaxyproject.org/citing-galaxy">cite Galaxy</a>.',
|
||||
'</div>',
|
||||
].join('');
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
|
||||
citationsElement: function() {
|
||||
return [
|
||||
'<div class="toolForm">',
|
||||
'<div class="toolFormTitle">',
|
||||
_l("Citations"),
|
||||
' <button type="button" class="btn btn-xs citations-to-bibtex" title="Show all in BibTeX format."><i class="fa fa-pencil-square-o"></i> Show BibTeX</button>',
|
||||
' <button type="button" class="btn btn-xs citations-to-formatted" title="Return to formatted citation list."><i class="fa fa-times"></i> Hide BibTeX</button>',
|
||||
'</div>',
|
||||
'<div class="toolFormBody" style="padding:5px 10px">',
|
||||
this.partialWarningElement(),
|
||||
'<span class="citations-formatted" style="word-wrap: break-word;"></span>',
|
||||
'</div>',
|
||||
'<div class="citations-bibtex toolFormBody" style="padding:5px 10px">',
|
||||
'<textarea style="width: 100%; height: 500px;" class="citations-bibtex-text"></textarea>',
|
||||
'</div>',
|
||||
'</div>'
|
||||
].join( '' );
|
||||
}
|
||||
});
|
||||
|
||||
//==============================================================================
|
||||
return {
|
||||
CitationView : CitationView,
|
||||
CitationListView : CitationListView
|
||||
};
|
||||
|
||||
});
|
||||
@@ -1,127 +1,130 @@
|
||||
define([
|
||||
], function( ){
|
||||
|
||||
/* For presentation-related functionality shared across collection creators.
|
||||
Particularily overlapping functionality related to name processing and help.
|
||||
*/
|
||||
var CollectionCreatorMixin = {
|
||||
|
||||
/** add (or clear if clear is truthy) a validation warning to the DOM element described in what */
|
||||
_validationWarning : function( what, clear ){
|
||||
var VALIDATION_CLASS = 'validation-warning';
|
||||
if( what === 'name' ){
|
||||
what = this.$( '.collection-name' ).add( this.$( '.collection-name-prompt' ) );
|
||||
this.$( '.collection-name' ).focus().select();
|
||||
_validationWarning: function(what, clear) {
|
||||
var VALIDATION_CLASS = "validation-warning";
|
||||
if (what === "name") {
|
||||
what = this.$(".collection-name").add(this.$(".collection-name-prompt"));
|
||||
this.$(".collection-name")
|
||||
.focus()
|
||||
.select();
|
||||
}
|
||||
if( clear ){
|
||||
what = what || this.$( '.' + VALIDATION_CLASS );
|
||||
what.removeClass( VALIDATION_CLASS );
|
||||
if (clear) {
|
||||
what = what || this.$(`.${VALIDATION_CLASS}`);
|
||||
what.removeClass(VALIDATION_CLASS);
|
||||
} else {
|
||||
what.addClass( VALIDATION_CLASS );
|
||||
what.addClass(VALIDATION_CLASS);
|
||||
}
|
||||
},
|
||||
|
||||
_changeHideOriginals: function( ev ) {
|
||||
this.hideOriginals = this.$( '.hide-originals' ).prop("checked");
|
||||
_changeHideOriginals: function(ev) {
|
||||
this.hideOriginals = this.$(".hide-originals").prop("checked");
|
||||
},
|
||||
|
||||
// ........................................................................ footer
|
||||
/** handle a collection name change */
|
||||
_changeName : function( ev ){
|
||||
this._validationWarning( 'name', !!this._getName() );
|
||||
_changeName: function(ev) {
|
||||
this._validationWarning("name", !!this._getName());
|
||||
},
|
||||
|
||||
/** check for enter key press when in the collection name and submit */
|
||||
_nameCheckForEnter : function( ev ){
|
||||
if( ev.keyCode === 13 && !this.blocking ){
|
||||
_nameCheckForEnter: function(ev) {
|
||||
if (ev.keyCode === 13 && !this.blocking) {
|
||||
this._clickCreate();
|
||||
}
|
||||
},
|
||||
|
||||
/** get the current collection name */
|
||||
_getName : function(){
|
||||
return _.escape( this.$( '.collection-name' ).val() );
|
||||
_getName: function() {
|
||||
return _.escape(this.$(".collection-name").val());
|
||||
},
|
||||
|
||||
// ........................................................................ header
|
||||
/** expand help */
|
||||
_clickMoreHelp : function( ev ){
|
||||
_clickMoreHelp: function(ev) {
|
||||
ev.stopPropagation();
|
||||
this.$( '.main-help' ).addClass( 'expanded' );
|
||||
this.$( '.more-help' ).hide();
|
||||
this.$(".main-help").addClass("expanded");
|
||||
this.$(".more-help").hide();
|
||||
},
|
||||
/** collapse help */
|
||||
_clickLessHelp : function( ev ){
|
||||
_clickLessHelp: function(ev) {
|
||||
ev.stopPropagation();
|
||||
this.$( '.main-help' ).removeClass( 'expanded' );
|
||||
this.$( '.more-help' ).show();
|
||||
this.$(".main-help").removeClass("expanded");
|
||||
this.$(".more-help").show();
|
||||
},
|
||||
/** toggle help */
|
||||
_toggleHelp : function( ev ){
|
||||
_toggleHelp: function(ev) {
|
||||
ev.stopPropagation();
|
||||
this.$( '.main-help' ).toggleClass( 'expanded' );
|
||||
this.$( '.more-help' ).toggle();
|
||||
this.$(".main-help").toggleClass("expanded");
|
||||
this.$(".more-help").toggle();
|
||||
},
|
||||
|
||||
/** show an alert on the top of the interface containing message (alertClass is bootstrap's alert-*) */
|
||||
_showAlert : function( message, alertClass ){
|
||||
alertClass = alertClass || 'alert-danger';
|
||||
this.$( '.main-help' ).hide();
|
||||
this.$( '.header .alert' )
|
||||
.attr( 'class', 'alert alert-dismissable' ).addClass( alertClass ).show()
|
||||
.find( '.alert-message' ).html( message );
|
||||
_showAlert: function(message, alertClass) {
|
||||
alertClass = alertClass || "alert-danger";
|
||||
this.$(".main-help").hide();
|
||||
this.$(".header .alert")
|
||||
.attr("class", "alert alert-dismissable")
|
||||
.addClass(alertClass)
|
||||
.show()
|
||||
.find(".alert-message")
|
||||
.html(message);
|
||||
},
|
||||
/** hide the alerts at the top */
|
||||
_hideAlert : function( message ){
|
||||
this.$( '.main-help' ).show();
|
||||
this.$( '.header .alert' ).hide();
|
||||
_hideAlert: function(message) {
|
||||
this.$(".main-help").show();
|
||||
this.$(".header .alert").hide();
|
||||
},
|
||||
|
||||
_cancelCreate: function( ev ){
|
||||
if( typeof this.oncancel === 'function' ){
|
||||
this.oncancel.call( this );
|
||||
_cancelCreate: function(ev) {
|
||||
if (typeof this.oncancel === "function") {
|
||||
this.oncancel.call(this);
|
||||
}
|
||||
},
|
||||
|
||||
/** attempt to create the current collection */
|
||||
_clickCreate : function( ev ){
|
||||
_clickCreate: function(ev) {
|
||||
var name = this._getName();
|
||||
if( !name ){
|
||||
this._validationWarning( 'name' );
|
||||
} else if( !this.blocking ){
|
||||
this.createList( name );
|
||||
if (!name) {
|
||||
this._validationWarning("name");
|
||||
} else if (!this.blocking) {
|
||||
this.createList(name);
|
||||
}
|
||||
},
|
||||
|
||||
_setUpCommonSettings : function( attributes ) {
|
||||
_setUpCommonSettings: function(attributes) {
|
||||
this.hideOriginals = attributes.defaultHideSourceItems || false;
|
||||
},
|
||||
|
||||
/** render the footer, completion controls, and cancel controls */
|
||||
_renderFooter : function( speed, callback ){
|
||||
var instance = this;
|
||||
var $footer = this.$( '.footer' ).empty().html( this.templates.footer() );
|
||||
_.each( this.footerSettings, function( property, selector ) {
|
||||
this.$( selector ).prop( 'checked', instance[ property ] );
|
||||
_renderFooter: function(speed, callback) {
|
||||
var self = this;
|
||||
var $footer = this.$(".footer")
|
||||
.empty()
|
||||
.html(this.templates.footer());
|
||||
_.each(this.footerSettings, (property, selector) => {
|
||||
self.$(selector).prop("checked", self[property]);
|
||||
});
|
||||
if( typeof this.oncancel === 'function' ){
|
||||
this.$( '.cancel-create.btn' ).show();
|
||||
if (typeof this.oncancel === "function") {
|
||||
this.$(".cancel-create.btn").show();
|
||||
}
|
||||
return $footer;
|
||||
},
|
||||
|
||||
_creatorTemplates: {
|
||||
main : _.template([
|
||||
'<div class="header flex-row no-flex"></div>',
|
||||
'<div class="middle flex-row flex-row-container"></div>',
|
||||
'<div class="footer flex-row no-flex"></div>'
|
||||
].join('')),
|
||||
main: _.template(
|
||||
[
|
||||
'<div class="header flex-row no-flex"></div>',
|
||||
'<div class="middle flex-row flex-row-container"></div>',
|
||||
'<div class="footer flex-row no-flex"></div>'
|
||||
].join("")
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
return {
|
||||
CollectionCreatorMixin: CollectionCreatorMixin
|
||||
};
|
||||
});
|
||||
export default {
|
||||
CollectionCreatorMixin: CollectionCreatorMixin
|
||||
};
|
||||
|
||||
@@ -1,53 +1,49 @@
|
||||
define([
|
||||
"mvc/collection/collection-li",
|
||||
"mvc/dataset/dataset-li-edit",
|
||||
"mvc/base-mvc",
|
||||
"utils/localization"
|
||||
], function( DC_LI, DATASET_LI_EDIT, BASE_MVC, _l ){
|
||||
import DC_LI from "mvc/collection/collection-li";
|
||||
import DATASET_LI_EDIT from "mvc/dataset/dataset-li-edit";
|
||||
import BASE_MVC from "mvc/base-mvc";
|
||||
import _l from "utils/localization";
|
||||
|
||||
'use strict';
|
||||
//==============================================================================
|
||||
var DCListItemView = DC_LI.DCListItemView;
|
||||
/** @class Edit view for DatasetCollection.
|
||||
*/
|
||||
var DCListItemEdit = DCListItemView.extend(
|
||||
/** @lends DCListItemEdit.prototype */{
|
||||
/** @lends DCListItemEdit.prototype */ {
|
||||
/** override to add linkTarget */
|
||||
initialize: function(attributes) {
|
||||
DCListItemView.prototype.initialize.call(this, attributes);
|
||||
},
|
||||
|
||||
/** override to add linkTarget */
|
||||
initialize : function( attributes ){
|
||||
DCListItemView.prototype.initialize.call( this, attributes );
|
||||
},
|
||||
|
||||
// ......................................................................... misc
|
||||
/** String representation */
|
||||
toString : function(){
|
||||
var modelString = ( this.model )?( this.model + '' ):( '(no model)' );
|
||||
return 'DCListItemEdit(' + modelString + ')';
|
||||
// ......................................................................... misc
|
||||
/** String representation */
|
||||
toString: function() {
|
||||
var modelString = this.model ? `${this.model}` : "(no model)";
|
||||
return `DCListItemEdit(${modelString})`;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
);
|
||||
|
||||
//==============================================================================
|
||||
var DCEListItemView = DC_LI.DCEListItemView;
|
||||
/** @class Read only view for DatasetCollectionElement.
|
||||
*/
|
||||
var DCEListItemEdit = DCEListItemView.extend(
|
||||
/** @lends DCEListItemEdit.prototype */{
|
||||
//TODO: this might be expendable - compacted with HDAListItemView
|
||||
/** @lends DCEListItemEdit.prototype */ {
|
||||
//TODO: this might be expendable - compacted with HDAListItemView
|
||||
|
||||
/** set up */
|
||||
initialize : function( attributes ){
|
||||
DCEListItemView.prototype.initialize.call( this, attributes );
|
||||
},
|
||||
/** set up */
|
||||
initialize: function(attributes) {
|
||||
DCEListItemView.prototype.initialize.call(this, attributes);
|
||||
},
|
||||
|
||||
// ......................................................................... misc
|
||||
/** String representation */
|
||||
toString : function(){
|
||||
var modelString = ( this.model )?( this.model + '' ):( '(no model)' );
|
||||
return 'DCEListItemEdit(' + modelString + ')';
|
||||
// ......................................................................... misc
|
||||
/** String representation */
|
||||
toString: function() {
|
||||
var modelString = this.model ? `${this.model}` : "(no model)";
|
||||
return `DCEListItemEdit(${modelString})`;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
);
|
||||
|
||||
//==============================================================================
|
||||
// NOTE: this does not inherit from DatasetDCEListItemView as you would expect
|
||||
@@ -56,69 +52,64 @@ var DCEListItemEdit = DCEListItemView.extend(
|
||||
* (a dataset contained in a dataset collection).
|
||||
*/
|
||||
var DatasetDCEListItemEdit = DATASET_LI_EDIT.DatasetListItemEdit.extend(
|
||||
/** @lends DatasetDCEListItemEdit.prototype */{
|
||||
/** @lends DatasetDCEListItemEdit.prototype */ {
|
||||
/** set up */
|
||||
initialize: function(attributes) {
|
||||
DATASET_LI_EDIT.DatasetListItemEdit.prototype.initialize.call(this, attributes);
|
||||
},
|
||||
|
||||
/** set up */
|
||||
initialize : function( attributes ){
|
||||
DATASET_LI_EDIT.DatasetListItemEdit.prototype.initialize.call( this, attributes );
|
||||
},
|
||||
// NOTE: this does not inherit from DatasetDCEListItemView - so we duplicate this here
|
||||
//TODO: fix
|
||||
/** In this override, only get details if in the ready state.
|
||||
* Note: fetch with no 'change' event triggering to prevent automatic rendering.
|
||||
*/
|
||||
_fetchModelDetails: function() {
|
||||
var view = this;
|
||||
if (view.model.inReadyState() && !view.model.hasDetails()) {
|
||||
return view.model.fetch({ silent: true });
|
||||
}
|
||||
return jQuery.when();
|
||||
},
|
||||
|
||||
// NOTE: this does not inherit from DatasetDCEListItemView - so we duplicate this here
|
||||
//TODO: fix
|
||||
/** In this override, only get details if in the ready state.
|
||||
* Note: fetch with no 'change' event triggering to prevent automatic rendering.
|
||||
*/
|
||||
_fetchModelDetails : function(){
|
||||
var view = this;
|
||||
if( view.model.inReadyState() && !view.model.hasDetails() ){
|
||||
return view.model.fetch({ silent: true });
|
||||
/** Override to remove delete button */
|
||||
_renderDeleteButton: function() {
|
||||
return null;
|
||||
},
|
||||
|
||||
// ......................................................................... misc
|
||||
/** String representation */
|
||||
toString: function() {
|
||||
var modelString = this.model ? `${this.model}` : "(no model)";
|
||||
return `DatasetDCEListItemEdit(${modelString})`;
|
||||
}
|
||||
return jQuery.when();
|
||||
},
|
||||
|
||||
/** Override to remove delete button */
|
||||
_renderDeleteButton : function(){
|
||||
return null;
|
||||
},
|
||||
|
||||
// ......................................................................... misc
|
||||
/** String representation */
|
||||
toString : function(){
|
||||
var modelString = ( this.model )?( this.model + '' ):( '(no model)' );
|
||||
return 'DatasetDCEListItemEdit(' + modelString + ')';
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
// ............................................................................ TEMPLATES
|
||||
/** underscore templates */
|
||||
DatasetDCEListItemEdit.prototype.templates = (function(){
|
||||
|
||||
return _.extend( {}, DATASET_LI_EDIT.DatasetListItemEdit.prototype.templates, {
|
||||
titleBar : DC_LI.DatasetDCEListItemView.prototype.templates.titleBar
|
||||
});
|
||||
}());
|
||||
|
||||
DatasetDCEListItemEdit.prototype.templates = (() =>
|
||||
_.extend({}, DATASET_LI_EDIT.DatasetListItemEdit.prototype.templates, {
|
||||
titleBar: DC_LI.DatasetDCEListItemView.prototype.templates.titleBar
|
||||
}))();
|
||||
|
||||
//==============================================================================
|
||||
/** @class Read only view for a DatasetCollectionElement that is also a DatasetCollection
|
||||
* (a nested DC).
|
||||
*/
|
||||
var NestedDCDCEListItemEdit = DC_LI.NestedDCDCEListItemView.extend(
|
||||
/** @lends NestedDCDCEListItemEdit.prototype */{
|
||||
|
||||
/** String representation */
|
||||
toString : function(){
|
||||
var modelString = ( this.model )?( this.model + '' ):( '(no model)' );
|
||||
return 'NestedDCDCEListItemEdit(' + modelString + ')';
|
||||
/** @lends NestedDCDCEListItemEdit.prototype */ {
|
||||
/** String representation */
|
||||
toString: function() {
|
||||
var modelString = this.model ? `${this.model}` : "(no model)";
|
||||
return `NestedDCDCEListItemEdit(${modelString})`;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
);
|
||||
|
||||
//==============================================================================
|
||||
return {
|
||||
DCListItemEdit : DCListItemEdit,
|
||||
DCEListItemEdit : DCEListItemEdit,
|
||||
DatasetDCEListItemEdit : DatasetDCEListItemEdit,
|
||||
NestedDCDCEListItemEdit : NestedDCDCEListItemEdit
|
||||
};
|
||||
});
|
||||
export default {
|
||||
DCListItemEdit: DCListItemEdit,
|
||||
DCEListItemEdit: DCEListItemEdit,
|
||||
DatasetDCEListItemEdit: DatasetDCEListItemEdit,
|
||||
NestedDCDCEListItemEdit: NestedDCDCEListItemEdit
|
||||
};
|
||||
|
||||
@@ -1,271 +1,278 @@
|
||||
define([
|
||||
"mvc/list/list-item",
|
||||
"mvc/dataset/dataset-li",
|
||||
"mvc/base-mvc",
|
||||
"utils/localization"
|
||||
], function( LIST_ITEM, DATASET_LI, BASE_MVC, _l ){
|
||||
import LIST_ITEM from "mvc/list/list-item";
|
||||
import DATASET_LI from "mvc/dataset/dataset-li";
|
||||
import BASE_MVC from "mvc/base-mvc";
|
||||
import _l from "utils/localization";
|
||||
|
||||
'use strict';
|
||||
//==============================================================================
|
||||
var FoldoutListItemView = LIST_ITEM.FoldoutListItemView,
|
||||
ListItemView = LIST_ITEM.ListItemView;
|
||||
var FoldoutListItemView = LIST_ITEM.FoldoutListItemView;
|
||||
|
||||
var ListItemView = LIST_ITEM.ListItemView;
|
||||
/** @class Read only view for DatasetCollection.
|
||||
*/
|
||||
var DCListItemView = FoldoutListItemView.extend(
|
||||
/** @lends DCListItemView.prototype */{
|
||||
/** @lends DCListItemView.prototype */ {
|
||||
className: `${FoldoutListItemView.prototype.className} dataset-collection`,
|
||||
id: function() {
|
||||
return ["dataset_collection", this.model.get("id")].join("-");
|
||||
},
|
||||
|
||||
className : FoldoutListItemView.prototype.className + " dataset-collection",
|
||||
id : function(){
|
||||
return [ 'dataset_collection', this.model.get( 'id' ) ].join( '-' );
|
||||
},
|
||||
/** override to add linkTarget */
|
||||
initialize: function(attributes) {
|
||||
this.linkTarget = attributes.linkTarget || "_blank";
|
||||
this.hasUser = attributes.hasUser;
|
||||
FoldoutListItemView.prototype.initialize.call(this, attributes);
|
||||
},
|
||||
|
||||
/** override to add linkTarget */
|
||||
initialize : function( attributes ){
|
||||
this.linkTarget = attributes.linkTarget || '_blank';
|
||||
this.hasUser = attributes.hasUser;
|
||||
FoldoutListItemView.prototype.initialize.call( this, attributes );
|
||||
},
|
||||
/** event listeners */
|
||||
_setUpListeners: function() {
|
||||
FoldoutListItemView.prototype._setUpListeners.call(this);
|
||||
this.listenTo(this.model, "change", function(model, options) {
|
||||
// if the model has changed deletion status render it entirely
|
||||
if (_.has(model.changed, "deleted")) {
|
||||
this.render();
|
||||
|
||||
/** event listeners */
|
||||
_setUpListeners : function(){
|
||||
FoldoutListItemView.prototype._setUpListeners.call( this );
|
||||
this.listenTo( this.model, 'change', function( model, options ){
|
||||
// if the model has changed deletion status render it entirely
|
||||
if( _.has( model.changed, 'deleted' ) ){
|
||||
this.render();
|
||||
// if the model has been decorated after the fact with the element count,
|
||||
// render the subtitle where the count is displayed
|
||||
} else if (_.has(model.changed, "element_count")) {
|
||||
this.$("> .title-bar .subtitle").replaceWith(this._renderSubtitle());
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// if the model has been decorated after the fact with the element count,
|
||||
// render the subtitle where the count is displayed
|
||||
} else if( _.has( model.changed, 'element_count' ) ){
|
||||
this.$( '> .title-bar .subtitle' ).replaceWith( this._renderSubtitle() );
|
||||
}
|
||||
});
|
||||
},
|
||||
// ......................................................................... rendering
|
||||
/** render a subtitle to show the user what sort of collection this is */
|
||||
_renderSubtitle: function() {
|
||||
return $(this.templates.subtitle(this.model.toJSON(), this));
|
||||
},
|
||||
|
||||
// ......................................................................... rendering
|
||||
/** render a subtitle to show the user what sort of collection this is */
|
||||
_renderSubtitle : function(){
|
||||
return $( this.templates.subtitle( this.model.toJSON(), this ) );
|
||||
},
|
||||
// ......................................................................... foldout
|
||||
/** override to add linktarget to sub-panel */
|
||||
_getFoldoutPanelOptions: function() {
|
||||
var options = FoldoutListItemView.prototype._getFoldoutPanelOptions.call(this);
|
||||
return _.extend(options, {
|
||||
linkTarget: this.linkTarget,
|
||||
hasUser: this.hasUser
|
||||
});
|
||||
},
|
||||
|
||||
// ......................................................................... foldout
|
||||
/** override to add linktarget to sub-panel */
|
||||
_getFoldoutPanelOptions : function(){
|
||||
var options = FoldoutListItemView.prototype._getFoldoutPanelOptions.call( this );
|
||||
return _.extend( options, {
|
||||
linkTarget : this.linkTarget,
|
||||
hasUser : this.hasUser
|
||||
});
|
||||
},
|
||||
/** override to not catch sub-panel selectors */
|
||||
$selector: function() {
|
||||
return this.$("> .selector");
|
||||
},
|
||||
|
||||
/** override to not catch sub-panel selectors */
|
||||
$selector : function(){
|
||||
return this.$( '> .selector' );
|
||||
},
|
||||
|
||||
// ......................................................................... misc
|
||||
/** String representation */
|
||||
toString : function(){
|
||||
var modelString = ( this.model )?( this.model + '' ):( '(no model)' );
|
||||
return 'DCListItemView(' + modelString + ')';
|
||||
// ......................................................................... misc
|
||||
/** String representation */
|
||||
toString: function() {
|
||||
var modelString = this.model ? `${this.model}` : "(no model)";
|
||||
return `DCListItemView(${modelString})`;
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
// ............................................................................ TEMPLATES
|
||||
/** underscore templates */
|
||||
DCListItemView.prototype.templates = (function(){
|
||||
|
||||
var warnings = _.extend( {}, FoldoutListItemView.prototype.templates.warnings, {
|
||||
error : BASE_MVC.wrapTemplate([
|
||||
DCListItemView.prototype.templates = (() => {
|
||||
var warnings = _.extend({}, FoldoutListItemView.prototype.templates.warnings, {
|
||||
error: BASE_MVC.wrapTemplate([
|
||||
// error during index fetch - show error on dataset
|
||||
'<% if( model.error ){ %>',
|
||||
'<div class="errormessagesmall">',
|
||||
_l( 'There was an error getting the data for this collection' ), ': <%- model.error %>',
|
||||
'</div>',
|
||||
'<% } %>'
|
||||
"<% if( model.error ){ %>",
|
||||
'<div class="errormessagesmall">',
|
||||
_l("There was an error getting the data for this collection"),
|
||||
": <%- model.error %>",
|
||||
"</div>",
|
||||
"<% } %>"
|
||||
]),
|
||||
purged : BASE_MVC.wrapTemplate([
|
||||
'<% if( model.purged ){ %>',
|
||||
'<div class="purged-msg warningmessagesmall">',
|
||||
_l( 'This collection has been deleted and removed from disk' ),
|
||||
'</div>',
|
||||
'<% } %>'
|
||||
purged: BASE_MVC.wrapTemplate([
|
||||
"<% if( model.purged ){ %>",
|
||||
'<div class="purged-msg warningmessagesmall">',
|
||||
_l("This collection has been deleted and removed from disk"),
|
||||
"</div>",
|
||||
"<% } %>"
|
||||
]),
|
||||
deleted : BASE_MVC.wrapTemplate([
|
||||
deleted: BASE_MVC.wrapTemplate([
|
||||
// deleted not purged
|
||||
'<% if( model.deleted && !model.purged ){ %>',
|
||||
'<div class="deleted-msg warningmessagesmall">',
|
||||
_l( 'This collection has been deleted' ),
|
||||
'</div>',
|
||||
'<% } %>'
|
||||
"<% if( model.deleted && !model.purged ){ %>",
|
||||
'<div class="deleted-msg warningmessagesmall">',
|
||||
_l("This collection has been deleted"),
|
||||
"</div>",
|
||||
"<% } %>"
|
||||
])
|
||||
});
|
||||
|
||||
// use element identifier
|
||||
var titleBarTemplate = BASE_MVC.wrapTemplate([
|
||||
'<div class="title-bar clear" tabindex="0">',
|
||||
var titleBarTemplate = BASE_MVC.wrapTemplate(
|
||||
[
|
||||
'<div class="title-bar clear" tabindex="0">',
|
||||
'<div class="title">',
|
||||
'<span class="name"><%- collection.element_identifier || collection.name %></span>',
|
||||
'</div>',
|
||||
'<span class="name"><%- collection.element_identifier || collection.name %></span>',
|
||||
"</div>",
|
||||
'<div class="subtitle"></div>',
|
||||
'</div>'
|
||||
], 'collection' );
|
||||
"</div>"
|
||||
],
|
||||
"collection"
|
||||
);
|
||||
|
||||
// use element identifier
|
||||
var subtitleTemplate = BASE_MVC.wrapTemplate([
|
||||
'<div class="subtitle">',
|
||||
var subtitleTemplate = BASE_MVC.wrapTemplate(
|
||||
[
|
||||
'<div class="subtitle">',
|
||||
'<% var countText = collection.element_count? ( collection.element_count + " " ) : ""; %>',
|
||||
'<% if( collection.collection_type === "list" ){ %>',
|
||||
_l( 'a list of <%- countText %>datasets' ),
|
||||
_l("a list of <%- countText %>datasets"),
|
||||
'<% } else if( collection.collection_type === "paired" ){ %>',
|
||||
_l( 'a pair of datasets' ),
|
||||
_l("a pair of datasets"),
|
||||
'<% } else if( collection.collection_type === "list:paired" ){ %>',
|
||||
_l( 'a list of <%- countText %>dataset pairs' ),
|
||||
_l("a list of <%- countText %>dataset pairs"),
|
||||
'<% } else if( collection.collection_type === "list:list" ){ %>',
|
||||
_l( 'a list of <%- countText %>dataset lists' ),
|
||||
'<% } %>',
|
||||
'</div>'
|
||||
], 'collection' );
|
||||
_l("a list of <%- countText %>dataset lists"),
|
||||
"<% } %>",
|
||||
"</div>"
|
||||
],
|
||||
"collection"
|
||||
);
|
||||
|
||||
return _.extend( {}, FoldoutListItemView.prototype.templates, {
|
||||
warnings : warnings,
|
||||
titleBar : titleBarTemplate,
|
||||
subtitle : subtitleTemplate
|
||||
return _.extend({}, FoldoutListItemView.prototype.templates, {
|
||||
warnings: warnings,
|
||||
titleBar: titleBarTemplate,
|
||||
subtitle: subtitleTemplate
|
||||
});
|
||||
}());
|
||||
|
||||
})();
|
||||
|
||||
//==============================================================================
|
||||
/** @class Read only view for DatasetCollectionElement.
|
||||
*/
|
||||
var DCEListItemView = ListItemView.extend(
|
||||
/** @lends DCEListItemView.prototype */{
|
||||
/** @lends DCEListItemView.prototype */ {
|
||||
/** add the DCE class to the list item */
|
||||
className: `${ListItemView.prototype.className} dataset-collection-element`,
|
||||
|
||||
/** add the DCE class to the list item */
|
||||
className : ListItemView.prototype.className + " dataset-collection-element",
|
||||
/** set up */
|
||||
initialize: function(attributes) {
|
||||
if (attributes.logger) {
|
||||
this.logger = this.model.logger = attributes.logger;
|
||||
}
|
||||
this.log("DCEListItemView.initialize:", attributes);
|
||||
ListItemView.prototype.initialize.call(this, attributes);
|
||||
},
|
||||
|
||||
/** set up */
|
||||
initialize : function( attributes ){
|
||||
if( attributes.logger ){ this.logger = this.model.logger = attributes.logger; }
|
||||
this.log( 'DCEListItemView.initialize:', attributes );
|
||||
ListItemView.prototype.initialize.call( this, attributes );
|
||||
},
|
||||
|
||||
// ......................................................................... misc
|
||||
/** String representation */
|
||||
toString : function(){
|
||||
var modelString = ( this.model )?( this.model + '' ):( '(no model)' );
|
||||
return 'DCEListItemView(' + modelString + ')';
|
||||
// ......................................................................... misc
|
||||
/** String representation */
|
||||
toString: function() {
|
||||
var modelString = this.model ? `${this.model}` : "(no model)";
|
||||
return `DCEListItemView(${modelString})`;
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
// ............................................................................ TEMPLATES
|
||||
/** underscore templates */
|
||||
DCEListItemView.prototype.templates = (function(){
|
||||
|
||||
DCEListItemView.prototype.templates = (() => {
|
||||
// use the element identifier here - since that will persist and the user will need it
|
||||
var titleBarTemplate = BASE_MVC.wrapTemplate([
|
||||
'<div class="title-bar clear" tabindex="0">',
|
||||
var titleBarTemplate = BASE_MVC.wrapTemplate(
|
||||
[
|
||||
'<div class="title-bar clear" tabindex="0">',
|
||||
'<div class="title">',
|
||||
'<span class="name"><%- element.element_identifier %></span>',
|
||||
'</div>',
|
||||
'<span class="name"><%- element.element_identifier %></span>',
|
||||
"</div>",
|
||||
'<div class="subtitle"></div>',
|
||||
'</div>'
|
||||
], 'element' );
|
||||
"</div>"
|
||||
],
|
||||
"element"
|
||||
);
|
||||
|
||||
return _.extend( {}, ListItemView.prototype.templates, {
|
||||
titleBar : titleBarTemplate
|
||||
return _.extend({}, ListItemView.prototype.templates, {
|
||||
titleBar: titleBarTemplate
|
||||
});
|
||||
}());
|
||||
|
||||
})();
|
||||
|
||||
//==============================================================================
|
||||
/** @class Read only view for a DatasetCollectionElement that is also an DatasetAssociation
|
||||
* (a dataset contained in a dataset collection).
|
||||
*/
|
||||
var DatasetDCEListItemView = DATASET_LI.DatasetListItemView.extend(
|
||||
/** @lends DatasetDCEListItemView.prototype */{
|
||||
/** @lends DatasetDCEListItemView.prototype */ {
|
||||
className: `${DATASET_LI.DatasetListItemView.prototype.className} dataset-collection-element`,
|
||||
|
||||
className : DATASET_LI.DatasetListItemView.prototype.className + " dataset-collection-element",
|
||||
/** set up */
|
||||
initialize: function(attributes) {
|
||||
if (attributes.logger) {
|
||||
this.logger = this.model.logger = attributes.logger;
|
||||
}
|
||||
this.log("DatasetDCEListItemView.initialize:", attributes);
|
||||
DATASET_LI.DatasetListItemView.prototype.initialize.call(this, attributes);
|
||||
},
|
||||
|
||||
/** set up */
|
||||
initialize : function( attributes ){
|
||||
if( attributes.logger ){ this.logger = this.model.logger = attributes.logger; }
|
||||
this.log( 'DatasetDCEListItemView.initialize:', attributes );
|
||||
DATASET_LI.DatasetListItemView.prototype.initialize.call( this, attributes );
|
||||
},
|
||||
/** In this override, only get details if in the ready state.
|
||||
* Note: fetch with no 'change' event triggering to prevent automatic rendering.
|
||||
*/
|
||||
_fetchModelDetails: function() {
|
||||
var view = this;
|
||||
if (view.model.inReadyState() && !view.model.hasDetails()) {
|
||||
return view.model.fetch({ silent: true });
|
||||
}
|
||||
return jQuery.when();
|
||||
},
|
||||
|
||||
/** In this override, only get details if in the ready state.
|
||||
* Note: fetch with no 'change' event triggering to prevent automatic rendering.
|
||||
*/
|
||||
_fetchModelDetails : function(){
|
||||
var view = this;
|
||||
if( view.model.inReadyState() && !view.model.hasDetails() ){
|
||||
return view.model.fetch({ silent: true });
|
||||
// ......................................................................... misc
|
||||
/** String representation */
|
||||
toString: function() {
|
||||
var modelString = this.model ? `${this.model}` : "(no model)";
|
||||
return `DatasetDCEListItemView(${modelString})`;
|
||||
}
|
||||
return jQuery.when();
|
||||
},
|
||||
|
||||
// ......................................................................... misc
|
||||
/** String representation */
|
||||
toString : function(){
|
||||
var modelString = ( this.model )?( this.model + '' ):( '(no model)' );
|
||||
return 'DatasetDCEListItemView(' + modelString + ')';
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
// ............................................................................ TEMPLATES
|
||||
/** underscore templates */
|
||||
DatasetDCEListItemView.prototype.templates = (function(){
|
||||
|
||||
DatasetDCEListItemView.prototype.templates = (() => {
|
||||
// use the element identifier here and not the dataset name
|
||||
//TODO:?? can we steal the DCE titlebar?
|
||||
var titleBarTemplate = BASE_MVC.wrapTemplate([
|
||||
'<div class="title-bar clear" tabindex="0">',
|
||||
var titleBarTemplate = BASE_MVC.wrapTemplate(
|
||||
[
|
||||
'<div class="title-bar clear" tabindex="0">',
|
||||
'<span class="state-icon"></span>',
|
||||
'<div class="title">',
|
||||
'<span class="name"><%- element.element_identifier %></span>',
|
||||
'</div>',
|
||||
'</div>'
|
||||
], 'element' );
|
||||
'<span class="name"><%- element.element_identifier %></span>',
|
||||
"</div>",
|
||||
"</div>"
|
||||
],
|
||||
"element"
|
||||
);
|
||||
|
||||
return _.extend( {}, DATASET_LI.DatasetListItemView.prototype.templates, {
|
||||
titleBar : titleBarTemplate
|
||||
return _.extend({}, DATASET_LI.DatasetListItemView.prototype.templates, {
|
||||
titleBar: titleBarTemplate
|
||||
});
|
||||
}());
|
||||
|
||||
})();
|
||||
|
||||
//==============================================================================
|
||||
/** @class Read only view for a DatasetCollectionElement that is also a DatasetCollection
|
||||
* (a nested DC).
|
||||
*/
|
||||
var NestedDCDCEListItemView = DCListItemView.extend(
|
||||
/** @lends NestedDCDCEListItemView.prototype */{
|
||||
/** @lends NestedDCDCEListItemView.prototype */ {
|
||||
className: `${DCListItemView.prototype.className} dataset-collection-element`,
|
||||
|
||||
className : DCListItemView.prototype.className + " dataset-collection-element",
|
||||
/** In this override, add the state as a class for use with state-based CSS */
|
||||
_swapNewRender: function($newRender) {
|
||||
DCListItemView.prototype._swapNewRender.call(this, $newRender);
|
||||
var state = this.model.get("state") || "ok";
|
||||
this.$el.addClass(`state-${state}`);
|
||||
return this.$el;
|
||||
},
|
||||
|
||||
/** In this override, add the state as a class for use with state-based CSS */
|
||||
_swapNewRender : function( $newRender ){
|
||||
DCListItemView.prototype._swapNewRender.call( this, $newRender );
|
||||
var state = this.model.get( 'state' ) || 'ok';
|
||||
this.$el.addClass( 'state-' + state );
|
||||
return this.$el;
|
||||
},
|
||||
|
||||
// ......................................................................... misc
|
||||
/** String representation */
|
||||
toString : function(){
|
||||
var modelString = ( this.model )?( this.model + '' ):( '(no model)' );
|
||||
return 'NestedDCDCEListItemView(' + modelString + ')';
|
||||
// ......................................................................... misc
|
||||
/** String representation */
|
||||
toString: function() {
|
||||
var modelString = this.model ? `${this.model}` : "(no model)";
|
||||
return `NestedDCDCEListItemView(${modelString})`;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
);
|
||||
|
||||
//==============================================================================
|
||||
return {
|
||||
DCListItemView : DCListItemView,
|
||||
DCEListItemView : DCEListItemView,
|
||||
DatasetDCEListItemView : DatasetDCEListItemView,
|
||||
NestedDCDCEListItemView : NestedDCDCEListItemView
|
||||
};
|
||||
});
|
||||
export default {
|
||||
DCListItemView: DCListItemView,
|
||||
DCEListItemView: DCEListItemView,
|
||||
DatasetDCEListItemView: DatasetDCEListItemView,
|
||||
NestedDCDCEListItemView: NestedDCDCEListItemView
|
||||
};
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
define([
|
||||
"mvc/dataset/dataset-model",
|
||||
"mvc/base-mvc",
|
||||
"utils/localization"
|
||||
], function( DATASET_MODEL, BASE_MVC, _l ){
|
||||
'use strict';
|
||||
import DATASET_MODEL from "mvc/dataset/dataset-model";
|
||||
import BASE_MVC from "mvc/base-mvc";
|
||||
import _l from "utils/localization";
|
||||
|
||||
//==============================================================================
|
||||
/*
|
||||
@@ -52,124 +49,132 @@ any YAGNI).
|
||||
* effectively changing a DCE from a container to a subclass (has a --> is a).
|
||||
*/
|
||||
var DatasetCollectionElementMixin = {
|
||||
|
||||
/** default attributes used by elements in a dataset collection */
|
||||
defaults : {
|
||||
model_class : 'DatasetCollectionElement',
|
||||
element_identifier : null,
|
||||
element_index : null,
|
||||
element_type : null
|
||||
defaults: {
|
||||
model_class: "DatasetCollectionElement",
|
||||
element_identifier: null,
|
||||
element_index: null,
|
||||
element_type: null
|
||||
},
|
||||
|
||||
/** merge the attributes of the sub-object 'object' into this model */
|
||||
_mergeObject : function( attributes ){
|
||||
_mergeObject: function(attributes) {
|
||||
// if we don't preserve and correct ids here, the element id becomes the object id
|
||||
// and collision in backbone's _byId will occur and only
|
||||
_.extend( attributes, attributes.object, { element_id: attributes.id });
|
||||
_.extend(attributes, attributes.object, {
|
||||
element_id: attributes.id
|
||||
});
|
||||
delete attributes.object;
|
||||
return attributes;
|
||||
},
|
||||
|
||||
/** override to merge this.object into this */
|
||||
constructor : function( attributes, options ){
|
||||
constructor: function(attributes, options) {
|
||||
// console.debug( '\t DatasetCollectionElement.constructor:', attributes, options );
|
||||
attributes = this._mergeObject( attributes );
|
||||
this.idAttribute = 'element_id';
|
||||
Backbone.Model.apply( this, arguments );
|
||||
attributes = this._mergeObject(attributes);
|
||||
this.idAttribute = "element_id";
|
||||
Backbone.Model.apply(this, arguments);
|
||||
},
|
||||
|
||||
/** when the model is fetched, merge this.object into this */
|
||||
parse : function( response, options ){
|
||||
parse: function(response, options) {
|
||||
var attributes = response;
|
||||
attributes = this._mergeObject( attributes );
|
||||
attributes = this._mergeObject(attributes);
|
||||
return attributes;
|
||||
}
|
||||
};
|
||||
|
||||
/** @class Concrete class of Generic DatasetCollectionElement */
|
||||
var DatasetCollectionElement = Backbone.Model
|
||||
.extend( BASE_MVC.LoggableMixin )
|
||||
.extend( DatasetCollectionElementMixin )
|
||||
.extend({ _logNamespace : 'collections' });
|
||||
|
||||
var DatasetCollectionElement = Backbone.Model.extend(BASE_MVC.LoggableMixin)
|
||||
.extend(DatasetCollectionElementMixin)
|
||||
.extend({ _logNamespace: "collections" });
|
||||
|
||||
//==============================================================================
|
||||
/** @class Base/Abstract Backbone collection for Generic DCEs. */
|
||||
var DCECollection = Backbone.Collection.extend( BASE_MVC.LoggableMixin ).extend(
|
||||
/** @lends DCECollection.prototype */{
|
||||
_logNamespace : 'collections',
|
||||
/** @class Base/Abstract Backbone collection for Generic DCEs -
|
||||
current may be associated with a dataset (DatasetDCECollection)
|
||||
or another collection (NestedDCDCECollection).
|
||||
*/
|
||||
var DCECollection = Backbone.Collection.extend(BASE_MVC.LoggableMixin).extend(
|
||||
/** @lends DCECollection.prototype */ {
|
||||
_logNamespace: "collections",
|
||||
|
||||
model: DatasetCollectionElement,
|
||||
model: DatasetCollectionElement,
|
||||
|
||||
/** String representation. */
|
||||
toString : function(){
|
||||
return ([ 'DatasetCollectionElementCollection(', this.length, ')' ].join( '' ));
|
||||
/** String representation. */
|
||||
toString: function() {
|
||||
return ["DatasetCollectionElementCollection(", this.length, ")"].join("");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
);
|
||||
|
||||
//==============================================================================
|
||||
/** @class Backbone model for a dataset collection element that is a dataset (HDA).
|
||||
*/
|
||||
var DatasetDCE = DATASET_MODEL.DatasetAssociation.extend( BASE_MVC.mixin( DatasetCollectionElementMixin,
|
||||
/** @lends DatasetDCE.prototype */{
|
||||
var DatasetDCE = DATASET_MODEL.DatasetAssociation.extend(
|
||||
BASE_MVC.mixin(
|
||||
DatasetCollectionElementMixin,
|
||||
/** @lends DatasetDCE.prototype */ {
|
||||
/** url fn */
|
||||
url: function() {
|
||||
// won't always be an hda
|
||||
if (!this.has("history_id")) {
|
||||
console.warn("no endpoint for non-hdas within a collection yet");
|
||||
// (a little silly since this api endpoint *also* points at hdas)
|
||||
return `${Galaxy.root}api/datasets`;
|
||||
}
|
||||
return `${Galaxy.root}api/histories/${this.get("history_id")}/contents/${this.get("id")}`;
|
||||
},
|
||||
|
||||
/** url fn */
|
||||
url : function(){
|
||||
// won't always be an hda
|
||||
if( !this.has( 'history_id' ) ){
|
||||
console.warn( 'no endpoint for non-hdas within a collection yet' );
|
||||
// (a little silly since this api endpoint *also* points at hdas)
|
||||
return Galaxy.root + 'api/datasets';
|
||||
defaults: _.extend(
|
||||
{},
|
||||
DATASET_MODEL.DatasetAssociation.prototype.defaults,
|
||||
DatasetCollectionElementMixin.defaults
|
||||
),
|
||||
|
||||
_downloadQueryParameters: function() {
|
||||
var fileExt = this.get("file_ext");
|
||||
var elementIdentifier = this.get("element_identifier");
|
||||
var parentHdcaId = this.get("parent_hdca_id");
|
||||
return `?to_ext=${fileExt}&hdca_id=${parentHdcaId}&element_identifier=${elementIdentifier}`;
|
||||
},
|
||||
|
||||
// because all objects have constructors (as this hashmap would even if this next line wasn't present)
|
||||
// the constructor in hcontentMixin won't be attached by BASE_MVC.mixin to this model
|
||||
// - re-apply manually for now
|
||||
/** call the mixin constructor */
|
||||
constructor: function(attributes, options) {
|
||||
this.debug("\t DatasetDCE.constructor:", attributes, options);
|
||||
//DATASET_MODEL.DatasetAssociation.prototype.constructor.call( this, attributes, options );
|
||||
DatasetCollectionElementMixin.constructor.call(this, attributes, options);
|
||||
},
|
||||
|
||||
/** Does this model already contain detailed data (as opposed to just summary level data)? */
|
||||
hasDetails: function() {
|
||||
return this.elements && this.elements.length;
|
||||
},
|
||||
|
||||
/** String representation. */
|
||||
toString: function() {
|
||||
var objStr = this.get("element_identifier");
|
||||
return `DatasetDCE({objStr})`;
|
||||
}
|
||||
}
|
||||
return Galaxy.root + 'api/histories/' + this.get( 'history_id' ) + '/contents/' + this.get( 'id' );
|
||||
},
|
||||
|
||||
defaults : _.extend( {},
|
||||
DATASET_MODEL.DatasetAssociation.prototype.defaults,
|
||||
DatasetCollectionElementMixin.defaults
|
||||
),
|
||||
|
||||
_downloadQueryParameters : function() {
|
||||
return '?to_ext=' + this.get( 'file_ext' ) + '&hdca_id=' + this.get( 'parent_hdca_id' ) + '&element_identifier=' + this.get( 'element_identifier' );
|
||||
},
|
||||
|
||||
// because all objects have constructors (as this hashmap would even if this next line wasn't present)
|
||||
// the constructor in hcontentMixin won't be attached by BASE_MVC.mixin to this model
|
||||
// - re-apply manually for now
|
||||
/** call the mixin constructor */
|
||||
constructor : function( attributes, options ){
|
||||
this.debug( '\t DatasetDCE.constructor:', attributes, options );
|
||||
//DATASET_MODEL.DatasetAssociation.prototype.constructor.call( this, attributes, options );
|
||||
DatasetCollectionElementMixin.constructor.call( this, attributes, options );
|
||||
},
|
||||
|
||||
/** Does this model already contain detailed data (as opposed to just summary level data)? */
|
||||
hasDetails : function(){
|
||||
return this.elements && this.elements.length;
|
||||
},
|
||||
|
||||
/** String representation. */
|
||||
toString : function(){
|
||||
var objStr = this.get( 'element_identifier' );
|
||||
return ([ 'DatasetDCE(', objStr, ')' ].join( '' ));
|
||||
}
|
||||
}));
|
||||
|
||||
)
|
||||
);
|
||||
|
||||
//==============================================================================
|
||||
/** @class DCECollection of DatasetDCE's (a list of datasets, a pair of datasets).
|
||||
*/
|
||||
var DatasetDCECollection = DCECollection.extend(
|
||||
/** @lends DatasetDCECollection.prototype */{
|
||||
model: DatasetDCE,
|
||||
/** @lends DatasetDCECollection.prototype */ {
|
||||
model: DatasetDCE,
|
||||
|
||||
/** String representation. */
|
||||
toString : function(){
|
||||
return ([ 'DatasetDCECollection(', this.length, ')' ].join( '' ));
|
||||
/** String representation. */
|
||||
toString: function() {
|
||||
return ["DatasetDCECollection(", this.length, ")"].join("");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
);
|
||||
|
||||
//_________________________________________________________________________________________________ COLLECTIONS
|
||||
/** @class Backbone model for Dataset Collections.
|
||||
@@ -180,300 +185,179 @@ var DatasetDCECollection = DCECollection.extend(
|
||||
* - passes that json onto the bbone collection
|
||||
* - caches the bbone collection in this.elements
|
||||
*/
|
||||
var DatasetCollection = Backbone.Model
|
||||
.extend( BASE_MVC.LoggableMixin )
|
||||
.extend( BASE_MVC.SearchableModelMixin )
|
||||
.extend(/** @lends DatasetCollection.prototype */{
|
||||
_logNamespace : 'collections',
|
||||
var DatasetCollection = Backbone.Model.extend(BASE_MVC.LoggableMixin)
|
||||
.extend(BASE_MVC.SearchableModelMixin)
|
||||
.extend(
|
||||
/** @lends DatasetCollection.prototype */ {
|
||||
_logNamespace: "collections",
|
||||
|
||||
/** default attributes for a model */
|
||||
defaults : {
|
||||
/* 'list', 'paired', or 'list:paired' */
|
||||
collection_type : null,
|
||||
//??
|
||||
deleted : false
|
||||
},
|
||||
/** default attributes for a model */
|
||||
defaults: {
|
||||
/* 'list', 'paired', or 'list:paired' */
|
||||
collection_type: null,
|
||||
//??
|
||||
deleted: false
|
||||
},
|
||||
|
||||
/** Which class to use for elements */
|
||||
collectionClass : DCECollection,
|
||||
/** Which class to use for elements */
|
||||
collectionClass: function() {
|
||||
if (this.attributes.collection_type.indexOf(":") > 0) {
|
||||
return NestedDCDCECollection;
|
||||
} else {
|
||||
return DatasetDCECollection;
|
||||
}
|
||||
},
|
||||
|
||||
/** set up: create elements instance var and (on changes to elements) update them */
|
||||
initialize : function( model, options ){
|
||||
this.debug( this + '(DatasetCollection).initialize:', model, options, this );
|
||||
this.elements = this._createElementsModel();
|
||||
this.on( 'change:elements', function(){
|
||||
this.log( 'change:elements' );
|
||||
//TODO: prob. better to update the collection instead of re-creating it
|
||||
this.elements = this._createElementsModel();
|
||||
});
|
||||
},
|
||||
/** set up: create elements instance var and (on changes to elements) update them */
|
||||
initialize: function(model, options) {
|
||||
this.debug(`${this}(DatasetCollection).initialize:`, model, options, this);
|
||||
this.elements = this._createElementsModel();
|
||||
this.on("change:elements", function() {
|
||||
this.log("change:elements");
|
||||
//TODO: prob. better to update the collection instead of re-creating it
|
||||
this.elements = this._createElementsModel();
|
||||
});
|
||||
},
|
||||
|
||||
/** move elements model attribute to full collection */
|
||||
_createElementsModel : function(){
|
||||
this.debug( this + '._createElementsModel', this.collectionClass, this.get( 'elements' ), this.elements );
|
||||
//TODO: same patterns as DatasetCollectionElement _createObjectModel - refactor to BASE_MVC.hasSubModel?
|
||||
var elements = this.get( 'elements' ) || [];
|
||||
this.unset( 'elements', { silent: true });
|
||||
var self = this;
|
||||
_.each(elements, function(element, index) {
|
||||
_.extend(element, {"parent_hdca_id": self.get("id")});
|
||||
});
|
||||
this.elements = new this.collectionClass( elements );
|
||||
//this.debug( 'collectionClass:', this.collectionClass + '', this.elements );
|
||||
return this.elements;
|
||||
},
|
||||
/** move elements model attribute to full collection */
|
||||
_createElementsModel: function() {
|
||||
var collectionClass = this.collectionClass();
|
||||
this.debug(`${this}._createElementsModel`, collectionClass, this.get("elements"), this.elements);
|
||||
//TODO: same patterns as DatasetCollectionElement _createObjectModel - refactor to BASE_MVC.hasSubModel?
|
||||
var elements = this.get("elements") || [];
|
||||
this.unset("elements", { silent: true });
|
||||
var self = this;
|
||||
_.each(elements, (element, index) => {
|
||||
_.extend(element, {
|
||||
parent_hdca_id: self.get("id")
|
||||
});
|
||||
});
|
||||
this.elements = new collectionClass(elements);
|
||||
//this.debug( 'collectionClass:', this.collectionClass + '', this.elements );
|
||||
return this.elements;
|
||||
},
|
||||
|
||||
// ........................................................................ common queries
|
||||
/** pass the elements back within the model json when this is serialized */
|
||||
toJSON : function(){
|
||||
var json = Backbone.Model.prototype.toJSON.call( this );
|
||||
if( this.elements ){
|
||||
json.elements = this.elements.toJSON();
|
||||
// ........................................................................ common queries
|
||||
/** pass the elements back within the model json when this is serialized */
|
||||
toJSON: function() {
|
||||
var json = Backbone.Model.prototype.toJSON.call(this);
|
||||
if (this.elements) {
|
||||
json.elements = this.elements.toJSON();
|
||||
}
|
||||
return json;
|
||||
},
|
||||
|
||||
/** Is this collection in a 'ready' state no processing (for the collection) is left
|
||||
* to do on the server.
|
||||
*/
|
||||
inReadyState: function() {
|
||||
var populated = this.get("populated");
|
||||
return this.isDeletedOrPurged() || populated;
|
||||
},
|
||||
|
||||
//TODO:?? the following are the same interface as DatasetAssociation - can we combine?
|
||||
/** Does the DC contain any elements yet? Is a fetch() required? */
|
||||
hasDetails: function() {
|
||||
return this.elements.length !== 0;
|
||||
},
|
||||
|
||||
/** Given the filters, what models in this.elements would be returned? */
|
||||
getVisibleContents: function(filters) {
|
||||
// filters unused for now
|
||||
return this.elements;
|
||||
},
|
||||
|
||||
// ........................................................................ ajax
|
||||
/** override to use actual Dates objects for create/update times */
|
||||
parse: function(response, options) {
|
||||
var parsed = Backbone.Model.prototype.parse.call(this, response, options);
|
||||
if (parsed.create_time) {
|
||||
parsed.create_time = new Date(parsed.create_time);
|
||||
}
|
||||
if (parsed.update_time) {
|
||||
parsed.update_time = new Date(parsed.update_time);
|
||||
}
|
||||
return parsed;
|
||||
},
|
||||
|
||||
/** save this dataset, _Mark_ing it as deleted (just a flag) */
|
||||
delete: function(options) {
|
||||
if (this.get("deleted")) {
|
||||
return jQuery.when();
|
||||
}
|
||||
return this.save({ deleted: true }, options);
|
||||
},
|
||||
/** save this dataset, _Mark_ing it as undeleted */
|
||||
undelete: function(options) {
|
||||
if (!this.get("deleted") || this.get("purged")) {
|
||||
return jQuery.when();
|
||||
}
|
||||
return this.save({ deleted: false }, options);
|
||||
},
|
||||
|
||||
/** Is this collection deleted or purged? */
|
||||
isDeletedOrPurged: function() {
|
||||
return this.get("deleted") || this.get("purged");
|
||||
},
|
||||
|
||||
// ........................................................................ searchable
|
||||
/** searchable attributes for collections */
|
||||
searchAttributes: ["name", "tags"],
|
||||
|
||||
// ........................................................................ misc
|
||||
/** String representation */
|
||||
toString: function() {
|
||||
var idAndName = [this.get("id"), this.get("name") || this.get("element_identifier")];
|
||||
return `DatasetCollection(${idAndName.join(",")})`;
|
||||
}
|
||||
}
|
||||
return json;
|
||||
},
|
||||
|
||||
/** Is this collection in a 'ready' state no processing (for the collection) is left
|
||||
* to do on the server.
|
||||
*/
|
||||
inReadyState : function(){
|
||||
var populated = this.get( 'populated' );
|
||||
return ( this.isDeletedOrPurged() || populated );
|
||||
},
|
||||
|
||||
//TODO:?? the following are the same interface as DatasetAssociation - can we combine?
|
||||
/** Does the DC contain any elements yet? Is a fetch() required? */
|
||||
hasDetails : function(){
|
||||
return this.elements.length !== 0;
|
||||
},
|
||||
|
||||
/** Given the filters, what models in this.elements would be returned? */
|
||||
getVisibleContents : function( filters ){
|
||||
// filters unused for now
|
||||
return this.elements;
|
||||
},
|
||||
|
||||
// ........................................................................ ajax
|
||||
/** override to use actual Dates objects for create/update times */
|
||||
parse : function( response, options ){
|
||||
var parsed = Backbone.Model.prototype.parse.call( this, response, options );
|
||||
if( parsed.create_time ){
|
||||
parsed.create_time = new Date( parsed.create_time );
|
||||
}
|
||||
if( parsed.update_time ){
|
||||
parsed.update_time = new Date( parsed.update_time );
|
||||
}
|
||||
return parsed;
|
||||
},
|
||||
|
||||
/** save this dataset, _Mark_ing it as deleted (just a flag) */
|
||||
'delete' : function( options ){
|
||||
if( this.get( 'deleted' ) ){ return jQuery.when(); }
|
||||
return this.save( { deleted: true }, options );
|
||||
},
|
||||
/** save this dataset, _Mark_ing it as undeleted */
|
||||
undelete : function( options ){
|
||||
if( !this.get( 'deleted' ) || this.get( 'purged' ) ){ return jQuery.when(); }
|
||||
return this.save( { deleted: false }, options );
|
||||
},
|
||||
|
||||
/** Is this collection deleted or purged? */
|
||||
isDeletedOrPurged : function(){
|
||||
return ( this.get( 'deleted' ) || this.get( 'purged' ) );
|
||||
},
|
||||
|
||||
// ........................................................................ searchable
|
||||
/** searchable attributes for collections */
|
||||
searchAttributes : [
|
||||
'name'
|
||||
],
|
||||
|
||||
// ........................................................................ misc
|
||||
/** String representation */
|
||||
toString : function(){
|
||||
var idAndName = [ this.get( 'id' ), this.get( 'name' ) || this.get( 'element_identifier' ) ];
|
||||
return 'DatasetCollection(' + ( idAndName.join(',') ) + ')';
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** Model for a DatasetCollection containing datasets (non-nested).
|
||||
*/
|
||||
var ListDatasetCollection = DatasetCollection.extend(
|
||||
/** @lends ListDatasetCollection.prototype */{
|
||||
|
||||
/** override since we know the collection will only contain datasets */
|
||||
collectionClass : DatasetDCECollection,
|
||||
|
||||
/** String representation. */
|
||||
toString : function(){ return 'List' + DatasetCollection.prototype.toString.call( this ); }
|
||||
});
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** Model for a DatasetCollection containing fwd/rev datasets (a list of 2).
|
||||
*/
|
||||
var PairDatasetCollection = ListDatasetCollection.extend(
|
||||
/** @lends PairDatasetCollection.prototype */{
|
||||
|
||||
/** String representation. */
|
||||
toString : function(){ return 'Pair' + DatasetCollection.prototype.toString.call( this ); }
|
||||
});
|
||||
|
||||
);
|
||||
|
||||
//_________________________________________________________________________________________________ NESTED COLLECTIONS
|
||||
// this is where things get weird, man. Weird.
|
||||
//TODO: it might be possible to compact all the following...I think.
|
||||
//==============================================================================
|
||||
/** @class Backbone model for a Generic DatasetCollectionElement that is also a DatasetCollection
|
||||
* (a nested collection). Currently only list:paired.
|
||||
* (a nested collection).
|
||||
*/
|
||||
var NestedDCDCE = DatasetCollection.extend( BASE_MVC.mixin( DatasetCollectionElementMixin,
|
||||
/** @lends NestedDCDCE.prototype */{
|
||||
|
||||
// because all objects have constructors (as this hashmap would even if this next line wasn't present)
|
||||
// the constructor in hcontentMixin won't be attached by BASE_MVC.mixin to this model
|
||||
// - re-apply manually it now
|
||||
/** call the mixin constructor */
|
||||
constructor : function( attributes, options ){
|
||||
this.debug( '\t NestedDCDCE.constructor:', attributes, options );
|
||||
DatasetCollectionElementMixin.constructor.call( this, attributes, options );
|
||||
},
|
||||
|
||||
/** String representation. */
|
||||
toString : function(){
|
||||
var objStr = ( this.object )?( '' + this.object ):( this.get( 'element_identifier' ) );
|
||||
return ([ 'NestedDCDCE(', objStr, ')' ].join( '' ));
|
||||
}
|
||||
}));
|
||||
var NestedDCDCE = DatasetCollection.extend(
|
||||
BASE_MVC.mixin(
|
||||
DatasetCollectionElementMixin,
|
||||
/** @lends NestedDCDCE.prototype */ {
|
||||
// because all objects have constructors (as this hashmap would even if this next line wasn't present)
|
||||
// the constructor in hcontentMixin won't be attached by BASE_MVC.mixin to this model
|
||||
// - re-apply manually it now
|
||||
/** call the mixin constructor */
|
||||
constructor: function(attributes, options) {
|
||||
this.debug("\t NestedDCDCE.constructor:", attributes, options);
|
||||
DatasetCollectionElementMixin.constructor.call(this, attributes, options);
|
||||
},
|
||||
|
||||
/** String representation. */
|
||||
toString: function() {
|
||||
var objStr = this.object ? `${this.object}` : this.get("element_identifier");
|
||||
return ["NestedDCDCE(", objStr, ")"].join("");
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
//==============================================================================
|
||||
/** @class Backbone collection containing Generic NestedDCDCE's (nested dataset collections).
|
||||
*/
|
||||
var NestedDCDCECollection = DCECollection.extend(
|
||||
/** @lends NestedDCDCECollection.prototype */{
|
||||
/** @lends NestedDCDCECollection.prototype */ {
|
||||
/** This is a collection of nested collections */
|
||||
model: NestedDCDCE,
|
||||
|
||||
/** This is a collection of nested collections */
|
||||
model: NestedDCDCE,
|
||||
|
||||
/** String representation. */
|
||||
toString : function(){
|
||||
return ([ 'NestedDCDCECollection(', this.length, ')' ].join( '' ));
|
||||
/** String representation. */
|
||||
toString: function() {
|
||||
return ["NestedDCDCECollection(", this.length, ")"].join("");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
);
|
||||
|
||||
//==============================================================================
|
||||
/** @class Backbone model for a paired dataset collection within a list:paired dataset collection.
|
||||
*/
|
||||
var NestedPairDCDCE = PairDatasetCollection.extend( BASE_MVC.mixin( DatasetCollectionElementMixin,
|
||||
/** @lends NestedPairDCDCE.prototype */{
|
||||
//TODO:?? possibly rename to NestedDatasetCollection?
|
||||
|
||||
// because all objects have constructors (as this hashmap would even if this next line wasn't present)
|
||||
// the constructor in hcontentMixin won't be attached by BASE_MVC.mixin to this model
|
||||
// - re-apply manually it now
|
||||
/** This is both a collection and a collection element - call the constructor */
|
||||
constructor : function( attributes, options ){
|
||||
this.debug( '\t NestedPairDCDCE.constructor:', attributes, options );
|
||||
//DatasetCollection.constructor.call( this, attributes, options );
|
||||
DatasetCollectionElementMixin.constructor.call( this, attributes, options );
|
||||
},
|
||||
|
||||
/** String representation. */
|
||||
toString : function(){
|
||||
var objStr = ( this.object )?( '' + this.object ):( this.get( 'element_identifier' ) );
|
||||
return ([ 'NestedPairDCDCE(', objStr, ')' ].join( '' ));
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** @class Backbone collection for a backbone collection containing paired dataset collections.
|
||||
*/
|
||||
var NestedPairDCDCECollection = NestedDCDCECollection.extend(
|
||||
/** @lends PairDCDCECollection.prototype */{
|
||||
|
||||
/** We know this collection is composed of only nested pair collections */
|
||||
model: NestedPairDCDCE,
|
||||
|
||||
/** String representation. */
|
||||
toString : function(){
|
||||
return ([ 'NestedPairDCDCECollection(', this.length, ')' ].join( '' ));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** @class Backbone Model for a DatasetCollection (list) that contains DatasetCollections (pairs).
|
||||
*/
|
||||
var ListPairedDatasetCollection = DatasetCollection.extend(
|
||||
/** @lends ListPairedDatasetCollection.prototype */{
|
||||
|
||||
/** list:paired is the only collection that itself contains collections */
|
||||
collectionClass : NestedPairDCDCECollection,
|
||||
|
||||
/** String representation. */
|
||||
toString : function(){
|
||||
return ([ 'ListPairedDatasetCollection(', this.get( 'name' ), ')' ].join( '' ));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** @class Backbone model for a list dataset collection within a list:list dataset collection. */
|
||||
var NestedListDCDCE = ListDatasetCollection.extend( BASE_MVC.mixin( DatasetCollectionElementMixin,
|
||||
/** @lends NestedListDCDCE.prototype */{
|
||||
|
||||
/** This is both a collection and a collection element - call the constructor */
|
||||
constructor : function( attributes, options ){
|
||||
this.debug( '\t NestedListDCDCE.constructor:', attributes, options );
|
||||
DatasetCollectionElementMixin.constructor.call( this, attributes, options );
|
||||
},
|
||||
|
||||
/** String representation. */
|
||||
toString : function(){
|
||||
var objStr = ( this.object )?( '' + this.object ):( this.get( 'element_identifier' ) );
|
||||
return ([ 'NestedListDCDCE(', objStr, ')' ].join( '' ));
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** @class Backbone collection containing list dataset collections. */
|
||||
var NestedListDCDCECollection = NestedDCDCECollection.extend({
|
||||
|
||||
/** We know this collection is composed of only nested pair collections */
|
||||
model: NestedListDCDCE,
|
||||
|
||||
/** String representation. */
|
||||
toString : function(){
|
||||
return ([ 'NestedListDCDCECollection(', this.length, ')' ].join( '' ));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** @class Backbone Model for a DatasetCollection (list) that contains other lists. */
|
||||
var ListOfListsDatasetCollection = DatasetCollection.extend({
|
||||
|
||||
/** list:paired is the only collection that itself contains collections */
|
||||
collectionClass : NestedListDCDCECollection,
|
||||
|
||||
/** String representation. */
|
||||
toString : function(){
|
||||
return ([ 'ListOfListsDatasetCollection(', this.get( 'name' ), ')' ].join( '' ));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
//==============================================================================
|
||||
return {
|
||||
ListDatasetCollection : ListDatasetCollection,
|
||||
PairDatasetCollection : PairDatasetCollection,
|
||||
ListPairedDatasetCollection : ListPairedDatasetCollection,
|
||||
ListOfListsDatasetCollection: ListOfListsDatasetCollection
|
||||
};
|
||||
});
|
||||
export default {
|
||||
DatasetCollection: DatasetCollection
|
||||
};
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
define([
|
||||
"mvc/collection/collection-view",
|
||||
"mvc/collection/collection-model",
|
||||
"mvc/collection/collection-li-edit",
|
||||
"mvc/base-mvc",
|
||||
"mvc/tag",
|
||||
"ui/fa-icon-button",
|
||||
"utils/localization",
|
||||
"ui/editable-text",
|
||||
], function( DC_VIEW, DC_MODEL, DC_EDIT, BASE_MVC, TAGS, faIconButton, _l ){
|
||||
import DC_VIEW from "mvc/collection/collection-view";
|
||||
import DC_MODEL from "mvc/collection/collection-model";
|
||||
import DC_EDIT from "mvc/collection/collection-li-edit";
|
||||
import BASE_MVC from "mvc/base-mvc";
|
||||
import TAGS from "mvc/tag";
|
||||
import faIconButton from "ui/fa-icon-button";
|
||||
import _l from "utils/localization";
|
||||
import "ui/editable-text";
|
||||
|
||||
'use strict';
|
||||
/* =============================================================================
|
||||
TODO:
|
||||
|
||||
@@ -18,170 +15,88 @@ TODO:
|
||||
*/
|
||||
var _super = DC_VIEW.CollectionView;
|
||||
var CollectionViewEdit = _super.extend(
|
||||
/** @lends CollectionView.prototype */{
|
||||
//MODEL is either a DatasetCollection (or subclass) or a DatasetCollectionElement (list of pairs)
|
||||
/** @lends CollectionView.prototype */ {
|
||||
//MODEL is either a DatasetCollection (or subclass) or a DatasetCollectionElement (list of pairs)
|
||||
|
||||
/** logger used to record this.log messages, commonly set to console */
|
||||
//logger : console,
|
||||
/** logger used to record this.log messages, commonly set to console */
|
||||
//logger : console,
|
||||
|
||||
/** sub view class used for datasets */
|
||||
DatasetDCEViewClass : DC_EDIT.DatasetDCEListItemEdit,
|
||||
/** sub view class used for nested collections */
|
||||
NestedDCDCEViewClass: DC_EDIT.NestedDCDCEListItemEdit,
|
||||
/** sub view class used for datasets */
|
||||
DatasetDCEViewClass: DC_EDIT.DatasetDCEListItemEdit,
|
||||
/** sub view class used for nested collections */
|
||||
NestedDCDCEViewClass: DC_EDIT.NestedDCDCEListItemEdit,
|
||||
|
||||
// ......................................................................... SET UP
|
||||
/** Set up the view, set up storage, bind listeners to HistoryContents events
|
||||
* @param {Object} attributes optional settings for the panel
|
||||
*/
|
||||
initialize : function( attributes ){
|
||||
_super.prototype.initialize.call( this, attributes );
|
||||
},
|
||||
|
||||
/** In this override, make the collection name editable
|
||||
*/
|
||||
_setUpBehaviors : function( $where ){
|
||||
$where = $where || this.$el;
|
||||
_super.prototype._setUpBehaviors.call( this, $where );
|
||||
if( !this.model ){ return; }
|
||||
|
||||
// anon users shouldn't have access to any of the following
|
||||
if( !Galaxy.user || Galaxy.user.isAnonymous() ){
|
||||
return;
|
||||
}
|
||||
|
||||
this.tagsEditorShown = true;
|
||||
|
||||
//TODO: extract
|
||||
var panel = this,
|
||||
nameSelector = '> .controls .name';
|
||||
$where.find( nameSelector )
|
||||
.attr( 'title', _l( 'Click to rename collection' ) )
|
||||
.tooltip({ placement: 'bottom' })
|
||||
.make_text_editable({
|
||||
on_finish: function( newName ){
|
||||
var previousName = panel.model.get( 'name' );
|
||||
if( newName && newName !== previousName ){
|
||||
panel.$el.find( nameSelector ).text( newName );
|
||||
panel.model.save({ name: newName })
|
||||
.fail( function(){
|
||||
panel.$el.find( nameSelector ).text( panel.model.previous( 'name' ) );
|
||||
});
|
||||
} else {
|
||||
panel.$el.find( nameSelector ).text( previousName );
|
||||
}
|
||||
}
|
||||
getNestedDCDCEViewClass: function() {
|
||||
return DC_EDIT.NestedDCDCEListItemEdit.extend({
|
||||
foldoutPanelClass: CollectionViewEdit
|
||||
});
|
||||
this.tagsEditor = new TAGS.TagsEditor({
|
||||
model : this.model,
|
||||
el : $where.find( '.tags-display' ),
|
||||
onshowFirstTime : function(){ this.render(); },
|
||||
usePrompt : false
|
||||
});
|
||||
this.tagsEditor.toggle( true );
|
||||
},
|
||||
},
|
||||
|
||||
// ........................................................................ misc
|
||||
/** string rep */
|
||||
toString : function(){
|
||||
return 'CollectionViewEdit(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';
|
||||
// ......................................................................... SET UP
|
||||
/** Set up the view, set up storage, bind listeners to HistoryContents events
|
||||
* @param {Object} attributes optional settings for the panel
|
||||
*/
|
||||
initialize: function(attributes) {
|
||||
_super.prototype.initialize.call(this, attributes);
|
||||
},
|
||||
|
||||
/** In this override, make the collection name editable
|
||||
*/
|
||||
_setUpBehaviors: function($where) {
|
||||
$where = $where || this.$el;
|
||||
_super.prototype._setUpBehaviors.call(this, $where);
|
||||
if (!this.model) {
|
||||
return;
|
||||
}
|
||||
|
||||
// anon users shouldn't have access to any of the following
|
||||
if (!Galaxy.user || Galaxy.user.isAnonymous()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.tagsEditorShown = true;
|
||||
|
||||
//TODO: extract
|
||||
var panel = this;
|
||||
|
||||
var nameSelector = "> .controls .name";
|
||||
$where
|
||||
.find(nameSelector)
|
||||
.attr("title", _l("Click to rename collection"))
|
||||
.tooltip({ placement: "bottom" })
|
||||
.make_text_editable({
|
||||
on_finish: function(newName) {
|
||||
var previousName = panel.model.get("name");
|
||||
if (newName && newName !== previousName) {
|
||||
panel.$el.find(nameSelector).text(newName);
|
||||
panel.model.save({ name: newName }).fail(() => {
|
||||
panel.$el.find(nameSelector).text(panel.model.previous("name"));
|
||||
});
|
||||
} else {
|
||||
panel.$el.find(nameSelector).text(previousName);
|
||||
}
|
||||
}
|
||||
});
|
||||
this.tagsEditor = new TAGS.TagsEditor({
|
||||
model: this.model,
|
||||
el: $where.find(".tags-display"),
|
||||
onshowFirstTime: function() {
|
||||
this.render();
|
||||
},
|
||||
usePrompt: false
|
||||
});
|
||||
this.tagsEditor.toggle(true);
|
||||
},
|
||||
|
||||
// ........................................................................ misc
|
||||
/** string rep */
|
||||
toString: function() {
|
||||
return `CollectionViewEdit(${this.model ? this.model.get("name") : ""})`;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// =============================================================================
|
||||
/** @class non-editable, read-only View/Controller for a dataset collection. */
|
||||
var ListCollectionViewEdit = CollectionViewEdit.extend(
|
||||
/** @lends ListCollectionView.prototype */{
|
||||
|
||||
//TODO: not strictly needed - due to switch in CollectionView._getContentClass
|
||||
/** sub view class used for datasets */
|
||||
DatasetDCEViewClass : DC_EDIT.DatasetDCEListItemEdit,
|
||||
|
||||
// ........................................................................ misc
|
||||
/** string rep */
|
||||
toString : function(){
|
||||
return 'ListCollectionViewEdit(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// =============================================================================
|
||||
/** @class Editable, read-only View/Controller for a dataset collection. */
|
||||
var PairCollectionViewEdit = ListCollectionViewEdit.extend(
|
||||
/** @lends PairCollectionViewEdit.prototype */{
|
||||
|
||||
// ........................................................................ misc
|
||||
/** string rep */
|
||||
toString : function(){
|
||||
return 'PairCollectionViewEdit(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// =============================================================================
|
||||
/** @class Editable (roughly since these collections are immutable),
|
||||
* View/Controller for a dataset collection.
|
||||
*/
|
||||
var NestedPairCollectionViewEdit = PairCollectionViewEdit.extend(
|
||||
/** @lends NestedPairCollectionViewEdit.prototype */{
|
||||
|
||||
/** Override to remove the editable text from the name/identifier - these collections are considered immutable */
|
||||
_setUpBehaviors : function( $where ){
|
||||
_super.prototype._setUpBehaviors.call( this, $where );
|
||||
},
|
||||
|
||||
// ........................................................................ misc
|
||||
/** string rep */
|
||||
toString : function(){
|
||||
return 'NestedPairCollectionViewEdit(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// =============================================================================
|
||||
/** @class editable, View/Controller for a list of pairs dataset collection. */
|
||||
var ListOfPairsCollectionViewEdit = CollectionViewEdit.extend(
|
||||
/** @lends ListOfPairsCollectionView.prototype */{
|
||||
|
||||
//TODO: not strictly needed - due to switch in CollectionView._getContentClass
|
||||
/** sub view class used for nested collections */
|
||||
NestedDCDCEViewClass : DC_EDIT.NestedDCDCEListItemEdit.extend({
|
||||
foldoutPanelClass : NestedPairCollectionViewEdit
|
||||
}),
|
||||
|
||||
// ........................................................................ misc
|
||||
/** string rep */
|
||||
toString : function(){
|
||||
return 'ListOfPairsCollectionViewEdit(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// =============================================================================
|
||||
/** @class View/Controller for a list of lists dataset collection. */
|
||||
var ListOfListsCollectionViewEdit = CollectionViewEdit.extend(
|
||||
/** @lends ListOfListsCollectionView.prototype */{
|
||||
|
||||
//TODO: not strictly needed - due to switch in CollectionView._getContentClass
|
||||
/** sub view class used for nested collections */
|
||||
NestedDCDCEViewClass : DC_EDIT.NestedDCDCEListItemEdit.extend({
|
||||
foldoutPanelClass : NestedPairCollectionViewEdit
|
||||
}),
|
||||
|
||||
// ........................................................................ misc
|
||||
/** string rep */
|
||||
toString : function(){
|
||||
return 'ListOfListsCollectionViewEdit(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';
|
||||
}
|
||||
});
|
||||
|
||||
);
|
||||
|
||||
//==============================================================================
|
||||
return {
|
||||
CollectionViewEdit : CollectionViewEdit,
|
||||
ListCollectionViewEdit : ListCollectionViewEdit,
|
||||
PairCollectionViewEdit : PairCollectionViewEdit,
|
||||
ListOfPairsCollectionViewEdit : ListOfPairsCollectionViewEdit,
|
||||
ListOfListsCollectionViewEdit : ListOfListsCollectionViewEdit
|
||||
};
|
||||
});
|
||||
export default {
|
||||
CollectionViewEdit: CollectionViewEdit
|
||||
};
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
define([
|
||||
"mvc/list/list-view",
|
||||
"mvc/collection/collection-model",
|
||||
"mvc/collection/collection-li",
|
||||
"mvc/base-mvc",
|
||||
"utils/localization"
|
||||
], function( LIST_VIEW, DC_MODEL, DC_LI, BASE_MVC, _l ){
|
||||
import LIST_VIEW from "mvc/list/list-view";
|
||||
import DC_MODEL from "mvc/collection/collection-model";
|
||||
import DC_LI from "mvc/collection/collection-li";
|
||||
import BASE_MVC from "mvc/base-mvc";
|
||||
import _l from "utils/localization";
|
||||
|
||||
'use strict';
|
||||
|
||||
var logNamespace = 'collections';
|
||||
var logNamespace = "collections";
|
||||
/* =============================================================================
|
||||
TODO:
|
||||
|
||||
@@ -17,249 +13,221 @@ TODO:
|
||||
*/
|
||||
var _super = LIST_VIEW.ModelListPanel;
|
||||
var CollectionView = _super.extend(
|
||||
/** @lends CollectionView.prototype */{
|
||||
//MODEL is either a DatasetCollection (or subclass) or a DatasetCollectionElement (list of pairs)
|
||||
_logNamespace : logNamespace,
|
||||
/** @lends CollectionView.prototype */ {
|
||||
//MODEL is either a DatasetCollection (or subclass) or a DatasetCollectionElement (list of pairs)
|
||||
_logNamespace: logNamespace,
|
||||
|
||||
className : _super.prototype.className + ' dataset-collection-panel',
|
||||
className: `${_super.prototype.className} dataset-collection-panel`,
|
||||
|
||||
/** sub view class used for datasets */
|
||||
DatasetDCEViewClass : DC_LI.DatasetDCEListItemView,
|
||||
/** sub view class used for nested collections */
|
||||
NestedDCDCEViewClass: DC_LI.NestedDCDCEListItemView,
|
||||
/** key of attribute in model to assign to this.collection */
|
||||
modelCollectionKey : 'elements',
|
||||
/** sub view class used for datasets */
|
||||
DatasetDCEViewClass: DC_LI.DatasetDCEListItemView,
|
||||
|
||||
// ......................................................................... SET UP
|
||||
/** Set up the view, set up storage, bind listeners to HistoryContents events
|
||||
* @param {Object} attributes optional settings for the panel
|
||||
*/
|
||||
initialize : function( attributes ){
|
||||
_super.prototype.initialize.call( this, attributes );
|
||||
this.linkTarget = attributes.linkTarget || '_blank';
|
||||
/** key of attribute in model to assign to this.collection */
|
||||
modelCollectionKey: "elements",
|
||||
|
||||
this.hasUser = attributes.hasUser;
|
||||
/** A stack of panels that currently cover or hide this panel */
|
||||
this.panelStack = [];
|
||||
/** The text of the link to go back to the panel containing this one */
|
||||
this.parentName = attributes.parentName;
|
||||
/** foldout or drilldown */
|
||||
this.foldoutStyle = attributes.foldoutStyle || 'foldout';
|
||||
this.downloadUrl = this.model.attributes.url + '/download';
|
||||
},
|
||||
// ......................................................................... SET UP
|
||||
/** Set up the view, set up storage, bind listeners to HistoryContents events
|
||||
* @param {Object} attributes optional settings for the panel
|
||||
*/
|
||||
initialize: function(attributes) {
|
||||
_super.prototype.initialize.call(this, attributes);
|
||||
this.linkTarget = attributes.linkTarget || "_blank";
|
||||
|
||||
_queueNewRender : function( $newRender, speed ) {
|
||||
speed = ( speed === undefined )?( this.fxSpeed ):( speed );
|
||||
var panel = this;
|
||||
panel.log( '_queueNewRender:', $newRender, speed );
|
||||
this.hasUser = attributes.hasUser;
|
||||
/** A stack of panels that currently cover or hide this panel */
|
||||
this.panelStack = [];
|
||||
/** The text of the link to go back to the panel containing this one */
|
||||
this.parentName = attributes.parentName;
|
||||
/** foldout or drilldown */
|
||||
this.foldoutStyle = attributes.foldoutStyle || "foldout";
|
||||
this.downloadUrl = `${Galaxy.root}api/dataset_collections/${this.model.attributes.id}/download`;
|
||||
},
|
||||
|
||||
// TODO: jquery@1.12 doesn't change display when the elem has display: flex
|
||||
// this causes display: block for those elems after the use of show/hide animations
|
||||
// animations are removed from this view for now until fixed
|
||||
panel._swapNewRender( $newRender );
|
||||
panel.trigger( 'rendered', panel );
|
||||
},
|
||||
getNestedDCDCEViewClass: function() {
|
||||
return DC_LI.NestedDCDCEListItemView.extend({
|
||||
foldoutPanelClass: CollectionView
|
||||
});
|
||||
},
|
||||
|
||||
// ------------------------------------------------------------------------ sub-views
|
||||
/** In this override, use model.getVisibleContents */
|
||||
_filterCollection : function(){
|
||||
//TODO: should *not* be model.getVisibleContents - visibility is not model related
|
||||
return this.model.getVisibleContents();
|
||||
},
|
||||
_queueNewRender: function($newRender, speed) {
|
||||
speed = speed === undefined ? this.fxSpeed : speed;
|
||||
var panel = this;
|
||||
this.handleWarning($newRender);
|
||||
panel.log("_queueNewRender:", $newRender, speed);
|
||||
|
||||
/** override to return proper view class based on element_type */
|
||||
_getItemViewClass : function( model ){
|
||||
//this.debug( this + '._getItemViewClass:', model );
|
||||
//TODO: subclasses use DCEViewClass - but are currently unused - decide
|
||||
switch( model.get( 'element_type' ) ){
|
||||
case 'hda':
|
||||
return this.DatasetDCEViewClass;
|
||||
case 'dataset_collection':
|
||||
return this.NestedDCDCEViewClass;
|
||||
}
|
||||
throw new TypeError( 'Unknown element type:', model.get( 'element_type' ) );
|
||||
},
|
||||
// TODO: jquery@1.12 doesn't change display when the elem has display: flex
|
||||
// this causes display: block for those elems after the use of show/hide animations
|
||||
// animations are removed from this view for now until fixed
|
||||
panel._swapNewRender($newRender);
|
||||
panel.trigger("rendered", panel);
|
||||
},
|
||||
|
||||
/** override to add link target and anon */
|
||||
_getItemViewOptions : function( model ){
|
||||
var options = _super.prototype._getItemViewOptions.call( this, model );
|
||||
return _.extend( options, {
|
||||
linkTarget : this.linkTarget,
|
||||
hasUser : this.hasUser,
|
||||
//TODO: could move to only nested: list:paired
|
||||
foldoutStyle : this.foldoutStyle
|
||||
});
|
||||
},
|
||||
|
||||
// ------------------------------------------------------------------------ collection sub-views
|
||||
/** In this override, add/remove expanded/collapsed model ids to/from web storage */
|
||||
_setUpItemViewListeners : function( view ){
|
||||
var panel = this;
|
||||
_super.prototype._setUpItemViewListeners.call( panel, view );
|
||||
|
||||
// use pub-sub to: handle drilldown expansion and collapse
|
||||
panel.listenTo( view, {
|
||||
'expanded:drilldown': function( v, drilldown ){
|
||||
this._expandDrilldownPanel( drilldown );
|
||||
},
|
||||
'collapsed:drilldown': function( v, drilldown ){
|
||||
this._collapseDrilldownPanel( drilldown );
|
||||
handleWarning: function($newRender) {
|
||||
var viewLength = this.views.length;
|
||||
var elementCount = this.model.get("element_count");
|
||||
if (elementCount && elementCount !== viewLength) {
|
||||
var warning = _l(`displaying only ${viewLength} of ${elementCount} items`);
|
||||
var $warns = $newRender.find(".elements-warning");
|
||||
$warns.html(`<div class="warningmessagesmall">${warning}</div>`);
|
||||
}
|
||||
});
|
||||
return this;
|
||||
},
|
||||
},
|
||||
|
||||
/** Handle drill down by hiding this panels list and controls and showing the sub-panel */
|
||||
_expandDrilldownPanel : function( drilldown ){
|
||||
this.panelStack.push( drilldown );
|
||||
// hide this panel's controls and list, set the name for back navigation, and attach to the $el
|
||||
this.$( '> .controls' ).add( this.$list() ).hide();
|
||||
drilldown.parentName = this.model.get( 'name' );
|
||||
this.$el.append( drilldown.render().$el );
|
||||
},
|
||||
// ------------------------------------------------------------------------ sub-views
|
||||
/** In this override, use model.getVisibleContents */
|
||||
_filterCollection: function() {
|
||||
//TODO: should *not* be model.getVisibleContents - visibility is not model related
|
||||
return this.model.getVisibleContents();
|
||||
},
|
||||
|
||||
/** Handle drilldown close by freeing the panel and re-rendering this panel */
|
||||
_collapseDrilldownPanel : function( drilldown ){
|
||||
this.panelStack.pop();
|
||||
this.render();
|
||||
},
|
||||
/** override to return proper view class based on element_type */
|
||||
_getItemViewClass: function(model) {
|
||||
//this.debug( this + '._getItemViewClass:', model );
|
||||
//TODO: subclasses use DCEViewClass - but are currently unused - decide
|
||||
switch (model.get("element_type")) {
|
||||
case "hda":
|
||||
return this.DatasetDCEViewClass;
|
||||
case "dataset_collection":
|
||||
return this.getNestedDCDCEViewClass();
|
||||
}
|
||||
throw new TypeError("Unknown element type:", model.get("element_type"));
|
||||
},
|
||||
|
||||
// ------------------------------------------------------------------------ panel events
|
||||
/** event map */
|
||||
events : {
|
||||
'click .navigation .back' : 'close'
|
||||
},
|
||||
/** override to add link target and anon */
|
||||
_getItemViewOptions: function(model) {
|
||||
var options = _super.prototype._getItemViewOptions.call(this, model);
|
||||
return _.extend(options, {
|
||||
linkTarget: this.linkTarget,
|
||||
hasUser: this.hasUser,
|
||||
//TODO: could move to only nested: list:paired
|
||||
foldoutStyle: this.foldoutStyle
|
||||
});
|
||||
},
|
||||
|
||||
/** close/remove this collection panel */
|
||||
close : function( event ){
|
||||
this.remove();
|
||||
this.trigger( 'close' );
|
||||
},
|
||||
// ------------------------------------------------------------------------ collection sub-views
|
||||
/** In this override, add/remove expanded/collapsed model ids to/from web storage */
|
||||
_setUpItemViewListeners: function(view) {
|
||||
var panel = this;
|
||||
_super.prototype._setUpItemViewListeners.call(panel, view);
|
||||
|
||||
// ........................................................................ misc
|
||||
/** string rep */
|
||||
toString : function(){
|
||||
return 'CollectionView(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';
|
||||
// use pub-sub to: handle drilldown expansion and collapse
|
||||
panel.listenTo(view, {
|
||||
"expanded:drilldown": function(v, drilldown) {
|
||||
this._expandDrilldownPanel(drilldown);
|
||||
},
|
||||
"collapsed:drilldown": function(v, drilldown) {
|
||||
this._collapseDrilldownPanel(drilldown);
|
||||
}
|
||||
});
|
||||
return this;
|
||||
},
|
||||
|
||||
/** Handle drill down by hiding this panels list and controls and showing the sub-panel */
|
||||
_expandDrilldownPanel: function(drilldown) {
|
||||
this.panelStack.push(drilldown);
|
||||
// hide this panel's controls and list, set the name for back navigation, and attach to the $el
|
||||
this.$("> .controls")
|
||||
.add(this.$list())
|
||||
.hide();
|
||||
drilldown.parentName = this.model.get("name");
|
||||
this.$el.append(drilldown.render().$el);
|
||||
},
|
||||
|
||||
/** Handle drilldown close by freeing the panel and re-rendering this panel */
|
||||
_collapseDrilldownPanel: function(drilldown) {
|
||||
this.panelStack.pop();
|
||||
this.render();
|
||||
},
|
||||
|
||||
// ------------------------------------------------------------------------ panel events
|
||||
/** event map */
|
||||
events: {
|
||||
"click .navigation .back": "close"
|
||||
},
|
||||
|
||||
/** close/remove this collection panel */
|
||||
close: function(event) {
|
||||
this.remove();
|
||||
this.trigger("close");
|
||||
},
|
||||
|
||||
// ........................................................................ misc
|
||||
/** string rep */
|
||||
toString: function() {
|
||||
return `CollectionView(${this.model ? this.model.get("name") : ""})`;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
);
|
||||
|
||||
//------------------------------------------------------------------------------ TEMPLATES
|
||||
CollectionView.prototype.templates = (function(){
|
||||
CollectionView.prototype.templates = (() => {
|
||||
var controlsTemplate = (collection, view) => {
|
||||
var subtitle = collectionDescription(view.model);
|
||||
return `
|
||||
<div class="controls">
|
||||
<div class="navigation">
|
||||
<a class="back" href="javascript:void(0)">
|
||||
<span class="fa fa-icon fa-angle-left"></span>
|
||||
${_l("Back to ")}
|
||||
${_.escape(view.parentName)}
|
||||
</a>
|
||||
</div>
|
||||
<div class="title">
|
||||
<div class="name">${_.escape(collection.name) || _.escape(collection.element_identifier)}</div>
|
||||
<div class="subtitle">
|
||||
${subtitle}
|
||||
</div>
|
||||
</div>
|
||||
<div class="elements-warning">
|
||||
</div>
|
||||
<div class="tags-display"></div>
|
||||
<div class="actions">
|
||||
<a class="download-btn icon-btn" href="${view.downloadUrl}"
|
||||
title="" download="" data-original-title="Download Collection">
|
||||
<span class="fa fa-floppy-o"></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>`;
|
||||
};
|
||||
|
||||
var controlsTemplate = BASE_MVC.wrapTemplate([
|
||||
'<div class="controls">',
|
||||
'<div class="navigation">',
|
||||
'<a class="back" href="javascript:void(0)">',
|
||||
'<span class="fa fa-icon fa-angle-left"></span>',
|
||||
_l( 'Back to ' ), '<%- view.parentName %>',
|
||||
'</a>',
|
||||
'</div>',
|
||||
|
||||
'<div class="title">',
|
||||
'<div class="name"><%- collection.name || collection.element_identifier %></div>',
|
||||
'<div class="subtitle">',
|
||||
'<% if( collection.collection_type === "list" ){ %>',
|
||||
_l( 'a list of datasets' ),
|
||||
'<% } else if( collection.collection_type === "paired" ){ %>',
|
||||
_l( 'a pair of datasets' ),
|
||||
'<% } else if( collection.collection_type === "list:paired" ){ %>',
|
||||
_l( 'a list of paired datasets' ),
|
||||
'<% } else if( collection.collection_type === "list:list" ){ %>',
|
||||
_l( 'a list of dataset lists' ),
|
||||
'<% } %>',
|
||||
'</div>',
|
||||
'</div>',
|
||||
|
||||
'<div class="tags-display"></div>',
|
||||
|
||||
'<div class="actions">',
|
||||
'<a class="download-btn icon-btn" ',
|
||||
'href="<%- view.downloadUrl %>', '" title="" download="" data-original-title="Download Collection">',
|
||||
'<span class="fa fa-floppy-o"></span>',
|
||||
'</a>',
|
||||
'</div>',
|
||||
'</div>',
|
||||
], 'collection' );
|
||||
|
||||
return _.extend( _.clone( _super.prototype.templates ), {
|
||||
controls : controlsTemplate
|
||||
return _.extend(_.clone(_super.prototype.templates), {
|
||||
controls: controlsTemplate
|
||||
});
|
||||
}());
|
||||
})();
|
||||
|
||||
|
||||
|
||||
// =============================================================================
|
||||
/** @class non-editable, read-only View/Controller for a dataset collection. */
|
||||
var ListCollectionView = CollectionView.extend(
|
||||
/** @lends ListCollectionView.prototype */{
|
||||
|
||||
//TODO: not strictly needed - due to switch in CollectionView._getContentClass
|
||||
/** sub view class used for datasets */
|
||||
DatasetDCEViewClass : DC_LI.DatasetDCEListItemView,
|
||||
|
||||
// ........................................................................ misc
|
||||
/** string rep */
|
||||
toString : function(){
|
||||
return 'ListCollectionView(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';
|
||||
function collectionTypeDescription(collection) {
|
||||
var collectionType = collection.get("collection_type");
|
||||
var collectionTypeDescription;
|
||||
if (collectionType == "list") {
|
||||
collectionTypeDescription = _l("list");
|
||||
} else if (collectionType == "paired") {
|
||||
collectionTypeDescription = _l("dataset pair");
|
||||
} else if (collectionType == "list:paired") {
|
||||
collectionTypeDescription = _l("list of pairs");
|
||||
} else {
|
||||
collectionTypeDescription = _l("nested list");
|
||||
}
|
||||
});
|
||||
return collectionTypeDescription;
|
||||
}
|
||||
|
||||
function collectionDescription(collection) {
|
||||
var elementCount = collection.get("element_count");
|
||||
|
||||
// =============================================================================
|
||||
/** @class non-editable, read-only View/Controller for a dataset collection. */
|
||||
var PairCollectionView = ListCollectionView.extend(
|
||||
/** @lends PairCollectionView.prototype */{
|
||||
|
||||
// ........................................................................ misc
|
||||
/** string rep */
|
||||
toString : function(){
|
||||
return 'PairCollectionView(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';
|
||||
var itemsDescription = `a ${collectionTypeDescription(collection)}`;
|
||||
if (elementCount) {
|
||||
var countDescription;
|
||||
if (elementCount == 1) {
|
||||
countDescription = "with 1 item";
|
||||
} else if (elementCount) {
|
||||
countDescription = `with ${elementCount} items`;
|
||||
}
|
||||
itemsDescription = `${itemsDescription} ${_l(countDescription)}`;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// =============================================================================
|
||||
/** @class non-editable, read-only View/Controller for a dataset collection. */
|
||||
var ListOfPairsCollectionView = CollectionView.extend(
|
||||
/** @lends ListOfPairsCollectionView.prototype */{
|
||||
|
||||
//TODO: not strictly needed - due to switch in CollectionView._getContentClass
|
||||
/** sub view class used for nested collections */
|
||||
NestedDCDCEViewClass : DC_LI.NestedDCDCEListItemView.extend({
|
||||
foldoutPanelClass : PairCollectionView
|
||||
}),
|
||||
|
||||
// ........................................................................ misc
|
||||
/** string rep */
|
||||
toString : function(){
|
||||
return 'ListOfPairsCollectionView(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// =============================================================================
|
||||
/** @class non-editable, read-only View/Controller for a list of lists dataset collection. */
|
||||
var ListOfListsCollectionView = CollectionView.extend({
|
||||
|
||||
/** sub view class used for nested collections */
|
||||
NestedDCDCEViewClass : DC_LI.NestedDCDCEListItemView.extend({
|
||||
foldoutPanelClass : PairCollectionView
|
||||
}),
|
||||
|
||||
/** string rep */
|
||||
toString : function(){
|
||||
return 'ListOfListsCollectionView(' + (( this.model )?( this.model.get( 'name' )):( '' )) + ')';
|
||||
}
|
||||
});
|
||||
|
||||
return itemsDescription;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
return {
|
||||
CollectionView : CollectionView,
|
||||
ListCollectionView : ListCollectionView,
|
||||
PairCollectionView : PairCollectionView,
|
||||
ListOfPairsCollectionView : ListOfPairsCollectionView,
|
||||
ListOfListsCollectionView : ListOfListsCollectionView
|
||||
};
|
||||
});
|
||||
export default {
|
||||
collectionTypeDescription: collectionTypeDescription,
|
||||
collectionDescription: collectionDescription,
|
||||
CollectionView: CollectionView
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,13 +1,9 @@
|
||||
define([
|
||||
"mvc/collection/list-collection-creator",
|
||||
"mvc/history/hdca-model",
|
||||
"mvc/base-mvc",
|
||||
"utils/localization"
|
||||
], function( LIST_CREATOR, HDCA, BASE_MVC, _l ){
|
||||
import LIST_CREATOR from "mvc/collection/list-collection-creator";
|
||||
import HDCA from "mvc/history/hdca-model";
|
||||
import BASE_MVC from "mvc/base-mvc";
|
||||
import _l from "utils/localization";
|
||||
|
||||
'use strict';
|
||||
|
||||
var logNamespace = 'collections';
|
||||
var logNamespace = "collections";
|
||||
/*==============================================================================
|
||||
TODO:
|
||||
the paired creator doesn't really mesh with the list creator as parent
|
||||
@@ -17,121 +13,125 @@ TODO:
|
||||
|
||||
==============================================================================*/
|
||||
/** */
|
||||
var PairedDatasetCollectionElementView = Backbone.View.extend( BASE_MVC.LoggableMixin ).extend({
|
||||
_logNamespace : logNamespace,
|
||||
var PairedDatasetCollectionElementView = Backbone.View.extend(BASE_MVC.LoggableMixin).extend({
|
||||
_logNamespace: logNamespace,
|
||||
|
||||
//TODO: use proper class (DatasetDCE or NestedDCDCE (or the union of both))
|
||||
tagName : 'li',
|
||||
className : 'collection-element',
|
||||
//TODO: use proper class (DatasetDCE or NestedDCDCE (or the union of both))
|
||||
tagName: "li",
|
||||
className: "collection-element",
|
||||
|
||||
initialize : function( attributes ){
|
||||
initialize: function(attributes) {
|
||||
this.element = attributes.element || {};
|
||||
this.identifier = attributes.identifier;
|
||||
},
|
||||
|
||||
render : function(){
|
||||
this.$el
|
||||
.attr( 'data-element-id', this.element.id )
|
||||
.html( this.template({ identifier: this.identifier, element: this.element }) );
|
||||
render: function() {
|
||||
this.$el.attr("data-element-id", this.element.id).html(
|
||||
this.template({
|
||||
identifier: this.identifier,
|
||||
element: this.element
|
||||
})
|
||||
);
|
||||
return this;
|
||||
},
|
||||
|
||||
//TODO: lots of unused space in the element - possibly load details and display them horiz.
|
||||
template : _.template([
|
||||
'<span class="identifier"><%- identifier %></span>',
|
||||
'<span class="name"><%- element.name %></span>',
|
||||
].join('')),
|
||||
template: _.template(
|
||||
['<span class="identifier"><%- identifier %></span>', '<span class="name"><%- element.name %></span>'].join("")
|
||||
),
|
||||
|
||||
/** remove the DOM and any listeners */
|
||||
destroy : function(){
|
||||
destroy: function() {
|
||||
this.off();
|
||||
this.$el.remove();
|
||||
},
|
||||
|
||||
/** string rep */
|
||||
toString : function(){
|
||||
return 'DatasetCollectionElementView()';
|
||||
toString: function() {
|
||||
return "DatasetCollectionElementView()";
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// ============================================================================
|
||||
var _super = LIST_CREATOR.ListCollectionCreator;
|
||||
|
||||
/** An interface for building collections.
|
||||
*/
|
||||
var PairCollectionCreator = _super.extend({
|
||||
|
||||
/** the class used to display individual elements */
|
||||
elementViewClass : PairedDatasetCollectionElementView,
|
||||
elementViewClass: PairedDatasetCollectionElementView,
|
||||
/** the class this creator will create and save */
|
||||
collectionClass : HDCA.HistoryPairDatasetCollection,
|
||||
className : 'pair-collection-creator collection-creator flex-row-container',
|
||||
collectionClass: HDCA.HistoryDatasetCollection,
|
||||
className: "pair-collection-creator collection-creator flex-row-container",
|
||||
|
||||
/** override to no-op */
|
||||
_mangleDuplicateNames : function(){},
|
||||
_mangleDuplicateNames: function() {},
|
||||
|
||||
// TODO: this whole pattern sucks. There needs to be two classes of problem area:
|
||||
// bad inital choices and
|
||||
// when the user has painted his/her self into a corner during creation/use-of-the-creator
|
||||
/** render the entire interface */
|
||||
render : function( speed, callback ){
|
||||
if( this.workingElements.length === 2 ){
|
||||
return _super.prototype.render.call( this, speed, callback );
|
||||
render: function(speed, callback) {
|
||||
if (this.workingElements.length === 2) {
|
||||
return _super.prototype.render.call(this, speed, callback);
|
||||
}
|
||||
return this._renderInvalid( speed, callback );
|
||||
return this._renderInvalid(speed, callback);
|
||||
},
|
||||
|
||||
// ------------------------------------------------------------------------ rendering elements
|
||||
/** render forward/reverse */
|
||||
_renderList : function( speed, callback ){
|
||||
_renderList: function(speed, callback) {
|
||||
//this.debug( '-- _renderList' );
|
||||
//precondition: there are two valid elements in workingElements
|
||||
var creator = this,
|
||||
$tmp = jQuery( '<div/>' ),
|
||||
$list = creator.$list();
|
||||
var creator = this;
|
||||
|
||||
var $tmp = jQuery("<div/>");
|
||||
var $list = creator.$list();
|
||||
|
||||
// lose the original views, create the new, append all at once, then call their renders
|
||||
_.each( this.elementViews, function( view ){
|
||||
_.each(this.elementViews, view => {
|
||||
view.destroy();
|
||||
creator.removeElementView( view );
|
||||
creator.removeElementView(view);
|
||||
});
|
||||
$tmp.append( creator._createForwardElementView().$el );
|
||||
$tmp.append( creator._createReverseElementView().$el );
|
||||
$list.empty().append( $tmp.children() );
|
||||
_.invoke( creator.elementViews, 'render' );
|
||||
$tmp.append(creator._createForwardElementView().$el);
|
||||
$tmp.append(creator._createReverseElementView().$el);
|
||||
$list.empty().append($tmp.children());
|
||||
_.invoke(creator.elementViews, "render");
|
||||
},
|
||||
|
||||
/** create the forward element view */
|
||||
_createForwardElementView : function(){
|
||||
return this._createElementView( this.workingElements[0], { identifier: 'forward' } );
|
||||
_createForwardElementView: function() {
|
||||
return this._createElementView(this.workingElements[0], {
|
||||
identifier: "forward"
|
||||
});
|
||||
},
|
||||
|
||||
/** create the forward element view */
|
||||
_createReverseElementView : function(){
|
||||
return this._createElementView( this.workingElements[1], { identifier: 'reverse' } );
|
||||
_createReverseElementView: function() {
|
||||
return this._createElementView(this.workingElements[1], {
|
||||
identifier: "reverse"
|
||||
});
|
||||
},
|
||||
|
||||
/** create an element view, cache in elementViews, and return */
|
||||
_createElementView : function( element, options ){
|
||||
var elementView = new this.elementViewClass( _.extend( options, {
|
||||
element : element,
|
||||
}));
|
||||
this.elementViews.push( elementView );
|
||||
_createElementView: function(element, options) {
|
||||
var elementView = new this.elementViewClass(
|
||||
_.extend(options, {
|
||||
element: element
|
||||
})
|
||||
);
|
||||
this.elementViews.push(elementView);
|
||||
return elementView;
|
||||
},
|
||||
|
||||
/** swap the forward, reverse elements and re-render */
|
||||
swap : function(){
|
||||
this.workingElements = [
|
||||
this.workingElements[1],
|
||||
this.workingElements[0],
|
||||
];
|
||||
swap: function() {
|
||||
this.workingElements = [this.workingElements[1], this.workingElements[0]];
|
||||
this._renderList();
|
||||
},
|
||||
|
||||
events : _.extend( _.clone( _super.prototype.events ), {
|
||||
'click .swap' : 'swap',
|
||||
events: _.extend(_.clone(_super.prototype.events), {
|
||||
"click .swap": "swap"
|
||||
}),
|
||||
|
||||
// ------------------------------------------------------------------------ templates
|
||||
@@ -139,116 +139,145 @@ var PairCollectionCreator = _super.extend({
|
||||
//TODO: underscore currently unnecc. bc no vars are used
|
||||
//TODO: better way of localizing text-nodes in long strings
|
||||
/** underscore template fns attached to class */
|
||||
templates : _.extend( _.clone( _super.prototype.templates ), {
|
||||
templates: _.extend(_.clone(_super.prototype.templates), {
|
||||
/** the middle: element list */
|
||||
middle : _.template([
|
||||
'<div class="collection-elements-controls">',
|
||||
'<a class="swap" href="javascript:void(0);" title="', _l( 'Swap forward and reverse datasets' ), '">',
|
||||
_l( 'Swap' ),
|
||||
'</a>',
|
||||
'</div>',
|
||||
'<div class="collection-elements scroll-container flex-row">',
|
||||
'</div>'
|
||||
].join('')),
|
||||
middle: _.template(
|
||||
[
|
||||
'<div class="collection-elements-controls">',
|
||||
'<a class="swap" href="javascript:void(0);" title="',
|
||||
_l("Swap forward and reverse datasets"),
|
||||
'">',
|
||||
_l("Swap"),
|
||||
"</a>",
|
||||
"</div>",
|
||||
'<div class="collection-elements scroll-container flex-row">',
|
||||
"</div>"
|
||||
].join("")
|
||||
),
|
||||
|
||||
/** help content */
|
||||
helpContent : _.template([
|
||||
'<p>', _l([
|
||||
'Pair collections are permanent collections containing two datasets: one forward and one reverse. ',
|
||||
'Often these are forward and reverse reads. The pair collections can be passed to tools and ',
|
||||
'workflows in order to have analyses done on both datasets. This interface allows ',
|
||||
'you to create a pair, name it, and swap which is forward and which reverse.'
|
||||
].join( '' )), '</p>',
|
||||
'<ul>',
|
||||
'<li>', _l([
|
||||
'Click the <i data-target=".swap">"Swap"</i> link to make your forward dataset the reverse ',
|
||||
'and the reverse dataset forward.'
|
||||
].join( '' )), '</li>',
|
||||
'<li>', _l([
|
||||
'Click the <i data-target=".cancel-create">"Cancel"</i> button to exit the interface.'
|
||||
].join( '' )), '</li>',
|
||||
'</ul><br />',
|
||||
'<p>', _l([
|
||||
'Once your collection is complete, enter a <i data-target=".collection-name">name</i> and ',
|
||||
'click <i data-target=".create-collection">"Create list"</i>.'
|
||||
].join( '' )), '</p>'
|
||||
].join('')),
|
||||
helpContent: _.template(
|
||||
[
|
||||
"<p>",
|
||||
_l(
|
||||
[
|
||||
"Pair collections are permanent collections containing two datasets: one forward and one reverse. ",
|
||||
"Often these are forward and reverse reads. The pair collections can be passed to tools and ",
|
||||
"workflows in order to have analyses done on both datasets. This interface allows ",
|
||||
"you to create a pair, name it, and swap which is forward and which reverse."
|
||||
].join("")
|
||||
),
|
||||
"</p>",
|
||||
"<ul>",
|
||||
"<li>",
|
||||
_l(
|
||||
[
|
||||
'Click the <i data-target=".swap">"Swap"</i> link to make your forward dataset the reverse ',
|
||||
"and the reverse dataset forward."
|
||||
].join("")
|
||||
),
|
||||
"</li>",
|
||||
"<li>",
|
||||
_l(['Click the <i data-target=".cancel-create">"Cancel"</i> button to exit the interface.'].join("")),
|
||||
"</li>",
|
||||
"</ul><br />",
|
||||
"<p>",
|
||||
_l(
|
||||
[
|
||||
'Once your collection is complete, enter a <i data-target=".collection-name">name</i> and ',
|
||||
'click <i data-target=".create-collection">"Create list"</i>.'
|
||||
].join("")
|
||||
),
|
||||
"</p>"
|
||||
].join("")
|
||||
),
|
||||
|
||||
/** a simplified page communicating what went wrong and why the user needs to reselect something else */
|
||||
invalidInitial : _.template([
|
||||
'<div class="header flex-row no-flex">',
|
||||
invalidInitial: _.template(
|
||||
[
|
||||
'<div class="header flex-row no-flex">',
|
||||
'<div class="alert alert-warning" style="display: block">',
|
||||
'<span class="alert-message">',
|
||||
'<% if( _.size( problems ) ){ %>',
|
||||
_l( 'The following selections could not be included due to problems' ),
|
||||
'<ul><% _.each( problems, function( problem ){ %>',
|
||||
'<li><b><%- problem.element.name %></b>: <%- problem.text %></li>',
|
||||
'<% }); %></ul>',
|
||||
'<% } else if( _.size( elements ) === 0 ){ %>',
|
||||
_l( 'No datasets were selected' ), '.',
|
||||
'<% } else if( _.size( elements ) === 1 ){ %>',
|
||||
_l( 'Only one dataset was selected' ), ': <%- elements[0].name %>',
|
||||
'<% } else if( _.size( elements ) > 2 ){ %>',
|
||||
_l( 'Too many datasets were selected' ),
|
||||
': <%- _.pluck( elements, "name" ).join( ", ") %>',
|
||||
'<% } %>',
|
||||
'<br />',
|
||||
_l( 'Two (and only two) elements are needed for the pair' ), '. ',
|
||||
_l( 'You may need to ' ),
|
||||
'<a class="cancel-create" href="javascript:void(0)">', _l( 'cancel' ), '</a> ',
|
||||
_l( 'and reselect new elements' ), '.',
|
||||
'</span>',
|
||||
'</div>',
|
||||
'</div>',
|
||||
'<div class="footer flex-row no-flex">',
|
||||
'<span class="alert-message">',
|
||||
"<% if( _.size( problems ) ){ %>",
|
||||
_l("The following selections could not be included due to problems"),
|
||||
"<ul><% _.each( problems, function( problem ){ %>",
|
||||
"<li><b><%- problem.element.name %></b>: <%- problem.text %></li>",
|
||||
"<% }); %></ul>",
|
||||
"<% } else if( _.size( elements ) === 0 ){ %>",
|
||||
_l("No datasets were selected"),
|
||||
".",
|
||||
"<% } else if( _.size( elements ) === 1 ){ %>",
|
||||
_l("Only one dataset was selected"),
|
||||
": <%- elements[0].name %>",
|
||||
"<% } else if( _.size( elements ) > 2 ){ %>",
|
||||
_l("Too many datasets were selected"),
|
||||
': <%- _.pluck( elements, "name" ).join( ", ") %>',
|
||||
"<% } %>",
|
||||
"<br />",
|
||||
_l("Two (and only two) elements are needed for the pair"),
|
||||
". ",
|
||||
_l("You may need to "),
|
||||
'<a class="cancel-create" href="javascript:void(0)">',
|
||||
_l("cancel"),
|
||||
"</a> ",
|
||||
_l("and reselect new elements"),
|
||||
".",
|
||||
"</span>",
|
||||
"</div>",
|
||||
"</div>",
|
||||
'<div class="footer flex-row no-flex">',
|
||||
'<div class="actions clear vertically-spaced">',
|
||||
'<div class="other-options pull-left">',
|
||||
'<button class="cancel-create btn" tabindex="-1">', _l( 'Cancel' ), '</button>',
|
||||
// _l( 'Create a different kind of collection' ),
|
||||
'</div>',
|
||||
'</div>',
|
||||
'</div>'
|
||||
].join('')),
|
||||
'<div class="other-options pull-left">',
|
||||
'<button class="cancel-create btn" tabindex="-1">',
|
||||
_l("Cancel"),
|
||||
"</button>",
|
||||
// _l( 'Create a different kind of collection' ),
|
||||
"</div>",
|
||||
"</div>",
|
||||
"</div>"
|
||||
].join("")
|
||||
)
|
||||
}),
|
||||
|
||||
// ------------------------------------------------------------------------ misc
|
||||
/** string rep */
|
||||
toString : function(){ return 'PairCollectionCreator'; }
|
||||
toString: function() {
|
||||
return "PairCollectionCreator";
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** List collection flavor of collectionCreatorModal. */
|
||||
var pairCollectionCreatorModal = function _pairCollectionCreatorModal( elements, options ){
|
||||
var pairCollectionCreatorModal = function _pairCollectionCreatorModal(elements, options) {
|
||||
options = options || {};
|
||||
options.title = _l( 'Create a collection from a pair of datasets' );
|
||||
return LIST_CREATOR.collectionCreatorModal( elements, options, PairCollectionCreator );
|
||||
options.title = _l("Create a collection from a pair of datasets");
|
||||
return LIST_CREATOR.collectionCreatorModal(elements, options, PairCollectionCreator);
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** Use a modal to create a pair collection, then add it to the given history contents.
|
||||
* @returns {Deferred} resolved when the collection is added to the history.
|
||||
*/
|
||||
function createPairCollection( contents, defaultHideSourceItems ){
|
||||
var elements = contents.toJSON(),
|
||||
promise = pairCollectionCreatorModal( elements, {
|
||||
defaultHideSourceItems: defaultHideSourceItems,
|
||||
creationFn : function( elements, name, hideSourceItems ){
|
||||
elements = [
|
||||
{ name: "forward", src: "hda", id: elements[0].id },
|
||||
{ name: "reverse", src: "hda", id: elements[1].id }
|
||||
];
|
||||
return contents.createHDCA( elements, 'paired', name, hideSourceItems );
|
||||
}
|
||||
});
|
||||
function createPairCollection(contents, defaultHideSourceItems) {
|
||||
var elements = contents.toJSON();
|
||||
|
||||
var promise = pairCollectionCreatorModal(elements, {
|
||||
defaultHideSourceItems: defaultHideSourceItems,
|
||||
creationFn: function(elements, name, hideSourceItems) {
|
||||
elements = [
|
||||
{ name: "forward", src: "hda", id: elements[0].id },
|
||||
{ name: "reverse", src: "hda", id: elements[1].id }
|
||||
];
|
||||
return contents.createHDCA(elements, "paired", name, hideSourceItems);
|
||||
}
|
||||
});
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
return {
|
||||
PairCollectionCreator : PairCollectionCreator,
|
||||
pairCollectionCreatorModal : pairCollectionCreatorModal,
|
||||
createPairCollection : createPairCollection,
|
||||
};
|
||||
});
|
||||
export default {
|
||||
PairCollectionCreator: PairCollectionCreator,
|
||||
pairCollectionCreatorModal: pairCollectionCreatorModal,
|
||||
createPairCollection: createPairCollection
|
||||
};
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import _l from "utils/localization";
|
||||
// Additional dependencies: jQuery, underscore.
|
||||
define(['mvc/ui/ui-modal', 'mvc/ui/ui-frames', 'mvc/ui/icon-button'], function(Modal, Frames, mod_icon_btn) {
|
||||
|
||||
import Modal from "mvc/ui/ui-modal";
|
||||
import Frames from "mvc/ui/ui-frames";
|
||||
import mod_icon_btn from "mvc/ui/icon-button";
|
||||
/**
|
||||
* Dataset metedata.
|
||||
*/
|
||||
@@ -12,39 +14,43 @@ var DatasetMetadata = Backbone.Model.extend({});
|
||||
*/
|
||||
var Dataset = Backbone.Model.extend({
|
||||
defaults: {
|
||||
id: '',
|
||||
type: '',
|
||||
name: '',
|
||||
hda_ldda: 'hda',
|
||||
id: "",
|
||||
type: "",
|
||||
name: "",
|
||||
hda_ldda: "hda",
|
||||
metadata: null
|
||||
},
|
||||
|
||||
initialize: function() {
|
||||
// Metadata can be passed in as a model or a set of attributes; if it's
|
||||
// already a model, there's no need to set metadata.
|
||||
if (!this.get('metadata')) {
|
||||
if (!this.get("metadata")) {
|
||||
this._set_metadata();
|
||||
}
|
||||
|
||||
// Update metadata on change.
|
||||
this.on('change', this._set_metadata, this);
|
||||
this.on("change", this._set_metadata, this);
|
||||
},
|
||||
|
||||
_set_metadata: function() {
|
||||
var metadata = new DatasetMetadata();
|
||||
|
||||
// Move metadata from dataset attributes to metadata object.
|
||||
_.each(_.keys(this.attributes), function(k) {
|
||||
if (k.indexOf('metadata_') === 0) {
|
||||
// Found metadata.
|
||||
var new_key = k.split('metadata_')[1];
|
||||
metadata.set(new_key, this.attributes[k]);
|
||||
delete this.attributes[k];
|
||||
}
|
||||
}, this);
|
||||
_.each(
|
||||
_.keys(this.attributes),
|
||||
function(k) {
|
||||
if (k.indexOf("metadata_") === 0) {
|
||||
// Found metadata.
|
||||
var new_key = k.split("metadata_")[1];
|
||||
metadata.set(new_key, this.attributes[k]);
|
||||
delete this.attributes[k];
|
||||
}
|
||||
},
|
||||
this
|
||||
);
|
||||
|
||||
// Because this is an internal change, silence it.
|
||||
this.set('metadata', metadata, { 'silent': true });
|
||||
this.set("metadata", metadata, { silent: true });
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -54,7 +60,7 @@ var Dataset = Backbone.Model.extend({
|
||||
return this.attributes.metadata.get(attribute);
|
||||
},
|
||||
|
||||
urlRoot: Galaxy.root + "api/datasets"
|
||||
urlRoot: `${Galaxy.root}api/datasets`
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -72,11 +78,11 @@ var TabularDataset = Dataset.extend({
|
||||
Dataset.prototype.initialize.call(this);
|
||||
|
||||
// If first data chunk is available, next chunk is 1.
|
||||
if (this.attributes.first_data_chunk){
|
||||
if (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';
|
||||
this.attributes.chunk_url = `${Galaxy.root}dataset/display?dataset_id=${this.id}`;
|
||||
this.attributes.url_viz = `${Galaxy.root}visualization`;
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -89,18 +95,18 @@ var TabularDataset = Dataset.extend({
|
||||
}
|
||||
|
||||
// Get next chunk.
|
||||
var self = this,
|
||||
next_chunk = $.Deferred();
|
||||
var self = this;
|
||||
|
||||
var next_chunk = $.Deferred();
|
||||
$.getJSON(this.attributes.chunk_url, {
|
||||
offset: self.attributes.offset
|
||||
}).success(function(chunk) {
|
||||
}).success(chunk => {
|
||||
var rval;
|
||||
if (chunk.ck_data !== '') {
|
||||
if (chunk.ck_data !== "") {
|
||||
// Found chunk.
|
||||
rval = chunk;
|
||||
self.attributes.offset = chunk.offset;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// At EOF.
|
||||
self.attributes.at_eof = true;
|
||||
rval = null;
|
||||
@@ -122,7 +128,6 @@ var DatasetCollection = Backbone.Collection.extend({
|
||||
* or EmbeddedTabularDatasetChunkedView.
|
||||
*/
|
||||
var TabularDatasetChunkedView = Backbone.View.extend({
|
||||
|
||||
/**
|
||||
* Initialize view and, importantly, set a scroll element.
|
||||
*/
|
||||
@@ -133,23 +138,23 @@ var TabularDatasetChunkedView = Backbone.View.extend({
|
||||
|
||||
// load trackster button
|
||||
new TabularButtonTracksterView({
|
||||
model : options.model,
|
||||
$el : this.$el
|
||||
model: options.model,
|
||||
$el: this.$el
|
||||
});
|
||||
},
|
||||
|
||||
expand_to_container: function(){
|
||||
if (this.$el.height() < this.scroll_elt.height()){
|
||||
expand_to_container: function() {
|
||||
if (this.$el.height() < this.scroll_elt.height()) {
|
||||
this.attempt_to_fetch();
|
||||
}
|
||||
},
|
||||
|
||||
attempt_to_fetch: function( func ){
|
||||
attempt_to_fetch: function(func) {
|
||||
var self = this;
|
||||
if ( !this.loading_chunk && this.scrolled_to_bottom() ) {
|
||||
if (!this.loading_chunk && this.scrolled_to_bottom()) {
|
||||
this.loading_chunk = true;
|
||||
this.loading_indicator.show();
|
||||
$.when(self.model.get_next_chunk()).then(function(result) {
|
||||
$.when(self.model.get_next_chunk()).then(result => {
|
||||
if (result) {
|
||||
self._renderChunk(result);
|
||||
self.loading_chunk = false;
|
||||
@@ -162,36 +167,36 @@ var TabularDatasetChunkedView = Backbone.View.extend({
|
||||
|
||||
render: function() {
|
||||
// Add loading indicator.
|
||||
this.loading_indicator = $('<div/>').attr('id', 'loading_indicator');
|
||||
this.loading_indicator = $("<div/>").attr("id", "loading_indicator");
|
||||
this.$el.append(this.loading_indicator);
|
||||
|
||||
// Add data table and header.
|
||||
var data_table = $('<table/>').attr({
|
||||
id: 'content_table',
|
||||
var data_table = $("<table/>").attr({
|
||||
id: "content_table",
|
||||
cellpadding: 0
|
||||
});
|
||||
this.$el.append(data_table);
|
||||
var column_names = this.model.get_metadata('column_names'),
|
||||
header_container = $('<thead/>').appendTo(data_table),
|
||||
header_row = $('<tr/>').appendTo(header_container);
|
||||
var column_names = this.model.get_metadata("column_names");
|
||||
var header_container = $("<thead/>").appendTo(data_table);
|
||||
var header_row = $("<tr/>").appendTo(header_container);
|
||||
if (column_names) {
|
||||
header_row.append('<th>' + column_names.join('</th><th>') + '</th>');
|
||||
header_row.append(`<th>${column_names.join("</th><th>")}</th>`);
|
||||
} else {
|
||||
for (var j = 1; j <= this.model.get_metadata('columns'); j++) {
|
||||
header_row.append('<th>' + j + '</th>');
|
||||
for (var j = 1; j <= this.model.get_metadata("columns"); j++) {
|
||||
header_row.append(`<th>${j}</th>`);
|
||||
}
|
||||
}
|
||||
|
||||
// Render first chunk.
|
||||
var self = this,
|
||||
first_chunk = this.model.get('first_data_chunk');
|
||||
var self = this;
|
||||
|
||||
var first_chunk = this.model.get("first_data_chunk");
|
||||
if (first_chunk) {
|
||||
// First chunk is bootstrapped, so render now.
|
||||
this._renderChunk(first_chunk);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// No bootstrapping, so get first chunk and then render.
|
||||
$.when(self.model.get_next_chunk()).then(function(result) {
|
||||
$.when(self.model.get_next_chunk()).then(result => {
|
||||
self._renderChunk(result);
|
||||
});
|
||||
}
|
||||
@@ -199,7 +204,7 @@ var TabularDatasetChunkedView = Backbone.View.extend({
|
||||
// -- Show new chunks during scrolling. --
|
||||
|
||||
// Set up chunk loading when scrolling using the scrolling element.
|
||||
this.scroll_elt.scroll(function(){
|
||||
this.scroll_elt.scroll(() => {
|
||||
self.attempt_to_fetch();
|
||||
});
|
||||
},
|
||||
@@ -214,15 +219,15 @@ var TabularDatasetChunkedView = Backbone.View.extend({
|
||||
// -- Helper functions. --
|
||||
|
||||
_renderCell: function(cell_contents, index, colspan) {
|
||||
var $cell = $('<td>').text(cell_contents);
|
||||
var column_types = this.model.get_metadata('column_types');
|
||||
var $cell = $("<td>").text(cell_contents);
|
||||
var column_types = this.model.get_metadata("column_types");
|
||||
if (colspan !== undefined) {
|
||||
$cell.attr('colspan', colspan).addClass('stringalign');
|
||||
$cell.attr("colspan", colspan).addClass("stringalign");
|
||||
} else if (column_types) {
|
||||
if (index < column_types.length) {
|
||||
if (column_types[index] === 'str' || column_types[index] === 'list') {
|
||||
if (column_types[index] === "str" || column_types[index] === "list") {
|
||||
/* Left align all str columns, right align the rest */
|
||||
$cell.addClass('stringalign');
|
||||
$cell.addClass("stringalign");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -231,39 +236,49 @@ var TabularDatasetChunkedView = Backbone.View.extend({
|
||||
|
||||
_renderRow: function(line) {
|
||||
// Check length of cells to ensure this is a complete row.
|
||||
var cells = line.split('\t'),
|
||||
row = $('<tr>'),
|
||||
num_columns = this.model.get_metadata('columns');
|
||||
var cells = line.split("\t");
|
||||
|
||||
var row = $("<tr>");
|
||||
var num_columns = this.model.get_metadata("columns");
|
||||
|
||||
if (this.row_count % 2 !== 0) {
|
||||
row.addClass('dark_row');
|
||||
row.addClass("dark_row");
|
||||
}
|
||||
|
||||
if (cells.length === num_columns) {
|
||||
_.each(cells, function(cell_contents, index) {
|
||||
row.append(this._renderCell(cell_contents, index));
|
||||
}, this);
|
||||
}
|
||||
else if (cells.length > num_columns) {
|
||||
_.each(
|
||||
cells,
|
||||
function(cell_contents, index) {
|
||||
row.append(this._renderCell(cell_contents, index));
|
||||
},
|
||||
this
|
||||
);
|
||||
} else if (cells.length > num_columns) {
|
||||
// SAM file or like format with optional metadata included.
|
||||
_.each(cells.slice(0, num_columns - 1), function(cell_contents, index) {
|
||||
row.append(this._renderCell(cell_contents, index));
|
||||
}, this);
|
||||
row.append(this._renderCell(cells.slice(num_columns - 1).join('\t'), num_columns - 1));
|
||||
}
|
||||
else if (cells.length === 1){
|
||||
_.each(
|
||||
cells.slice(0, num_columns - 1),
|
||||
function(cell_contents, index) {
|
||||
row.append(this._renderCell(cell_contents, index));
|
||||
},
|
||||
this
|
||||
);
|
||||
row.append(this._renderCell(cells.slice(num_columns - 1).join("\t"), num_columns - 1));
|
||||
} else if (cells.length === 1) {
|
||||
// Comment line, just return the one cell.
|
||||
row.append(this._renderCell(line, 0, num_columns));
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// cells.length is greater than one, but less than num_columns. Render cells and pad tds.
|
||||
// Possibly a SAM file or like format with optional metadata missing.
|
||||
// Could also be a tabular file with a line with missing columns.
|
||||
_.each(cells, function(cell_contents, index) {
|
||||
row.append(this._renderCell(cell_contents, index));
|
||||
}, this);
|
||||
_.each(_.range(num_columns - cells.length), function(){
|
||||
row.append($('<td>'));
|
||||
_.each(
|
||||
cells,
|
||||
function(cell_contents, index) {
|
||||
row.append(this._renderCell(cell_contents, index));
|
||||
},
|
||||
this
|
||||
);
|
||||
_.each(_.range(num_columns - cells.length), () => {
|
||||
row.append($("<td>"));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -272,12 +287,16 @@ var TabularDatasetChunkedView = Backbone.View.extend({
|
||||
},
|
||||
|
||||
_renderChunk: function(chunk) {
|
||||
var data_table = this.$el.find('table');
|
||||
_.each(chunk.ck_data.split('\n'), function(line, index) {
|
||||
if (line !== ''){
|
||||
data_table.append(this._renderRow(line));
|
||||
}
|
||||
}, this);
|
||||
var data_table = this.$el.find("table");
|
||||
_.each(
|
||||
chunk.ck_data.split("\n"),
|
||||
function(line, index) {
|
||||
if (line !== "") {
|
||||
data_table.append(this._renderRow(line));
|
||||
}
|
||||
},
|
||||
this
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -286,17 +305,16 @@ var TabularDatasetChunkedView = Backbone.View.extend({
|
||||
* view top-level elements outside of view.
|
||||
*/
|
||||
var TopLevelTabularDatasetChunkedView = TabularDatasetChunkedView.extend({
|
||||
|
||||
initialize: function(options) {
|
||||
TabularDatasetChunkedView.prototype.initialize.call(this, options);
|
||||
|
||||
// Scrolling happens in top-level elements.
|
||||
scroll_elt = _.find(this.$el.parents(), function(p) {
|
||||
return $(p).css('overflow') === 'auto';
|
||||
});
|
||||
var scroll_elt = _.find(this.$el.parents(), p => $(p).css("overflow") === "auto");
|
||||
|
||||
// If no scrolling element found, use window.
|
||||
if (!scroll_elt) { scroll_elt = window; }
|
||||
if (!scroll_elt) {
|
||||
scroll_elt = window;
|
||||
}
|
||||
|
||||
// Wrap scrolling element for easy access.
|
||||
this.scroll_elt = $(scroll_elt);
|
||||
@@ -306,24 +324,22 @@ var TopLevelTabularDatasetChunkedView = TabularDatasetChunkedView.extend({
|
||||
* Returns true if user has scrolled to the bottom of the view.
|
||||
*/
|
||||
scrolled_to_bottom: function() {
|
||||
return (this.$el.height() - this.scroll_elt.scrollTop() - this.scroll_elt.height() <= 0);
|
||||
return this.$el.height() - this.scroll_elt.scrollTop() - this.scroll_elt.height() <= 0;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
/**
|
||||
* Tabular view tnat is embedded in a page. Scrolling occurs in view's el.
|
||||
*/
|
||||
var EmbeddedTabularDatasetChunkedView = TabularDatasetChunkedView.extend({
|
||||
|
||||
initialize: function(options) {
|
||||
TabularDatasetChunkedView.prototype.initialize.call(this, options);
|
||||
|
||||
// Because view is embedded, set up div to do scrolling.
|
||||
this.scroll_elt = this.$el.css({
|
||||
position: 'relative',
|
||||
overflow: 'scroll',
|
||||
height: options.height || '500px'
|
||||
position: "relative",
|
||||
overflow: "scroll",
|
||||
height: options.height || "500px"
|
||||
});
|
||||
},
|
||||
|
||||
@@ -333,33 +349,31 @@ var EmbeddedTabularDatasetChunkedView = TabularDatasetChunkedView.extend({
|
||||
scrolled_to_bottom: function() {
|
||||
return this.$el.scrollTop() + this.$el.innerHeight() >= this.el.scrollHeight;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// button for trackster visualization
|
||||
/** Button for trackster visualization */
|
||||
var TabularButtonTracksterView = Backbone.View.extend({
|
||||
|
||||
// gene region columns
|
||||
col: {
|
||||
chrom : null,
|
||||
start : null,
|
||||
end : null
|
||||
chrom: null,
|
||||
start: null,
|
||||
end: null
|
||||
},
|
||||
|
||||
// url for trackster
|
||||
url_viz : null,
|
||||
url_viz: null,
|
||||
|
||||
// dataset id
|
||||
dataset_id : null,
|
||||
dataset_id: null,
|
||||
|
||||
// database key
|
||||
genome_build: null,
|
||||
|
||||
// data type
|
||||
file_ext : null,
|
||||
file_ext: null,
|
||||
|
||||
// backbone initialize
|
||||
initialize: function (options) {
|
||||
initialize: function(options) {
|
||||
// check if environment is available
|
||||
var Galaxy = parent.Galaxy;
|
||||
|
||||
@@ -379,57 +393,53 @@ var TabularButtonTracksterView = Backbone.View.extend({
|
||||
}
|
||||
|
||||
// model/metadata
|
||||
var model = options.model;
|
||||
var metadata = model.get('metadata');
|
||||
var model = options.model;
|
||||
var metadata = model.get("metadata");
|
||||
|
||||
// check for datatype
|
||||
if (!model.get('file_ext')) {
|
||||
if (!model.get("file_ext")) {
|
||||
return;
|
||||
}
|
||||
|
||||
// get data type
|
||||
this.file_ext = model.get('file_ext');
|
||||
this.file_ext = model.get("file_ext");
|
||||
|
||||
// check for bed-file format
|
||||
if (this.file_ext == 'bed')
|
||||
{
|
||||
if (this.file_ext == "bed") {
|
||||
// verify that metadata exists
|
||||
if (metadata.get('chromCol') && metadata.get('startCol') && metadata.get('endCol'))
|
||||
{
|
||||
if (metadata.get("chromCol") && metadata.get("startCol") && metadata.get("endCol")) {
|
||||
// read in columns
|
||||
this.col.chrom = metadata.get('chromCol') - 1;
|
||||
this.col.start = metadata.get('startCol') - 1;
|
||||
this.col.end = metadata.get('endCol') - 1;
|
||||
this.col.chrom = metadata.get("chromCol") - 1;
|
||||
this.col.start = metadata.get("startCol") - 1;
|
||||
this.col.end = metadata.get("endCol") - 1;
|
||||
} else {
|
||||
console.log('TabularButtonTrackster : Bed-file metadata incomplete.');
|
||||
console.log("TabularButtonTrackster : Bed-file metadata incomplete.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// check for vcf-file format
|
||||
if (this.file_ext == 'vcf')
|
||||
{
|
||||
if (this.file_ext == "vcf") {
|
||||
// search array
|
||||
function search (str, array) {
|
||||
for (var j = 0; j < array.length; j++)
|
||||
if (array[j].match(str)) return j;
|
||||
function search(str, array) {
|
||||
for (var j = 0; j < array.length; j++) if (array[j].match(str)) return j;
|
||||
return -1;
|
||||
};
|
||||
}
|
||||
|
||||
// load
|
||||
this.col.chrom = search('Chrom', metadata.get('column_names'));
|
||||
this.col.start = search('Pos', metadata.get('column_names'));
|
||||
this.col.end = null;
|
||||
this.col.chrom = search("Chrom", metadata.get("column_names"));
|
||||
this.col.start = search("Pos", metadata.get("column_names"));
|
||||
this.col.end = null;
|
||||
|
||||
// verify that metadata exists
|
||||
if (this.col.chrom == -1 || this.col.start == -1) {
|
||||
console.log('TabularButtonTrackster : VCF-file metadata incomplete.');
|
||||
console.log("TabularButtonTrackster : VCF-file metadata incomplete.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// check
|
||||
if(this.col.chrom === undefined) {
|
||||
if (this.col.chrom === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -437,29 +447,29 @@ var TabularButtonTracksterView = Backbone.View.extend({
|
||||
if (model.id) {
|
||||
this.dataset_id = model.id;
|
||||
} else {
|
||||
console.log('TabularButtonTrackster : Dataset identification is missing.');
|
||||
console.log("TabularButtonTrackster : Dataset identification is missing.");
|
||||
return;
|
||||
}
|
||||
|
||||
// get url
|
||||
if (model.get('url_viz')) {
|
||||
this.url_viz = model.get('url_viz');
|
||||
if (model.get("url_viz")) {
|
||||
this.url_viz = model.get("url_viz");
|
||||
} else {
|
||||
console.log('TabularButtonTrackster : Url for visualization controller is missing.');
|
||||
console.log("TabularButtonTrackster : Url for visualization controller is missing.");
|
||||
return;
|
||||
}
|
||||
|
||||
// get genome_build / database key
|
||||
if (model.get('genome_build')) {
|
||||
this.genome_build = model.get('genome_build');
|
||||
if (model.get("genome_build")) {
|
||||
this.genome_build = model.get("genome_build");
|
||||
}
|
||||
|
||||
// create the icon
|
||||
var btn_viz = new mod_icon_btn.IconButtonView({
|
||||
model : new mod_icon_btn.IconButton({
|
||||
title : 'Visualize',
|
||||
icon_class : 'chart_curve',
|
||||
id : 'btn_viz'
|
||||
model: new mod_icon_btn.IconButton({
|
||||
title: _l("Visualize"),
|
||||
icon_class: "chart_curve",
|
||||
id: "btn_viz"
|
||||
})
|
||||
});
|
||||
|
||||
@@ -473,147 +483,83 @@ var TabularButtonTracksterView = Backbone.View.extend({
|
||||
this.hide();
|
||||
},
|
||||
|
||||
// backbone events
|
||||
events:
|
||||
{
|
||||
'mouseover tr' : 'show',
|
||||
'mouseleave' : 'hide'
|
||||
/** Add event handlers */
|
||||
events: {
|
||||
"mouseover tr": "show",
|
||||
mouseleave: "hide"
|
||||
},
|
||||
|
||||
// show button
|
||||
show: function (e) {
|
||||
show: function(e) {
|
||||
var self = this;
|
||||
|
||||
// is numeric
|
||||
function is_numeric(n) {
|
||||
return !isNaN(parseFloat(n)) && isFinite(n);
|
||||
};
|
||||
}
|
||||
|
||||
// check
|
||||
if(this.col.chrom === null)
|
||||
return;
|
||||
if (this.col.chrom === null) return;
|
||||
|
||||
// get selected data line
|
||||
var row = $(e.target).parent();
|
||||
|
||||
// verify that location has been found
|
||||
var chrom = row.children().eq(this.col.chrom).html();
|
||||
var start = row.children().eq(this.col.start).html();
|
||||
var chrom = row
|
||||
.children()
|
||||
.eq(this.col.chrom)
|
||||
.html();
|
||||
var start = row
|
||||
.children()
|
||||
.eq(this.col.start)
|
||||
.html();
|
||||
|
||||
// end is optional
|
||||
var end = this.col.end ? row.children().eq(this.col.end).html() : start;
|
||||
var end = this.col.end
|
||||
? row
|
||||
.children()
|
||||
.eq(this.col.end)
|
||||
.html()
|
||||
: start;
|
||||
|
||||
// double check location
|
||||
if (!chrom.match("^#") && chrom !== "" && is_numeric(start)) {
|
||||
|
||||
// get target gene region
|
||||
var btn_viz_pars = {
|
||||
dataset_id : this.dataset_id,
|
||||
gene_region : chrom + ":" + start + "-" + end
|
||||
dataset_id: this.dataset_id,
|
||||
gene_region: `${chrom}:${start}-${end}`
|
||||
};
|
||||
|
||||
// get button position
|
||||
var offset = row.offset();
|
||||
var left = offset.left - 10;
|
||||
var top = offset.top - $(window).scrollTop() + 3;
|
||||
var offset = row.offset();
|
||||
var left = offset.left - 10;
|
||||
var top = offset.top - $(window).scrollTop() + 3;
|
||||
|
||||
// update css
|
||||
$('#btn_viz').css({'position': 'fixed', 'top': top + 'px', 'left': left + 'px'});
|
||||
$('#btn_viz').off('click');
|
||||
$('#btn_viz').click(this.create_trackster_action(this.url_viz, btn_viz_pars, this.genome_build));
|
||||
$("#btn_viz").css({
|
||||
position: "fixed",
|
||||
top: `${top}px`,
|
||||
left: `${left}px`
|
||||
});
|
||||
$("#btn_viz").off("click");
|
||||
$("#btn_viz").click(() => {
|
||||
self.frame.add({
|
||||
title: _l("Trackster"),
|
||||
url: `${self.url_viz}/trackster?${$.param(btn_viz_pars)}`
|
||||
});
|
||||
});
|
||||
|
||||
// show the button
|
||||
$('#btn_viz').show();
|
||||
$("#btn_viz").show();
|
||||
} else {
|
||||
// hide the button
|
||||
$('#btn_viz').hide();
|
||||
$("#btn_viz").hide();
|
||||
}
|
||||
},
|
||||
|
||||
// hide button
|
||||
hide: function () {
|
||||
this.$el.find('#btn_viz').hide();
|
||||
},
|
||||
|
||||
// create action
|
||||
create_trackster_action : function (vis_url, dataset_params, dbkey) {
|
||||
// link this
|
||||
var self = this;
|
||||
|
||||
// create function
|
||||
return function() {
|
||||
var listTracksParams = {};
|
||||
if (dbkey) {
|
||||
listTracksParams[ 'f-dbkey' ] = dbkey;
|
||||
}
|
||||
$.ajax({
|
||||
url: vis_url + '/list_tracks?' + $.param( listTracksParams ),
|
||||
dataType: 'html',
|
||||
error: function() {
|
||||
// show error message
|
||||
self.modal.show({
|
||||
title : 'Something went wrong!',
|
||||
body : 'Unfortunately we could not add this dataset to the track browser. Please try again or contact us.',
|
||||
buttons : {
|
||||
'Cancel': function(){
|
||||
self.modal.hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
success: function(table_html) {
|
||||
self.modal.show({
|
||||
title : 'View Data in a New or Saved Visualization',
|
||||
buttons :{
|
||||
'Cancel': function(){
|
||||
self.modal.hide();
|
||||
},
|
||||
'View in saved visualization': function(){
|
||||
// show modal with saved visualizations
|
||||
self.modal.show(
|
||||
{
|
||||
title : 'Add Data to Saved Visualization',
|
||||
body : table_html,
|
||||
buttons : {
|
||||
'Cancel': function(){
|
||||
self.modal.hide();
|
||||
},
|
||||
'Add to visualization': function(){
|
||||
// hide
|
||||
self.modal.hide();
|
||||
|
||||
// search selected fields
|
||||
self.modal.$el.find('input[name=id]:checked').each(function(){
|
||||
// get visualization id
|
||||
var vis_id = $(this).val();
|
||||
dataset_params.id = vis_id;
|
||||
|
||||
// add widget
|
||||
self.frame.add({
|
||||
title : 'Trackster',
|
||||
type : 'url',
|
||||
content : vis_url + '/trackster?' + $.param(dataset_params)
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
'View in new visualization': function(){
|
||||
// hide
|
||||
self.modal.hide();
|
||||
|
||||
// add widget
|
||||
self.frame.add({
|
||||
title : 'Trackster',
|
||||
type : 'url',
|
||||
content : vis_url + '/trackster?' + $.param(dataset_params)
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return false;
|
||||
};
|
||||
/** hide button */
|
||||
hide: function() {
|
||||
this.$("#btn_viz").hide();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -622,7 +568,7 @@ var TabularButtonTracksterView = Backbone.View.extend({
|
||||
/**
|
||||
* Create a model, attach it to a view, render view, and attach it to a parent element.
|
||||
*/
|
||||
var createModelAndView = function(model, view, model_config, parent_elt) {
|
||||
var createModelAndView = (model, view, model_config, parent_elt) => {
|
||||
// Create model, view.
|
||||
var a_view = new view({
|
||||
model: new model(model_config)
|
||||
@@ -641,7 +587,7 @@ var createModelAndView = function(model, view, model_config, parent_elt) {
|
||||
* Create a tabular dataset chunked view (and requisite tabular dataset model)
|
||||
* and appends to parent_elt.
|
||||
*/
|
||||
var createTabularDatasetChunkedView = function(options) {
|
||||
var createTabularDatasetChunkedView = options => {
|
||||
// If no model, create and set model from dataset config.
|
||||
if (!options.model) {
|
||||
options.model = new TabularDataset(options.dataset_config);
|
||||
@@ -656,8 +602,9 @@ var createTabularDatasetChunkedView = function(options) {
|
||||
delete options.dataset_config;
|
||||
|
||||
// Create and set up view.
|
||||
var view = (embedded ? new EmbeddedTabularDatasetChunkedView(options) :
|
||||
new TopLevelTabularDatasetChunkedView(options));
|
||||
var view = embedded
|
||||
? new EmbeddedTabularDatasetChunkedView(options)
|
||||
: new TopLevelTabularDatasetChunkedView(options);
|
||||
view.render();
|
||||
|
||||
if (parent_elt) {
|
||||
@@ -671,12 +618,10 @@ var createTabularDatasetChunkedView = function(options) {
|
||||
return view;
|
||||
};
|
||||
|
||||
return {
|
||||
export default {
|
||||
Dataset: Dataset,
|
||||
TabularDataset: TabularDataset,
|
||||
DatasetCollection: DatasetCollection,
|
||||
TabularDatasetChunkedView: TabularDatasetChunkedView,
|
||||
createTabularDatasetChunkedView: createTabularDatasetChunkedView
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
define([
|
||||
'mvc/dataset/dataset-model',
|
||||
'mvc/dataset/dataset-list',
|
||||
'mvc/ui/ui-modal',
|
||||
'mvc/base-mvc',
|
||||
'utils/localization'
|
||||
], function( DATASET, DATASET_LIST, MODAL, BASE_MVC, _l ){
|
||||
'use strict';
|
||||
import DATASET from "mvc/dataset/dataset-model";
|
||||
import DATASET_LIST from "mvc/dataset/dataset-list";
|
||||
import MODAL from "mvc/ui/ui-modal";
|
||||
import BASE_MVC from "mvc/base-mvc";
|
||||
import _l from "utils/localization";
|
||||
|
||||
var logNamespace = 'dataset';
|
||||
var logNamespace = "dataset";
|
||||
/* ============================================================================
|
||||
TODO:
|
||||
does this really work with mixed contents?
|
||||
@@ -25,12 +22,12 @@ TODO:
|
||||
============================================================================ */
|
||||
/** Filters an array of dataset plain JSON objs.
|
||||
*/
|
||||
function _filterDatasetJSON( datasetJSON, where, datasetsOnly ){
|
||||
//TODO: replace with _.matches (underscore 1.6.0)
|
||||
function matches( obj, toMatch ){
|
||||
for( var key in toMatch ){
|
||||
if( toMatch.hasOwnProperty( key ) ){
|
||||
if( obj[ key ] !== toMatch[ key ] ){
|
||||
function _filterDatasetJSON(datasetJSON, where, datasetsOnly) {
|
||||
//TODO: replace with _.matches (underscore 1.6.0)
|
||||
function matches(obj, toMatch) {
|
||||
for (var key in toMatch) {
|
||||
if (toMatch.hasOwnProperty(key)) {
|
||||
if (obj[key] !== toMatch[key]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -38,11 +35,14 @@ function _filterDatasetJSON( datasetJSON, where, datasetsOnly ){
|
||||
return true;
|
||||
}
|
||||
|
||||
return datasetJSON.filter( function( json ){
|
||||
console.debug( json )
|
||||
return ( !json.deleted && json.visible )
|
||||
&& ( !datasetsOnly || json.collection_type === undefined )
|
||||
&& ( matches( json, where ) );
|
||||
return datasetJSON.filter(json => {
|
||||
console.debug(json);
|
||||
return (
|
||||
!json.deleted &&
|
||||
json.visible &&
|
||||
(!datasetsOnly || json.collection_type === undefined) &&
|
||||
matches(json, where)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -81,96 +81,97 @@ function _filterDatasetJSON( datasetJSON, where, datasetsOnly ){
|
||||
* }
|
||||
* });
|
||||
*/
|
||||
var DatasetChoiceModal = function( datasetJSON, options ){
|
||||
|
||||
var DatasetChoiceModal = (datasetJSON, options) => {
|
||||
// option defaults
|
||||
options = _.defaults( options || {}, {
|
||||
options = _.defaults(options || {}, {
|
||||
// show datasets or datasets and collections
|
||||
datasetsOnly : true,
|
||||
datasetsOnly: true,
|
||||
// map of attributes to filter datasetJSON by
|
||||
where : { state: 'ok' },
|
||||
where: { state: "ok" },
|
||||
// select more than one dataset?
|
||||
multiselect : false,
|
||||
multiselect: false,
|
||||
// any dataset ids that will display as already selected
|
||||
selected : []
|
||||
selected: []
|
||||
});
|
||||
// default title should depend on multiselect
|
||||
options.title = options.title ||
|
||||
( options.multiselect? _l( 'Choose datasets:' ): _l( 'Choose a dataset:' ) );
|
||||
options.title = options.title || (options.multiselect ? _l("Choose datasets:") : _l("Choose a dataset:"));
|
||||
|
||||
var modal, list, buttons,
|
||||
promise = jQuery.Deferred(),
|
||||
filterFn = options.filter || _filterDatasetJSON;
|
||||
var modal;
|
||||
var list;
|
||||
var buttons;
|
||||
var promise = jQuery.Deferred();
|
||||
var filterFn = options.filter || _filterDatasetJSON;
|
||||
|
||||
// filter the given datasets and if none left return a rejected promise for use with fail()
|
||||
datasetJSON = filterFn( datasetJSON, options.where, options.datasetsOnly );
|
||||
if( !datasetJSON.length ){
|
||||
return promise.reject( 'No matches found' );
|
||||
datasetJSON = filterFn(datasetJSON, options.where, options.datasetsOnly);
|
||||
if (!datasetJSON.length) {
|
||||
return promise.reject("No matches found");
|
||||
}
|
||||
|
||||
// resolve the returned promise with the json of the selected datasets
|
||||
function resolveWithSelected(){
|
||||
promise.resolve( list.getSelectedModels().map( function( model ){
|
||||
return model.toJSON();
|
||||
}));
|
||||
function resolveWithSelected() {
|
||||
promise.resolve(list.getSelectedModels().map(model => model.toJSON()));
|
||||
}
|
||||
// if multiselect - add a button for the user to complete the changes
|
||||
if( options.multiselect ){
|
||||
if (options.multiselect) {
|
||||
buttons = {};
|
||||
buttons[ _l( 'Ok' ) ] = resolveWithSelected;
|
||||
buttons[_l("Ok")] = resolveWithSelected;
|
||||
}
|
||||
|
||||
// create a full-height modal that's cancellable, remove unneeded elements and styles
|
||||
modal = new MODAL.View({
|
||||
height : 'auto',
|
||||
buttons : buttons,
|
||||
closing_events : true,
|
||||
closing_callback : function(){ promise.resolve( null ); },
|
||||
body : [
|
||||
'<div class="list-panel"></div>'
|
||||
].join('')
|
||||
height: "auto",
|
||||
buttons: buttons,
|
||||
closing_events: true,
|
||||
closing_callback: function() {
|
||||
promise.resolve(null);
|
||||
},
|
||||
body: ['<div class="list-panel"></div>'].join("")
|
||||
});
|
||||
modal.$( '.modal-header' ).remove();
|
||||
modal.$( '.modal-footer' ).css( 'margin-top', '0px' );
|
||||
modal.$(".modal-header").remove();
|
||||
modal.$(".modal-footer").css("margin-top", "0px");
|
||||
|
||||
// attach a dataset list (of the filtered datasets) to that modal that's selectable
|
||||
list = new DATASET_LIST.DatasetList({
|
||||
title : options.title,
|
||||
subtitle : options.subtitle || _l([
|
||||
//TODO: as option
|
||||
'Click the checkboxes on the right to select datasets. ',
|
||||
'Click the datasets names to see their details. '
|
||||
].join('')),
|
||||
el : modal.$body.find( '.list-panel' ),
|
||||
selecting : true,
|
||||
selected : options.selected,
|
||||
collection : new DATASET.DatasetAssociationCollection( datasetJSON )
|
||||
title: options.title,
|
||||
subtitle:
|
||||
options.subtitle ||
|
||||
_l(
|
||||
[
|
||||
//TODO: as option
|
||||
"Click the checkboxes on the right to select datasets. ",
|
||||
"Click the datasets names to see their details. "
|
||||
].join("")
|
||||
),
|
||||
el: modal.$body.find(".list-panel"),
|
||||
selecting: true,
|
||||
selected: options.selected,
|
||||
collection: new DATASET.DatasetAssociationCollection(datasetJSON)
|
||||
});
|
||||
|
||||
// when the list is rendered, show the modal (also add a specifying class for css)
|
||||
list.once( 'rendered:initial', function(){
|
||||
list.once("rendered:initial", () => {
|
||||
modal.show();
|
||||
modal.$el.addClass( 'dataset-choice-modal' );
|
||||
modal.$el.addClass("dataset-choice-modal");
|
||||
});
|
||||
if( !options.multiselect ){
|
||||
if (!options.multiselect) {
|
||||
// if single select, remove the all/none list actions from the panel
|
||||
list.on( 'rendered', function(){
|
||||
list.$( '.list-actions' ).hide();
|
||||
list.on("rendered", () => {
|
||||
list.$(".list-actions").hide();
|
||||
});
|
||||
// if single select, immediately resolve on a single selection
|
||||
list.on( 'view:selected', function( view ){
|
||||
promise.resolve([ view.model.toJSON() ]);
|
||||
list.on("view:selected", view => {
|
||||
promise.resolve([view.model.toJSON()]);
|
||||
});
|
||||
}
|
||||
list.render( 0 );
|
||||
list.render(0);
|
||||
|
||||
// return the promise, and on any resolution close the modal
|
||||
return promise.always( function(){
|
||||
return promise.always(() => {
|
||||
modal.hide();
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// ============================================================================
|
||||
/** Activator for single dataset selection modal and display of the selected dataset.
|
||||
* The activator/display will show as a single div and, when a dataset is selected,
|
||||
@@ -199,18 +200,18 @@ var DatasetChoiceModal = function( datasetJSON, options ){
|
||||
* // ... do stuff with new selections
|
||||
* });
|
||||
*/
|
||||
var DatasetChoice = Backbone.View.extend( BASE_MVC.LoggableMixin ).extend({
|
||||
_logNamespace : logNamespace,
|
||||
var DatasetChoice = Backbone.View.extend(BASE_MVC.LoggableMixin).extend({
|
||||
_logNamespace: logNamespace,
|
||||
|
||||
className : 'dataset-choice',
|
||||
className: "dataset-choice",
|
||||
|
||||
/** set up defaults, options, and listeners */
|
||||
initialize : function( attributes ){
|
||||
this.debug( this + '(DatasetChoice).initialize:', attributes );
|
||||
initialize: function(attributes) {
|
||||
this.debug(`${this}(DatasetChoice).initialize:`, attributes);
|
||||
|
||||
this.label = attributes.label !== undefined? _l( attributes.label ) : '';
|
||||
this.label = attributes.label !== undefined ? _l(attributes.label) : "";
|
||||
this.where = attributes.where;
|
||||
this.datasetsOnly = attributes.datasetsOnly !== undefined? attributes.datasetsOnly: true;
|
||||
this.datasetsOnly = attributes.datasetsOnly !== undefined ? attributes.datasetsOnly : true;
|
||||
|
||||
this.datasetJSON = attributes.datasetJSON || [];
|
||||
this.selected = attributes.selected || [];
|
||||
@@ -219,122 +220,123 @@ var DatasetChoice = Backbone.View.extend( BASE_MVC.LoggableMixin ).extend({
|
||||
},
|
||||
|
||||
/** add any (bbone) listeners */
|
||||
_setUpListeners : function(){
|
||||
_setUpListeners: function() {
|
||||
//this.on( 'all', function(){
|
||||
// this.log( this + '', arguments );
|
||||
//});
|
||||
},
|
||||
|
||||
/** render the view */
|
||||
render : function(){
|
||||
render: function() {
|
||||
var json = this.toJSON();
|
||||
this.$el.html( this._template( json ) );
|
||||
this.$( '.selected' ).replaceWith( this._renderSelected( json ) );
|
||||
this.$el.html(this._template(json));
|
||||
this.$(".selected").replaceWith(this._renderSelected(json));
|
||||
return this;
|
||||
},
|
||||
|
||||
/** return plain html for the overall control */
|
||||
_template : function( json ){
|
||||
return _.template([
|
||||
'<label>',
|
||||
'<span class="prompt"><%- label %></span>',
|
||||
'<div class="selected"></div>',
|
||||
'</label>'
|
||||
].join(''))( json );
|
||||
_template: function(json) {
|
||||
return _.template(
|
||||
["<label>", '<span class="prompt"><%- label %></span>', '<div class="selected"></div>', "</label>"].join("")
|
||||
)(json);
|
||||
},
|
||||
|
||||
/** return jQ DOM for the selected dataset (only one) */
|
||||
_renderSelected : function( json ){
|
||||
if( json.selected.length ){
|
||||
//TODO: break out?
|
||||
return $( _.template([
|
||||
'<div class="selected">',
|
||||
'<span class="title"><%- selected.hid %>: <%- selected.name %></span>',
|
||||
'<span class="subtitle">',
|
||||
'<i><%- selected.misc_blurb %></i>',
|
||||
'<i>', _l( 'format' ) + ': ', '<%- selected.file_ext %></i>',
|
||||
'<i><%- selected.misc_info %></i>',
|
||||
'</span>',
|
||||
'</div>'
|
||||
].join( '' ), { variable : 'selected' })( json.selected[0] ));
|
||||
_renderSelected: function(json) {
|
||||
if (json.selected.length) {
|
||||
//TODO: break out?
|
||||
return $(
|
||||
_.template(
|
||||
[
|
||||
'<div class="selected">',
|
||||
'<span class="title"><%- selected.hid %>: <%- selected.name %></span>',
|
||||
'<span class="subtitle">',
|
||||
"<i><%- selected.misc_blurb %></i>",
|
||||
"<i>",
|
||||
`${_l("format")}: `,
|
||||
"<%- selected.file_ext %></i>",
|
||||
"<i><%- selected.misc_info %></i>",
|
||||
"</span>",
|
||||
"</div>"
|
||||
].join(""),
|
||||
{ variable: "selected" }
|
||||
)(json.selected[0])
|
||||
);
|
||||
}
|
||||
return $([
|
||||
'<span class="none-selected-msg">(',
|
||||
_l( 'click to select a dataset' ),
|
||||
')</span>'
|
||||
].join( '' ));
|
||||
return $(['<span class="none-selected-msg">(', _l("click to select a dataset"), ")</span>"].join(""));
|
||||
},
|
||||
|
||||
//TODO:?? why not just pass in view?
|
||||
//TODO:?? why not just pass in view?
|
||||
/** return a plain JSON object with both the view and dataset attributes */
|
||||
toJSON : function(){
|
||||
toJSON: function() {
|
||||
var chooser = this;
|
||||
return {
|
||||
label : chooser.label,
|
||||
datasets : chooser.datasetJSON,
|
||||
selected : _.compact( _.map( chooser.selected, function( id ){
|
||||
return _.findWhere( chooser.datasetJSON, { id: id });
|
||||
}))
|
||||
label: chooser.label,
|
||||
datasets: chooser.datasetJSON,
|
||||
selected: _.compact(
|
||||
_.map(chooser.selected, id =>
|
||||
_.findWhere(chooser.datasetJSON, {
|
||||
id: id
|
||||
})
|
||||
)
|
||||
)
|
||||
};
|
||||
},
|
||||
|
||||
/** event map: when to open the modal */
|
||||
events : {
|
||||
events: {
|
||||
// the whole thing functions as a button
|
||||
'click' : 'chooseWithModal'
|
||||
click: "chooseWithModal"
|
||||
},
|
||||
|
||||
//TODO:?? modal to prop of this?
|
||||
//TODO:?? should be able to handle 'none selectable' on initialize
|
||||
//TODO:?? modal to prop of this?
|
||||
//TODO:?? should be able to handle 'none selectable' on initialize
|
||||
/** open the modal and handle the promise representing the user's choice
|
||||
* @fires 'selected' when the user selects dataset(s) - passed full json of the selected datasets
|
||||
* @fires 'cancelled' when the user clicks away/closes the modal (no selection made) - passed this
|
||||
* @fires 'error' if the modal has no selectable datasets based on this.where - passed this and other args
|
||||
*/
|
||||
chooseWithModal : function(){
|
||||
chooseWithModal: function() {
|
||||
var chooser = this;
|
||||
|
||||
return this._createModal()
|
||||
.done( function( json ){
|
||||
if( json ){
|
||||
chooser.selected = _.pluck( json, 'id' );
|
||||
chooser.trigger( 'selected', chooser, json );
|
||||
.done(json => {
|
||||
if (json) {
|
||||
chooser.selected = _.pluck(json, "id");
|
||||
chooser.trigger("selected", chooser, json);
|
||||
chooser.render();
|
||||
|
||||
} else {
|
||||
chooser.trigger( 'cancelled', chooser );
|
||||
chooser.trigger("cancelled", chooser);
|
||||
}
|
||||
})
|
||||
|
||||
.fail( function(){
|
||||
chooser.trigger( 'error', chooser, arguments );
|
||||
.fail(function() {
|
||||
chooser.trigger("error", chooser, arguments);
|
||||
});
|
||||
},
|
||||
|
||||
/** create and return the modal to use for choosing */
|
||||
_createModal : function(){
|
||||
return new DatasetChoiceModal( this.datasetJSON, this._getModalOptions() );
|
||||
_createModal: function() {
|
||||
return new DatasetChoiceModal(this.datasetJSON, this._getModalOptions());
|
||||
},
|
||||
|
||||
/** return a plain JSON containing the options to pass to the modal */
|
||||
_getModalOptions : function(){
|
||||
_getModalOptions: function() {
|
||||
return {
|
||||
title : this.label,
|
||||
multiselect : false,
|
||||
selected : this.selected,
|
||||
where : this.where,
|
||||
datasetsOnly : this.datasetsOnly
|
||||
title: this.label,
|
||||
multiselect: false,
|
||||
selected: this.selected,
|
||||
where: this.where,
|
||||
datasetsOnly: this.datasetsOnly
|
||||
};
|
||||
},
|
||||
|
||||
// ------------------------------------------------------------------------ misc
|
||||
/** string rep */
|
||||
toString : function(){
|
||||
return 'DatasetChoice(' + this.selected + ')';
|
||||
toString: function() {
|
||||
return `DatasetChoice(${this.selected})`;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// ============================================================================
|
||||
/** Activator for multiple dataset selection modal and display of the selected datasets.
|
||||
* The activator/display will show as a table of all choices.
|
||||
@@ -347,85 +349,83 @@ var DatasetChoice = Backbone.View.extend( BASE_MVC.LoggableMixin ).extend({
|
||||
* (e.g. { file_ext: _l( 'Format' ) }) - defaults are listed below
|
||||
*/
|
||||
var MultiDatasetChoice = DatasetChoice.extend({
|
||||
|
||||
className : DatasetChoice.prototype.className + ' multi',
|
||||
className: `${DatasetChoice.prototype.className} multi`,
|
||||
|
||||
/** default (dataset attribute key -> table header text) map of what cells to display in the table */
|
||||
cells : {
|
||||
hid : _l( 'History #' ),
|
||||
name : _l( 'Name' ),
|
||||
misc_blurb : _l( 'Summary' ),
|
||||
file_ext : _l( 'Format' ),
|
||||
genome_build : _l( 'Genome' ),
|
||||
tags : _l( 'Tags' ),
|
||||
annotation : _l( 'Annotation' )
|
||||
cells: {
|
||||
hid: _l("History #"),
|
||||
name: _l("Name"),
|
||||
misc_blurb: _l("Summary"),
|
||||
file_ext: _l("Format"),
|
||||
genome_build: _l("Genome"),
|
||||
tags: _l("Tags"),
|
||||
annotation: _l("Annotation")
|
||||
},
|
||||
|
||||
/** in this override, add the showHeaders and cells options */
|
||||
initialize : function( attributes ){
|
||||
this.showHeaders = attributes.showHeaders !== undefined? attributes.showHeaders : true;
|
||||
initialize: function(attributes) {
|
||||
this.showHeaders = attributes.showHeaders !== undefined ? attributes.showHeaders : true;
|
||||
this.cells = attributes.cells || this.cells;
|
||||
DatasetChoice.prototype.initialize.call( this, attributes );
|
||||
DatasetChoice.prototype.initialize.call(this, attributes);
|
||||
},
|
||||
|
||||
/** in this override, display the selected datasets as a table with optional headers */
|
||||
_renderSelected : function( json ){
|
||||
if( json.selected.length ){
|
||||
return $( _.template([
|
||||
'<table class="selected">',
|
||||
'<% if( json.showHeaders ){ %>',
|
||||
'<thead><tr>',
|
||||
'<% _.map( json.cells, function( val, key ){ %>',
|
||||
'<th><%- val %></th>',
|
||||
'<% }); %>',
|
||||
'</tr></thead>',
|
||||
'<% } %>',
|
||||
'<tbody>',
|
||||
'<% _.map( json.selected, function( selected ){ %>',
|
||||
'<tr>',
|
||||
'<% _.map( json.cells, function( val, key ){ %>',
|
||||
'<td class="cell-<%- key %>"><%- selected[ key ] %></td>',
|
||||
'<% }) %>',
|
||||
'</tr>',
|
||||
'<% }); %>',
|
||||
'</tbody>',
|
||||
'</table>'
|
||||
].join( '' ), { variable: 'json' })( json ));
|
||||
_renderSelected: function(json) {
|
||||
if (json.selected.length) {
|
||||
return $(
|
||||
_.template(
|
||||
[
|
||||
'<table class="selected">',
|
||||
"<% if( json.showHeaders ){ %>",
|
||||
"<thead><tr>",
|
||||
"<% _.map( json.cells, function( val, key ){ %>",
|
||||
"<th><%- val %></th>",
|
||||
"<% }); %>",
|
||||
"</tr></thead>",
|
||||
"<% } %>",
|
||||
"<tbody>",
|
||||
"<% _.map( json.selected, function( selected ){ %>",
|
||||
"<tr>",
|
||||
"<% _.map( json.cells, function( val, key ){ %>",
|
||||
'<td class="cell-<%- key %>"><%- selected[ key ] %></td>',
|
||||
"<% }) %>",
|
||||
"</tr>",
|
||||
"<% }); %>",
|
||||
"</tbody>",
|
||||
"</table>"
|
||||
].join(""),
|
||||
{ variable: "json" }
|
||||
)(json)
|
||||
);
|
||||
}
|
||||
return $([
|
||||
'<span class="none-selected-msg">(',
|
||||
_l( 'click to select a dataset' ),
|
||||
')</span>'
|
||||
].join( '' ));
|
||||
return $(['<span class="none-selected-msg">(', _l("click to select a dataset"), ")</span>"].join(""));
|
||||
},
|
||||
|
||||
/** in this override, send the showHeaders and cells options as well */
|
||||
toJSON : function(){
|
||||
return _.extend( DatasetChoice.prototype.toJSON.call( this ), {
|
||||
showHeaders : this.showHeaders,
|
||||
cells : this.cells
|
||||
toJSON: function() {
|
||||
return _.extend(DatasetChoice.prototype.toJSON.call(this), {
|
||||
showHeaders: this.showHeaders,
|
||||
cells: this.cells
|
||||
});
|
||||
},
|
||||
|
||||
/** in this override, set multiselect to true */
|
||||
_getModalOptions : function(){
|
||||
return _.extend( DatasetChoice.prototype._getModalOptions.call( this ), {
|
||||
multiselect : true
|
||||
_getModalOptions: function() {
|
||||
return _.extend(DatasetChoice.prototype._getModalOptions.call(this), {
|
||||
multiselect: true
|
||||
});
|
||||
},
|
||||
|
||||
// ------------------------------------------------------------------------ misc
|
||||
/** string rep */
|
||||
toString : function(){
|
||||
return 'DatasetChoice(' + this.selected + ')';
|
||||
toString: function() {
|
||||
return `DatasetChoice(${this.selected})`;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// ============================================================================
|
||||
return {
|
||||
DatasetChoiceModal : DatasetChoiceModal,
|
||||
DatasetChoice : DatasetChoice,
|
||||
MultiDatasetChoice : MultiDatasetChoice
|
||||
};
|
||||
});
|
||||
export default {
|
||||
DatasetChoiceModal: DatasetChoiceModal,
|
||||
DatasetChoice: DatasetChoice,
|
||||
MultiDatasetChoice: MultiDatasetChoice
|
||||
};
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
import _l from "utils/localization";
|
||||
/** Dataset edit attributes view */
|
||||
import Utils from "utils/utils";
|
||||
import Tabs from "mvc/ui/ui-tabs";
|
||||
import Ui from "mvc/ui/ui-misc";
|
||||
import Form from "mvc/form/form-view";
|
||||
var View = Backbone.View.extend({
|
||||
initialize: function() {
|
||||
this.setElement("<div/>");
|
||||
this.model = new Backbone.Model({
|
||||
dataset_id: Galaxy.params.dataset_id
|
||||
});
|
||||
this.message = new Ui.Message({ persistent: true });
|
||||
this.tabs = this._createTabs();
|
||||
this.$el
|
||||
.append($("<h4/>").append("Edit dataset attributes"))
|
||||
.append(this.message.$el)
|
||||
.append("<p/>")
|
||||
.append(this.tabs.$el)
|
||||
.hide();
|
||||
this.render();
|
||||
},
|
||||
|
||||
/** fetch data for the selected dataset and build forms */
|
||||
render: function() {
|
||||
var self = this;
|
||||
$.ajax({
|
||||
url: `${Galaxy.root}dataset/get_edit?dataset_id=${self.model.get("dataset_id")}`,
|
||||
success: function(response) {
|
||||
!self.initial_message && self.message.update(response);
|
||||
self.initial_message = true;
|
||||
_.each(self.forms, (form, key) => {
|
||||
form.model.set("inputs", response[`${key}_inputs`]);
|
||||
form.model.set("hide_operations", response[`${key}_disable`]);
|
||||
form.render();
|
||||
});
|
||||
self.$el.show();
|
||||
},
|
||||
error: function(response) {
|
||||
var err_msg = response.responseJSON && response.responseJSON.err_msg;
|
||||
self.message.update({
|
||||
status: "danger",
|
||||
message: err_msg || "Error occured while loading the dataset."
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/** submit data to backend to update attributes */
|
||||
_submit: function(operation, form) {
|
||||
var self = this;
|
||||
var data = form.data.create();
|
||||
data.dataset_id = this.model.get("dataset_id");
|
||||
data.operation = operation;
|
||||
$.ajax({
|
||||
type: "PUT",
|
||||
url: `${Galaxy.root}dataset/set_edit`,
|
||||
data: data,
|
||||
success: function(response) {
|
||||
self.message.update(response);
|
||||
self.render();
|
||||
self._reloadHistory();
|
||||
},
|
||||
error: function(response) {
|
||||
var err_msg = response.responseJSON && response.responseJSON.err_msg;
|
||||
self.message.update({
|
||||
status: "danger",
|
||||
message: err_msg || "Error occured while editing the dataset attributes."
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/** create tabs for different dataset attribute categories*/
|
||||
_createTabs: function() {
|
||||
this.forms = {
|
||||
attribute: this._getAttribute(),
|
||||
conversion: this._getConversion(),
|
||||
datatype: this._getDatatype(),
|
||||
permission: this._getPermission()
|
||||
};
|
||||
var tabs = new Tabs.View();
|
||||
tabs.add({
|
||||
id: "attribute",
|
||||
title: _l("Attributes"),
|
||||
icon: "fa fa-bars",
|
||||
tooltip: _l("Edit dataset attributes"),
|
||||
$el: this.forms.attribute.$el
|
||||
});
|
||||
tabs.add({
|
||||
id: "convert",
|
||||
title: _l("Convert"),
|
||||
icon: "fa-gear",
|
||||
tooltip: _l("Convert to new format"),
|
||||
$el: this.forms.conversion.$el
|
||||
});
|
||||
tabs.add({
|
||||
id: "datatype",
|
||||
title: _l("Datatypes"),
|
||||
icon: "fa-database",
|
||||
tooltip: _l("Change data type"),
|
||||
$el: this.forms.datatype.$el
|
||||
});
|
||||
tabs.add({
|
||||
id: "permissions",
|
||||
title: _l("Permissions"),
|
||||
icon: "fa-user",
|
||||
tooltip: _l("Permissions"),
|
||||
$el: this.forms.permission.$el
|
||||
});
|
||||
return tabs;
|
||||
},
|
||||
|
||||
/** edit main attributes form */
|
||||
_getAttribute: function() {
|
||||
var self = this;
|
||||
var form = new Form({
|
||||
title: _l("Edit attributes"),
|
||||
operations: {
|
||||
submit_attributes: new Ui.ButtonIcon({
|
||||
tooltip: _l("Save attributes of the dataset."),
|
||||
icon: "fa-floppy-o",
|
||||
title: _l("Save"),
|
||||
onclick: function() {
|
||||
self._submit("attributes", form);
|
||||
}
|
||||
}),
|
||||
submit_autodetect: new Ui.ButtonIcon({
|
||||
tooltip:
|
||||
"This will inspect the dataset and attempt to correct the values of fields if they are not accurate.",
|
||||
icon: "fa-undo",
|
||||
title: "Auto-detect",
|
||||
onclick: function() {
|
||||
self._submit("autodetect", form);
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
return form;
|
||||
},
|
||||
|
||||
/** datatype conversion form */
|
||||
_getConversion: function() {
|
||||
var self = this;
|
||||
var form = new Form({
|
||||
title: _l("Convert to new format"),
|
||||
operations: {
|
||||
submit_conversion: new Ui.ButtonIcon({
|
||||
tooltip: _l("Convert the datatype to a new format."),
|
||||
title: _l("Convert datatype"),
|
||||
icon: "fa-exchange",
|
||||
onclick: function() {
|
||||
self._submit("conversion", form);
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
return form;
|
||||
},
|
||||
|
||||
/** change datatype form */
|
||||
_getDatatype: function() {
|
||||
var self = this;
|
||||
var form = new Form({
|
||||
title: _l("Change datatype"),
|
||||
operations: {
|
||||
submit_datatype: new Ui.ButtonIcon({
|
||||
tooltip: _l("Change the datatype to a new type."),
|
||||
title: _l("Change datatype"),
|
||||
icon: "fa-exchange",
|
||||
onclick: function() {
|
||||
self._submit("datatype", form);
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
return form;
|
||||
},
|
||||
|
||||
/** dataset permissions form */
|
||||
_getPermission: function() {
|
||||
var self = this;
|
||||
var form = new Form({
|
||||
title: _l("Manage dataset permissions"),
|
||||
operations: {
|
||||
submit_permission: new Ui.ButtonIcon({
|
||||
tooltip: _l("Save permissions."),
|
||||
title: _l("Save permissions"),
|
||||
icon: "fa-floppy-o ",
|
||||
onclick: function() {
|
||||
self._submit("permission", form);
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
return form;
|
||||
},
|
||||
|
||||
/** reload Galaxy's history after updating dataset's attributes */
|
||||
_reloadHistory: function() {
|
||||
if (window.Galaxy) {
|
||||
window.Galaxy.currHistoryPanel.loadCurrentHistory();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default {
|
||||
View: View
|
||||
};
|
||||
@@ -0,0 +1,190 @@
|
||||
import _l from "utils/localization";
|
||||
import Utils from "utils/utils";
|
||||
import Ui from "mvc/ui/ui-misc";
|
||||
import Form from "mvc/form/form-view";
|
||||
/** Dataset edit attributes view */
|
||||
var View = Backbone.View.extend({
|
||||
initialize: function() {
|
||||
this.setElement("<div/>");
|
||||
this.model = new Backbone.Model({
|
||||
dataset_id: Galaxy.params.dataset_id
|
||||
});
|
||||
this.render();
|
||||
},
|
||||
|
||||
// Fetch data for the selected dataset and
|
||||
render: function() {
|
||||
var data_url = `${Galaxy.root}api/datasets/${this.model.get("dataset_id")}`;
|
||||
|
||||
var self = this;
|
||||
|
||||
Utils.get({
|
||||
url: data_url,
|
||||
success: function(dataset) {
|
||||
var job_url = `${Galaxy.root}api/jobs/${dataset.creating_job}?full=True`;
|
||||
Utils.get({
|
||||
url: job_url,
|
||||
success: function(job) {
|
||||
var job_url = `${Galaxy.root}api/jobs/${dataset.creating_job}?full=True`;
|
||||
self.render_error_page(self, dataset, job);
|
||||
},
|
||||
error: function(response) {
|
||||
var error_response = {
|
||||
status: "error",
|
||||
message: "Error occured while loading the job.",
|
||||
persistent: true,
|
||||
cls: "errormessage"
|
||||
};
|
||||
self.display_message(error_response, self.$(".response-message"));
|
||||
}
|
||||
});
|
||||
},
|
||||
error: function(response) {
|
||||
var error_response = {
|
||||
status: "error",
|
||||
message: "Error occured while loading the dataset.",
|
||||
persistent: true,
|
||||
cls: "errormessage"
|
||||
};
|
||||
self.display_message(error_response, self.$(".response-message"));
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/** Render the view */
|
||||
render_error_page: function(self, dataset, job) {
|
||||
self.$el.empty().append(self._templateHeader());
|
||||
self.$el.append("<h2>Dataset Error</h2>");
|
||||
self.$el.append(`<p>An error occured while running the tool <b>${job.tool_id}</b>.</p>`);
|
||||
self.$el.append("<p>Tool execution generated the following messages:</p>");
|
||||
self.$el.append(`<pre class="code">${job.stderr}</pre>`);
|
||||
self.$el.append("<h2>Report This Error</pre>");
|
||||
self.$el.append(
|
||||
"<p>Usually the local Galaxy administrators regularly review errors that occur on the server. However, if you would like to provide additional information (such as what you were trying to do when the error occurred) and a contact e-mail address, we will be better able to investigate your problem and get back to you.</p>"
|
||||
);
|
||||
self.$el.append(self._getBugFormTemplate(dataset, job));
|
||||
},
|
||||
|
||||
/** Display actions messages */
|
||||
display_message: function(response, $el, doNotClear, safe) {
|
||||
if (!safe) {
|
||||
if (doNotClear) {
|
||||
$el.append(new Ui.Message(response).$el);
|
||||
} else {
|
||||
$el.empty().html(new Ui.Message(response).$el);
|
||||
}
|
||||
} else {
|
||||
if (doNotClear) {
|
||||
$el.append(new Ui.UnescapedMessage(response).$el);
|
||||
} else {
|
||||
$el.empty().html(new Ui.UnescapedMessage(response).$el);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/** Main template */
|
||||
_templateHeader: function() {
|
||||
return '<div class="page-container edit-attr">' + '<div class="response-message"></div>' + "</div>";
|
||||
},
|
||||
|
||||
/** Convert tab template */
|
||||
_getBugFormTemplate: function(dataset, job) {
|
||||
var self = this;
|
||||
var inputs = [
|
||||
{
|
||||
help: _l("Your email address"),
|
||||
options: [],
|
||||
type: "text",
|
||||
name: "email",
|
||||
label: "Your email",
|
||||
value: Galaxy.user.get("email")
|
||||
},
|
||||
{
|
||||
help: _l(
|
||||
"Any additional comments you can provide regarding what you were doing at the time of the bug."
|
||||
),
|
||||
options: [],
|
||||
type: "text",
|
||||
area: true,
|
||||
name: "message",
|
||||
label: "Message"
|
||||
}
|
||||
];
|
||||
|
||||
// TODO
|
||||
/*
|
||||
if (false && response.any_public) {
|
||||
inputs.push({
|
||||
name: "public_consent",
|
||||
label: "Public Disclosure Consent",
|
||||
help:
|
||||
"This Galaxy is configured to report to one or more error reporting backends that public to the world. By selecting 'yes', you acknowledge that this bug report will be made public.",
|
||||
value: String(Boolean(false)),
|
||||
options: [],
|
||||
type: "boolean"
|
||||
});
|
||||
}
|
||||
*/
|
||||
|
||||
var form = new Form({
|
||||
title: _l("Error Report"),
|
||||
inputs: inputs,
|
||||
buttons: {
|
||||
save: new Ui.Button({
|
||||
icon: "fa-bug",
|
||||
title: _l("Report"),
|
||||
cls: "ui-button btn btn-primary",
|
||||
floating: "clear",
|
||||
onclick: function() {
|
||||
var form_data = form.data.create();
|
||||
var url = `${Galaxy.root}api/jobs/${job.id}/error`;
|
||||
form_data.dataset_id = dataset.id;
|
||||
self.submit(form_data, url);
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
return form.$el;
|
||||
},
|
||||
|
||||
/** Make ajax request */
|
||||
submit: function(form_data, url) {
|
||||
var self = this;
|
||||
// Some required metadata
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: url,
|
||||
data: form_data,
|
||||
success: function(response) {
|
||||
// Clear out the div
|
||||
self.$el.empty().append(self._templateHeader());
|
||||
// And display the messages.
|
||||
response.messages.forEach(message => {
|
||||
self.display_message(
|
||||
{
|
||||
status: message[1],
|
||||
message: message[0],
|
||||
persistent: true
|
||||
},
|
||||
self.$(".response-message"),
|
||||
true,
|
||||
true
|
||||
);
|
||||
});
|
||||
},
|
||||
error: function(response) {
|
||||
var error_response = {
|
||||
status: "error",
|
||||
message: "Error occured while saving. Please fill all the required fields and try again.",
|
||||
persistent: true,
|
||||
cls: "errormessage"
|
||||
};
|
||||
self.display_message(error_response, self.$(".response-message"));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export default {
|
||||
View: View
|
||||
};
|
||||
@@ -1,425 +1,489 @@
|
||||
define([
|
||||
"mvc/dataset/states",
|
||||
"mvc/dataset/dataset-li",
|
||||
"mvc/tag",
|
||||
"mvc/annotation",
|
||||
"ui/fa-icon-button",
|
||||
"mvc/base-mvc",
|
||||
"utils/localization"
|
||||
], function( STATES, DATASET_LI, TAGS, ANNOTATIONS, faIconButton, BASE_MVC, _l ){
|
||||
import STATES from "mvc/dataset/states";
|
||||
import DATASET_LI from "mvc/dataset/dataset-li";
|
||||
import TAGS from "mvc/tag";
|
||||
import ANNOTATIONS from "mvc/annotation";
|
||||
import faIconButton from "ui/fa-icon-button";
|
||||
import BASE_MVC from "mvc/base-mvc";
|
||||
import _l from "utils/localization";
|
||||
|
||||
'use strict';
|
||||
//==============================================================================
|
||||
var _super = DATASET_LI.DatasetListItemView;
|
||||
/** @class Editing view for DatasetAssociation.
|
||||
*/
|
||||
var DatasetListItemEdit = _super.extend(
|
||||
/** @lends DatasetListItemEdit.prototype */{
|
||||
/** @lends DatasetListItemEdit.prototype */ {
|
||||
/** set up: options */
|
||||
initialize: function(attributes) {
|
||||
_super.prototype.initialize.call(this, attributes);
|
||||
this.hasUser = attributes.hasUser;
|
||||
|
||||
/** set up: options */
|
||||
initialize : function( attributes ){
|
||||
_super.prototype.initialize.call( this, attributes );
|
||||
this.hasUser = attributes.hasUser;
|
||||
/** allow user purge of dataset files? */
|
||||
this.purgeAllowed = attributes.purgeAllowed || false;
|
||||
|
||||
/** allow user purge of dataset files? */
|
||||
this.purgeAllowed = attributes.purgeAllowed || false;
|
||||
//TODO: move to HiddenUntilActivatedViewMixin
|
||||
/** should the tags editor be shown or hidden initially? */
|
||||
this.tagsEditorShown = attributes.tagsEditorShown || false;
|
||||
/** should the tags editor be shown or hidden initially? */
|
||||
this.annotationEditorShown = attributes.annotationEditorShown || false;
|
||||
},
|
||||
|
||||
//TODO: move to HiddenUntilActivatedViewMixin
|
||||
/** should the tags editor be shown or hidden initially? */
|
||||
this.tagsEditorShown = attributes.tagsEditorShown || false;
|
||||
/** should the tags editor be shown or hidden initially? */
|
||||
this.annotationEditorShown = attributes.annotationEditorShown || false;
|
||||
},
|
||||
// ......................................................................... titlebar actions
|
||||
/** In this override, add the other two primary actions: edit and delete */
|
||||
_renderPrimaryActions: function() {
|
||||
var actions = _super.prototype._renderPrimaryActions.call(this);
|
||||
if (this.model.get("state") === STATES.NOT_VIEWABLE) {
|
||||
return actions;
|
||||
}
|
||||
// render the display, edit attr and delete icon-buttons
|
||||
return _super.prototype._renderPrimaryActions
|
||||
.call(this)
|
||||
.concat([this._renderEditButton(), this._renderDeleteButton()]);
|
||||
},
|
||||
|
||||
// ......................................................................... titlebar actions
|
||||
/** In this override, add the other two primary actions: edit and delete */
|
||||
_renderPrimaryActions : function(){
|
||||
var actions = _super.prototype._renderPrimaryActions.call( this );
|
||||
if( this.model.get( 'state' ) === STATES.NOT_VIEWABLE ){
|
||||
return actions;
|
||||
}
|
||||
// render the display, edit attr and delete icon-buttons
|
||||
return _super.prototype._renderPrimaryActions.call( this ).concat([
|
||||
this._renderEditButton(),
|
||||
this._renderDeleteButton()
|
||||
]);
|
||||
},
|
||||
//TODO: move titleButtons into state renderers, remove state checks in the buttons
|
||||
|
||||
//TODO: move titleButtons into state renderers, remove state checks in the buttons
|
||||
/** Render icon-button to edit the attributes (format, permissions, etc.) this dataset. */
|
||||
_renderEditButton: function() {
|
||||
var self = this;
|
||||
// don't show edit while uploading, in-accessible
|
||||
// DO show if in error (ala previous history panel)
|
||||
if (this.model.get("state") === STATES.DISCARDED || !this.model.get("accessible")) {
|
||||
return null;
|
||||
}
|
||||
var purged = this.model.get("purged");
|
||||
var deleted = this.model.get("deleted");
|
||||
|
||||
/** Render icon-button to edit the attributes (format, permissions, etc.) this dataset. */
|
||||
_renderEditButton : function(){
|
||||
// don't show edit while uploading, in-accessible
|
||||
// DO show if in error (ala previous history panel)
|
||||
if( ( this.model.get( 'state' ) === STATES.DISCARDED )
|
||||
|| ( !this.model.get( 'accessible' ) ) ){
|
||||
return null;
|
||||
}
|
||||
|
||||
var purged = this.model.get( 'purged' ),
|
||||
deleted = this.model.get( 'deleted' ),
|
||||
editBtnData = {
|
||||
title : _l( 'Edit attributes' ),
|
||||
href : this.model.urls.edit,
|
||||
target : this.linkTarget,
|
||||
faIcon : 'fa-pencil',
|
||||
classes : 'edit-btn'
|
||||
var editBtnData = {
|
||||
title: _l("Edit attributes"),
|
||||
href: `${Galaxy.root}datasets/edit?dataset_id=${this.model.attributes.id}`,
|
||||
faIcon: "fa-pencil",
|
||||
classes: "edit-btn",
|
||||
onclick: function(ev) {
|
||||
if (Galaxy.router) {
|
||||
ev.preventDefault();
|
||||
Galaxy.router.push("datasets/edit", {
|
||||
dataset_id: self.model.attributes.id
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// disable if purged or deleted and explain why in the tooltip
|
||||
if( deleted || purged ){
|
||||
editBtnData.disabled = true;
|
||||
if( purged ){
|
||||
editBtnData.title = _l( 'Cannot edit attributes of datasets removed from disk' );
|
||||
} else if( deleted ){
|
||||
editBtnData.title = _l( 'Undelete dataset to edit attributes' );
|
||||
}
|
||||
|
||||
// disable if still uploading or new
|
||||
} else if( _.contains( [ STATES.UPLOAD, STATES.NEW ], this.model.get( 'state' ) ) ){
|
||||
editBtnData.disabled = true;
|
||||
editBtnData.title = _l( 'This dataset is not yet editable' );
|
||||
}
|
||||
return faIconButton( editBtnData );
|
||||
},
|
||||
|
||||
/** Render icon-button to delete this hda. */
|
||||
_renderDeleteButton : function(){
|
||||
// don't show delete if...
|
||||
if( ( !this.model.get( 'accessible' ) ) ){
|
||||
return null;
|
||||
}
|
||||
|
||||
var self = this,
|
||||
deletedAlready = this.model.isDeletedOrPurged();
|
||||
return faIconButton({
|
||||
title : !deletedAlready? _l( 'Delete' ) : _l( 'Dataset is already deleted' ),
|
||||
disabled : deletedAlready,
|
||||
faIcon : 'fa-times',
|
||||
classes : 'delete-btn',
|
||||
onclick : function() {
|
||||
// ...bler... tooltips being left behind in DOM (hover out never called on deletion)
|
||||
self.$el.find( '.icon-btn.delete-btn' ).trigger( 'mouseout' );
|
||||
self.model[ 'delete' ]();
|
||||
// disable if purged or deleted and explain why in the tooltip
|
||||
if (deleted || purged) {
|
||||
editBtnData.disabled = true;
|
||||
if (purged) {
|
||||
editBtnData.title = _l("Cannot edit attributes of datasets removed from disk");
|
||||
} else if (deleted) {
|
||||
editBtnData.title = _l("Undelete dataset to edit attributes");
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// ......................................................................... details
|
||||
/** In this override, add tags and annotations controls, make the ? dbkey a link to editing page */
|
||||
_renderDetails : function(){
|
||||
//TODO: generalize to be allow different details for each state
|
||||
var $details = _super.prototype._renderDetails.call( this ),
|
||||
state = this.model.get( 'state' );
|
||||
|
||||
if( !this.model.isDeletedOrPurged() && _.contains([ STATES.OK, STATES.FAILED_METADATA ], state ) ){
|
||||
this._renderTags( $details );
|
||||
this._renderAnnotation( $details );
|
||||
this._makeDbkeyEditLink( $details );
|
||||
}
|
||||
|
||||
this._setUpBehaviors( $details );
|
||||
return $details;
|
||||
},
|
||||
|
||||
/**************************************************************************
|
||||
* Render help button to show tool help text without rerunning the tool.
|
||||
* Issue #2100
|
||||
*/
|
||||
_renderToolHelpButton : function() {
|
||||
var datasetID = this.model.attributes.dataset_id;
|
||||
var jobID = this.model.attributes.creating_job;
|
||||
var self = this;
|
||||
|
||||
var parseToolBuild = function(data) {
|
||||
var helpString = '<div id="thdiv-' + datasetID + '" class="toolhelp">'
|
||||
if (data.name && data.help){
|
||||
helpString += '<strong>Tool help for ' + data.name + '</strong><hr/>';
|
||||
helpString += data.help;
|
||||
} else {
|
||||
helpString += '<strong>Tool help is unavailable for this dataset.</strong><hr/>';
|
||||
// disable if still uploading or new
|
||||
} else if (_.contains([STATES.UPLOAD, STATES.NEW], this.model.get("state"))) {
|
||||
editBtnData.disabled = true;
|
||||
editBtnData.title = _l("This dataset is not yet editable");
|
||||
}
|
||||
helpString += '</div>';
|
||||
self.$el.find( '.details' ).append($.parseHTML(helpString));
|
||||
};
|
||||
var parseToolID = function(data) {
|
||||
$.ajax({
|
||||
url: Galaxy.root + 'api/tools/' + data.tool_id + '/build'
|
||||
}).done(function(data){
|
||||
parseToolBuild(data);
|
||||
}).fail(function(){
|
||||
parseToolBuild({})
|
||||
});
|
||||
};
|
||||
if (Galaxy.user.id === null){
|
||||
return null
|
||||
}
|
||||
return faIconButton({
|
||||
title: 'Tool Help',
|
||||
classes: 'icon-btn',
|
||||
href: '#',
|
||||
faIcon: 'fa-question',
|
||||
onclick: function() {
|
||||
var divString = 'thdiv-' + datasetID;
|
||||
if (self.$el.find(".toolhelp").length > 0){
|
||||
self.$el.find(".toolhelp").toggle();
|
||||
} else {
|
||||
$.ajax({
|
||||
url: Galaxy.root + 'api/jobs/' + jobID
|
||||
}).done(function(data){
|
||||
parseToolID(data);
|
||||
}).fail(function(){
|
||||
console.log('Failed at recovering job information from the Galaxy API for job id "' + jobID + '".');
|
||||
});
|
||||
}
|
||||
return faIconButton(editBtnData);
|
||||
},
|
||||
|
||||
/** Render icon-button to delete this hda. */
|
||||
_renderDeleteButton: function() {
|
||||
// don't show delete if...
|
||||
if (!this.model.get("accessible")) {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
},
|
||||
//*************************************************************************
|
||||
|
||||
/** Add less commonly used actions in the details section based on state */
|
||||
_renderSecondaryActions : function(){
|
||||
var actions = _super.prototype._renderSecondaryActions.call( this );
|
||||
switch( this.model.get( 'state' ) ){
|
||||
case STATES.UPLOAD:
|
||||
case STATES.NOT_VIEWABLE:
|
||||
return actions;
|
||||
case STATES.ERROR:
|
||||
// error button comes first
|
||||
actions.unshift( this._renderErrButton() );
|
||||
return actions.concat([ this._renderRerunButton(), this._renderToolHelpButton() ]);
|
||||
case STATES.OK:
|
||||
case STATES.FAILED_METADATA:
|
||||
return actions.concat([ this._renderRerunButton(), this._renderVisualizationsButton(), this._renderToolHelpButton() ]);
|
||||
}
|
||||
return actions.concat([ this._renderRerunButton(), this._renderToolHelpButton() ]);
|
||||
},
|
||||
|
||||
/** Render icon-button to report an error on this dataset to the galaxy admin. */
|
||||
_renderErrButton : function(){
|
||||
return faIconButton({
|
||||
title : _l( 'View or report this error' ),
|
||||
href : this.model.urls.report_error,
|
||||
classes : 'report-error-btn',
|
||||
target : this.linkTarget,
|
||||
faIcon : 'fa-bug'
|
||||
});
|
||||
},
|
||||
|
||||
/** Render icon-button to re-run the job that created this dataset. */
|
||||
_renderRerunButton : function(){
|
||||
var creating_job = this.model.get( 'creating_job' );
|
||||
if( this.model.get( 'rerunnable' ) ){
|
||||
var self = this;
|
||||
var deletedAlready = this.model.isDeletedOrPurged();
|
||||
return faIconButton({
|
||||
title : _l( 'Run this job again' ),
|
||||
href : this.model.urls.rerun,
|
||||
classes : 'rerun-btn',
|
||||
target : this.linkTarget,
|
||||
faIcon : 'fa-refresh',
|
||||
onclick : function( ev ) {
|
||||
ev.preventDefault();
|
||||
Galaxy.router.push( '/', { job_id : creating_job } );
|
||||
title: !deletedAlready ? _l("Delete") : _l("Dataset is already deleted"),
|
||||
disabled: deletedAlready,
|
||||
faIcon: "fa-times",
|
||||
classes: "delete-btn",
|
||||
onclick: function() {
|
||||
// ...bler... tooltips being left behind in DOM (hover out never called on deletion)
|
||||
self.$el.find(".icon-btn.delete-btn").trigger("mouseout");
|
||||
self.model["delete"]();
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
/** Render an icon-button or popupmenu of links based on the applicable visualizations */
|
||||
_renderVisualizationsButton : function(){
|
||||
//TODO: someday - lazyload visualizations
|
||||
var visualizations = this.model.get( 'visualizations' );
|
||||
if( ( this.model.isDeletedOrPurged() )
|
||||
|| ( !this.hasUser )
|
||||
|| ( !this.model.hasData() )
|
||||
|| ( _.isEmpty( visualizations ) ) ){
|
||||
return null;
|
||||
}
|
||||
if( !_.isObject( visualizations[0] ) ){
|
||||
this.warn( 'Visualizations have been switched off' );
|
||||
return null;
|
||||
}
|
||||
// ......................................................................... details
|
||||
/** In this override, add tags and annotations controls, make the ? dbkey a link to editing page */
|
||||
_renderDetails: function() {
|
||||
//TODO: generalize to be allow different details for each state
|
||||
var $details = _super.prototype._renderDetails.call(this);
|
||||
|
||||
var $visualizations = $( this.templates.visualizations( visualizations, this ) );
|
||||
//HACK: need to re-write those directed at galaxy_main with linkTarget
|
||||
$visualizations.find( '[target="galaxy_main"]').attr( 'target', this.linkTarget );
|
||||
// use addBack here to include the root $visualizations elem (for the case of 1 visualization)
|
||||
this._addScratchBookFn( $visualizations.find( '.visualization-link' ).addBack( '.visualization-link' ) );
|
||||
return $visualizations;
|
||||
},
|
||||
var state = this.model.get("state");
|
||||
|
||||
/** add scratchbook functionality to visualization links */
|
||||
_addScratchBookFn : function( $links ){
|
||||
var li = this;
|
||||
$links.click( function( ev ){
|
||||
if( Galaxy.frame && Galaxy.frame.active ){
|
||||
Galaxy.frame.add({
|
||||
title : 'Visualization',
|
||||
url : $( this ).attr( 'href' )
|
||||
});
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
if (!this.model.isDeletedOrPurged() && _.contains([STATES.OK, STATES.FAILED_METADATA], state)) {
|
||||
this._renderTags($details);
|
||||
this._renderAnnotation($details);
|
||||
this._makeDbkeyEditLink($details);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
//TODO: if possible move these to readonly view - but display the owner's tags/annotation (no edit)
|
||||
/** Render the tags list/control */
|
||||
_renderTags : function( $where ){
|
||||
if( !this.hasUser ){ return; }
|
||||
var view = this;
|
||||
this.tagsEditor = new TAGS.TagsEditor({
|
||||
model : this.model,
|
||||
el : $where.find( '.tags-display' ),
|
||||
onshowFirstTime : function(){ this.render(); },
|
||||
// persist state on the hda view (and not the editor) since these are currently re-created each time
|
||||
onshow : function(){ view.tagsEditorShown = true; },
|
||||
onhide : function(){ view.tagsEditorShown = false; },
|
||||
$activator : faIconButton({
|
||||
title : _l( 'Edit dataset tags' ),
|
||||
classes : 'tag-btn',
|
||||
faIcon : 'fa-tags'
|
||||
}).appendTo( $where.find( '.actions .right' ) )
|
||||
});
|
||||
if( this.tagsEditorShown ){ this.tagsEditor.toggle( true ); }
|
||||
},
|
||||
this._setUpBehaviors($details);
|
||||
return $details;
|
||||
},
|
||||
|
||||
/** Render the annotation display/control */
|
||||
_renderAnnotation : function( $where ){
|
||||
if( !this.hasUser ){ return; }
|
||||
var view = this;
|
||||
this.annotationEditor = new ANNOTATIONS.AnnotationEditor({
|
||||
model : this.model,
|
||||
el : $where.find( '.annotation-display' ),
|
||||
onshowFirstTime : function(){ this.render(); },
|
||||
// persist state on the hda view (and not the editor) since these are currently re-created each time
|
||||
onshow : function(){ view.annotationEditorShown = true; },
|
||||
onhide : function(){ view.annotationEditorShown = false; },
|
||||
$activator : faIconButton({
|
||||
title : _l( 'Edit dataset annotation' ),
|
||||
classes : 'annotate-btn',
|
||||
faIcon : 'fa-comment'
|
||||
}).appendTo( $where.find( '.actions .right' ) )
|
||||
});
|
||||
if( this.annotationEditorShown ){ this.annotationEditor.toggle( true ); }
|
||||
},
|
||||
/**************************************************************************
|
||||
* Render help button to show tool help text without rerunning the tool.
|
||||
* Issue #2100
|
||||
*/
|
||||
_renderToolHelpButton: function() {
|
||||
var datasetID = this.model.attributes.dataset_id;
|
||||
var jobID = this.model.attributes.creating_job;
|
||||
var self = this;
|
||||
|
||||
/** If the format/dbkey/genome_build isn't set, make the display a link to the edit page */
|
||||
_makeDbkeyEditLink : function( $details ){
|
||||
// make the dbkey a link to editing
|
||||
if( this.model.get( 'metadata_dbkey' ) === '?'
|
||||
&& !this.model.isDeletedOrPurged() ){
|
||||
var editableDbkey = $( '<a class="value">?</a>' )
|
||||
.attr( 'href', this.model.urls.edit )
|
||||
.attr( 'target', this.linkTarget );
|
||||
$details.find( '.dbkey .value' ).replaceWith( editableDbkey );
|
||||
var parseToolBuild = data => {
|
||||
var helpString = `<div id="thdiv-${datasetID}" class="toolhelp">`;
|
||||
if (data.name && data.help) {
|
||||
helpString += `<strong>Tool help for ${data.name}</strong><hr/>`;
|
||||
helpString += data.help;
|
||||
} else {
|
||||
helpString += "<strong>Tool help is unavailable for this dataset.</strong><hr/>";
|
||||
}
|
||||
helpString += "</div>";
|
||||
self.$el.find(".details").append($.parseHTML(helpString));
|
||||
};
|
||||
var parseToolID = data => {
|
||||
$.ajax({
|
||||
url: `${Galaxy.root}api/tools/${data.tool_id}/build`
|
||||
})
|
||||
.done(data => {
|
||||
parseToolBuild(data);
|
||||
})
|
||||
.fail(() => {
|
||||
parseToolBuild({});
|
||||
});
|
||||
};
|
||||
if (Galaxy.user.id === null) {
|
||||
return null;
|
||||
}
|
||||
return faIconButton({
|
||||
title: _l("Tool Help"),
|
||||
classes: "icon-btn",
|
||||
href: "#",
|
||||
faIcon: "fa-question",
|
||||
onclick: function() {
|
||||
var divString = `thdiv-${datasetID}`;
|
||||
if (self.$el.find(".toolhelp").length > 0) {
|
||||
self.$el.find(".toolhelp").toggle();
|
||||
} else {
|
||||
$.ajax({
|
||||
url: `${Galaxy.root}api/jobs/${jobID}`
|
||||
})
|
||||
.done(data => {
|
||||
parseToolID(data);
|
||||
})
|
||||
.fail(() => {
|
||||
console.log(
|
||||
`Failed at recovering job information from the Galaxy API for job id "${jobID}".`
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
//*************************************************************************
|
||||
|
||||
/** Add less commonly used actions in the details section based on state */
|
||||
_renderSecondaryActions: function() {
|
||||
var actions = _super.prototype._renderSecondaryActions.call(this);
|
||||
switch (this.model.get("state")) {
|
||||
case STATES.UPLOAD:
|
||||
case STATES.NOT_VIEWABLE:
|
||||
return actions;
|
||||
case STATES.ERROR:
|
||||
// error button comes first
|
||||
actions.unshift(this._renderErrButton());
|
||||
return actions.concat([this._renderRerunButton(), this._renderToolHelpButton()]);
|
||||
case STATES.OK:
|
||||
case STATES.FAILED_METADATA:
|
||||
return actions.concat([
|
||||
this._renderRerunButton(),
|
||||
this._renderVisualizationsButton(),
|
||||
this._renderToolHelpButton()
|
||||
]);
|
||||
}
|
||||
return actions.concat([this._renderRerunButton(), this._renderToolHelpButton()]);
|
||||
},
|
||||
|
||||
/** Render icon-button to report an error on this dataset to the galaxy admin. */
|
||||
_renderErrButton: function() {
|
||||
var self = this;
|
||||
return faIconButton({
|
||||
title: _l("View or report this error"),
|
||||
href: `${Galaxy.root}datasets/error?dataset_id=${this.model.attributes.id}`,
|
||||
classes: "report-error-btn",
|
||||
faIcon: "fa-bug",
|
||||
onclick: function(ev) {
|
||||
if (Galaxy.router) {
|
||||
ev.preventDefault();
|
||||
Galaxy.router.push("datasets/error", {
|
||||
dataset_id: self.model.attributes.id
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/** Render icon-button to re-run the job that created this dataset. */
|
||||
_renderRerunButton: function() {
|
||||
var creating_job = this.model.get("creating_job");
|
||||
if (this.model.get("rerunnable")) {
|
||||
return faIconButton({
|
||||
title: _l("Run this job again"),
|
||||
href: this.model.urls.rerun,
|
||||
classes: "rerun-btn",
|
||||
target: this.linkTarget,
|
||||
faIcon: "fa-refresh",
|
||||
onclick: function(ev) {
|
||||
if (Galaxy.router) {
|
||||
ev.preventDefault();
|
||||
Galaxy.router.push("/", {
|
||||
job_id: creating_job
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/** Render an icon-button or popupmenu of links based on the applicable visualizations */
|
||||
_renderVisualizationsButton: function() {
|
||||
//TODO: someday - lazyload visualizations
|
||||
var visualizations = this.model.get("visualizations");
|
||||
if (this.model.isDeletedOrPurged() || !this.hasUser || !this.model.hasData() || _.isEmpty(visualizations)) {
|
||||
return null;
|
||||
}
|
||||
if (!_.isObject(visualizations[0])) {
|
||||
this.warn("Visualizations have been switched off");
|
||||
return null;
|
||||
}
|
||||
|
||||
var $visualizations = $(this.templates.visualizations(visualizations, this));
|
||||
//HACK: need to re-write those directed at galaxy_main with linkTarget
|
||||
$visualizations.find('[target="galaxy_main"]').attr("target", this.linkTarget);
|
||||
// use addBack here to include the root $visualizations elem (for the case of 1 visualization)
|
||||
this._addScratchBookFn($visualizations.find(".visualization-link").addBack(".visualization-link"));
|
||||
return $visualizations;
|
||||
},
|
||||
|
||||
/** add scratchbook functionality to visualization links */
|
||||
_addScratchBookFn: function($links) {
|
||||
var li = this;
|
||||
$links.click(function(ev) {
|
||||
if (Galaxy.frame && Galaxy.frame.active) {
|
||||
Galaxy.frame.add({
|
||||
title: _l("Visualization"),
|
||||
url: $(this).attr("href")
|
||||
});
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
//TODO: if possible move these to readonly view - but display the owner's tags/annotation (no edit)
|
||||
/** Render the tags list/control */
|
||||
_renderTags: function($where) {
|
||||
if (!this.hasUser) {
|
||||
return;
|
||||
}
|
||||
var view = this;
|
||||
this.tagsEditor = new TAGS.TagsEditor({
|
||||
model: this.model,
|
||||
el: $where.find(".tags-display"),
|
||||
onshowFirstTime: function() {
|
||||
this.render();
|
||||
},
|
||||
// persist state on the hda view (and not the editor) since these are currently re-created each time
|
||||
onshow: function() {
|
||||
view.tagsEditorShown = true;
|
||||
},
|
||||
onhide: function() {
|
||||
view.tagsEditorShown = false;
|
||||
},
|
||||
$activator: faIconButton({
|
||||
title: _l("Edit dataset tags"),
|
||||
classes: "tag-btn",
|
||||
faIcon: "fa-tags"
|
||||
}).appendTo($where.find(".actions .right"))
|
||||
});
|
||||
if (this.tagsEditorShown) {
|
||||
this.tagsEditor.toggle(true);
|
||||
}
|
||||
},
|
||||
|
||||
/** Render the annotation display/control */
|
||||
_renderAnnotation: function($where) {
|
||||
if (!this.hasUser) {
|
||||
return;
|
||||
}
|
||||
var view = this;
|
||||
this.annotationEditor = new ANNOTATIONS.AnnotationEditor({
|
||||
model: this.model,
|
||||
el: $where.find(".annotation-display"),
|
||||
onshowFirstTime: function() {
|
||||
this.render();
|
||||
},
|
||||
// persist state on the hda view (and not the editor) since these are currently re-created each time
|
||||
onshow: function() {
|
||||
view.annotationEditorShown = true;
|
||||
},
|
||||
onhide: function() {
|
||||
view.annotationEditorShown = false;
|
||||
},
|
||||
$activator: faIconButton({
|
||||
title: _l("Edit dataset annotation"),
|
||||
classes: "annotate-btn",
|
||||
faIcon: "fa-comment"
|
||||
}).appendTo($where.find(".actions .right"))
|
||||
});
|
||||
if (this.annotationEditorShown) {
|
||||
this.annotationEditor.toggle(true);
|
||||
}
|
||||
},
|
||||
|
||||
/** If the format/dbkey/genome_build isn't set, make the display a link to the edit page */
|
||||
_makeDbkeyEditLink: function($details) {
|
||||
// make the dbkey a link to editing
|
||||
if (this.model.get("metadata_dbkey") === "?" && !this.model.isDeletedOrPurged()) {
|
||||
var editableDbkey = $('<a class="value">?</a>')
|
||||
.attr("href", this.model.urls.edit)
|
||||
.attr("target", "_top");
|
||||
$details.find(".dbkey .value").replaceWith(editableDbkey);
|
||||
}
|
||||
},
|
||||
|
||||
// ......................................................................... events
|
||||
/** event map */
|
||||
events: _.extend(_.clone(_super.prototype.events), {
|
||||
"click .undelete-link": "_clickUndeleteLink",
|
||||
"click .purge-link": "_clickPurgeLink",
|
||||
|
||||
"click .edit-btn": function(ev) {
|
||||
this.trigger("edit", this, ev);
|
||||
},
|
||||
"click .delete-btn": function(ev) {
|
||||
this.trigger("delete", this, ev);
|
||||
},
|
||||
"click .rerun-btn": function(ev) {
|
||||
this.trigger("rerun", this, ev);
|
||||
},
|
||||
"click .report-err-btn": function(ev) {
|
||||
this.trigger("report-err", this, ev);
|
||||
},
|
||||
"click .visualization-btn": function(ev) {
|
||||
this.trigger("visualize", this, ev);
|
||||
},
|
||||
"click .dbkey a": function(ev) {
|
||||
this.trigger("edit", this, ev);
|
||||
}
|
||||
}),
|
||||
|
||||
/** listener for item undelete (in the messages section) */
|
||||
_clickUndeleteLink: function(ev) {
|
||||
this.model.undelete();
|
||||
return false;
|
||||
},
|
||||
|
||||
/** listener for item purge (in the messages section) */
|
||||
_clickPurgeLink: function(ev) {
|
||||
if (confirm(_l("This will permanently remove the data in your dataset. Are you sure?"))) {
|
||||
this.model.purge();
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
// ......................................................................... misc
|
||||
/** string rep */
|
||||
toString: function() {
|
||||
var modelString = this.model ? `${this.model}` : "(no model)";
|
||||
return `HDAEditView(${modelString})`;
|
||||
}
|
||||
},
|
||||
|
||||
// ......................................................................... events
|
||||
/** event map */
|
||||
events : _.extend( _.clone( _super.prototype.events ), {
|
||||
'click .undelete-link' : '_clickUndeleteLink',
|
||||
'click .purge-link' : '_clickPurgeLink',
|
||||
|
||||
'click .edit-btn' : function( ev ){ this.trigger( 'edit', this, ev ); },
|
||||
'click .delete-btn' : function( ev ){ this.trigger( 'delete', this, ev ); },
|
||||
'click .rerun-btn' : function( ev ){ this.trigger( 'rerun', this, ev ); },
|
||||
'click .report-err-btn' : function( ev ){ this.trigger( 'report-err', this, ev ); },
|
||||
'click .visualization-btn' : function( ev ){ this.trigger( 'visualize', this, ev ); },
|
||||
'click .dbkey a' : function( ev ){ this.trigger( 'edit', this, ev ); }
|
||||
}),
|
||||
|
||||
/** listener for item undelete (in the messages section) */
|
||||
_clickUndeleteLink : function( ev ){
|
||||
this.model.undelete();
|
||||
return false;
|
||||
},
|
||||
|
||||
/** listener for item purge (in the messages section) */
|
||||
_clickPurgeLink : function( ev ){
|
||||
if( confirm( _l( 'This will permanently remove the data in your dataset. Are you sure?' ) ) ){
|
||||
this.model.purge();
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
// ......................................................................... misc
|
||||
/** string rep */
|
||||
toString : function(){
|
||||
var modelString = ( this.model )?( this.model + '' ):( '(no model)' );
|
||||
return 'HDAEditView(' + modelString + ')';
|
||||
}
|
||||
});
|
||||
|
||||
);
|
||||
|
||||
// ............................................................................ TEMPLATES
|
||||
/** underscore templates */
|
||||
DatasetListItemEdit.prototype.templates = (function(){
|
||||
|
||||
var warnings = _.extend( {}, _super.prototype.templates.warnings, {
|
||||
failed_metadata : BASE_MVC.wrapTemplate([
|
||||
// in this override, provide a link to the edit page
|
||||
'<% if( dataset.state === "failed_metadata" ){ %>',
|
||||
DatasetListItemEdit.prototype.templates = (() => {
|
||||
var warnings = _.extend({}, _super.prototype.templates.warnings, {
|
||||
failed_metadata: BASE_MVC.wrapTemplate(
|
||||
[
|
||||
// in this override, provide a link to the edit page
|
||||
'<% if( dataset.state === "failed_metadata" ){ %>',
|
||||
'<div class="failed_metadata-warning warningmessagesmall">',
|
||||
_l( 'An error occurred setting the metadata for this dataset' ),
|
||||
'<br /><a href="<%- dataset.urls.edit %>" target="<%- view.linkTarget %>">',
|
||||
_l( 'Set it manually or retry auto-detection' ),
|
||||
'</a>',
|
||||
'</div>',
|
||||
'<% } %>'
|
||||
], 'dataset' ),
|
||||
_l("An error occurred setting the metadata for this dataset"),
|
||||
'<br /><a href="<%- dataset.urls.edit %>" target="_top">',
|
||||
_l("Set it manually or retry auto-detection"),
|
||||
"</a>",
|
||||
"</div>",
|
||||
"<% } %>"
|
||||
],
|
||||
"dataset"
|
||||
),
|
||||
|
||||
deleted : BASE_MVC.wrapTemplate([
|
||||
// in this override, provide links to undelete or purge the dataset
|
||||
'<% if( dataset.deleted && !dataset.purged ){ %>',
|
||||
deleted: BASE_MVC.wrapTemplate(
|
||||
[
|
||||
// in this override, provide links to undelete or purge the dataset
|
||||
"<% if( dataset.deleted && !dataset.purged ){ %>",
|
||||
// deleted not purged
|
||||
'<div class="deleted-msg warningmessagesmall">',
|
||||
_l( 'This dataset has been deleted' ),
|
||||
'<br /><a class="undelete-link" href="javascript:void(0);">', _l( 'Undelete it' ), '</a>',
|
||||
'<% if( view.purgeAllowed ){ %>',
|
||||
'<br /><a class="purge-link" href="javascript:void(0);">',
|
||||
_l( 'Permanently remove it from disk' ),
|
||||
'</a>',
|
||||
'<% } %>',
|
||||
'</div>',
|
||||
'<% } %>'
|
||||
], 'dataset' )
|
||||
_l("This dataset has been deleted"),
|
||||
'<br /><a class="undelete-link" href="javascript:void(0);">',
|
||||
_l("Undelete it"),
|
||||
"</a>",
|
||||
"<% if( view.purgeAllowed ){ %>",
|
||||
'<br /><a class="purge-link" href="javascript:void(0);">',
|
||||
_l("Permanently remove it from disk"),
|
||||
"</a>",
|
||||
"<% } %>",
|
||||
"</div>",
|
||||
"<% } %>"
|
||||
],
|
||||
"dataset"
|
||||
)
|
||||
});
|
||||
|
||||
var visualizationsTemplate = BASE_MVC.wrapTemplate([
|
||||
'<% if( visualizations.length === 1 ){ %>',
|
||||
var visualizationsTemplate = BASE_MVC.wrapTemplate(
|
||||
[
|
||||
"<% if( visualizations.length === 1 ){ %>",
|
||||
'<a class="visualization-link icon-btn" href="<%- visualizations[0].href %>"',
|
||||
' target="<%- visualizations[0].target %>" title="', _l( 'Visualize in' ),
|
||||
' <%- visualizations[0].html %>">',
|
||||
'<span class="fa fa-bar-chart-o"></span>',
|
||||
'</a>',
|
||||
' target="<%- visualizations[0].target %>" title="',
|
||||
_l("Visualize in"),
|
||||
' <%- visualizations[0].html %>">',
|
||||
'<span class="fa fa-bar-chart-o"></span>',
|
||||
"</a>",
|
||||
|
||||
'<% } else { %>',
|
||||
"<% } else { %>",
|
||||
'<div class="visualizations-dropdown dropdown icon-btn">',
|
||||
'<a data-toggle="dropdown" title="', _l( 'Visualize' ), '">',
|
||||
'<span class="fa fa-bar-chart-o"></span>',
|
||||
'</a>',
|
||||
'<ul class="dropdown-menu" role="menu">',
|
||||
'<% _.each( visualizations, function( visualization ){ %>',
|
||||
'<li><a class="visualization-link" href="<%- visualization.href %>"',
|
||||
' target="<%- visualization.target %>">',
|
||||
'<%- visualization.html %>',
|
||||
'</a></li>',
|
||||
'<% }); %>',
|
||||
'</ul>',
|
||||
'</div>',
|
||||
'<% } %>'
|
||||
], 'visualizations' );
|
||||
'<a data-toggle="dropdown" title="',
|
||||
_l("Visualize"),
|
||||
'">',
|
||||
'<span class="fa fa-bar-chart-o"></span>',
|
||||
"</a>",
|
||||
'<ul class="dropdown-menu" role="menu">',
|
||||
"<% _.each( visualizations, function( visualization ){ %>",
|
||||
'<li><a class="visualization-link" href="<%- visualization.href %>"',
|
||||
' target="<%- visualization.target %>">',
|
||||
"<%- visualization.html %>",
|
||||
"</a></li>",
|
||||
"<% }); %>",
|
||||
"</ul>",
|
||||
"</div>",
|
||||
"<% } %>"
|
||||
],
|
||||
"visualizations"
|
||||
);
|
||||
|
||||
return _.extend( {}, _super.prototype.templates, {
|
||||
warnings : warnings,
|
||||
visualizations : visualizationsTemplate
|
||||
return _.extend({}, _super.prototype.templates, {
|
||||
warnings: warnings,
|
||||
visualizations: visualizationsTemplate
|
||||
});
|
||||
}());
|
||||
|
||||
})();
|
||||
|
||||
//==============================================================================
|
||||
return {
|
||||
DatasetListItemEdit : DatasetListItemEdit
|
||||
};
|
||||
});
|
||||
export default {
|
||||
DatasetListItemEdit: DatasetListItemEdit
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,12 +1,9 @@
|
||||
define([
|
||||
"mvc/list/list-view",
|
||||
"mvc/dataset/dataset-li",
|
||||
"mvc/base-mvc",
|
||||
"utils/localization"
|
||||
], function( LIST_VIEW, DATASET_LI, BASE_MVC, _l ){
|
||||
'use strict';
|
||||
import LIST_VIEW from "mvc/list/list-view";
|
||||
import DATASET_LI from "mvc/dataset/dataset-li";
|
||||
import BASE_MVC from "mvc/base-mvc";
|
||||
import _l from "utils/localization";
|
||||
|
||||
var logNamespace = 'dataset';
|
||||
var logNamespace = "dataset";
|
||||
/* =============================================================================
|
||||
TODO:
|
||||
|
||||
@@ -15,32 +12,32 @@ var _super = LIST_VIEW.ListPanel;
|
||||
/** @class non-editable, read-only View/Controller for a list of datasets.
|
||||
*/
|
||||
var DatasetList = _super.extend(
|
||||
/** @lends DatasetList.prototype */{
|
||||
_logNamespace : logNamespace,
|
||||
/** @lends DatasetList.prototype */ {
|
||||
_logNamespace: logNamespace,
|
||||
|
||||
/** class to use for constructing the sub-views */
|
||||
viewClass : DATASET_LI.DatasetListItemView,
|
||||
className : _super.prototype.className + ' dataset-list',
|
||||
/** class to use for constructing the sub-views */
|
||||
viewClass: DATASET_LI.DatasetListItemView,
|
||||
className: `${_super.prototype.className} dataset-list`,
|
||||
|
||||
/** string to no hdas match the search terms */
|
||||
noneFoundMsg : _l( 'No matching datasets found' ),
|
||||
/** string to no hdas match the search terms */
|
||||
noneFoundMsg: _l("No matching datasets found"),
|
||||
|
||||
// ......................................................................... SET UP
|
||||
/** Set up the view, set up storage, bind listeners to HistoryContents events
|
||||
* @param {Object} attributes optional settings for the panel
|
||||
*/
|
||||
initialize : function( attributes ){
|
||||
_super.prototype.initialize.call( this, attributes );
|
||||
},
|
||||
// ......................................................................... SET UP
|
||||
/** Set up the view, set up storage, bind listeners to HistoryContents events
|
||||
* @param {Object} attributes optional settings for the panel
|
||||
*/
|
||||
initialize: function(attributes) {
|
||||
_super.prototype.initialize.call(this, attributes);
|
||||
},
|
||||
|
||||
/** Return a string rep of the history */
|
||||
toString : function(){
|
||||
return 'DatasetList(' + this.collection + ')';
|
||||
/** Return a string rep of the history */
|
||||
toString: function() {
|
||||
return `DatasetList(${this.collection})`;
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
//==============================================================================
|
||||
return {
|
||||
DatasetList : DatasetList
|
||||
};
|
||||
});
|
||||
export default {
|
||||
DatasetList: DatasetList
|
||||
};
|
||||
|
||||
@@ -1,338 +1,348 @@
|
||||
define([
|
||||
"mvc/dataset/states",
|
||||
"mvc/base-mvc",
|
||||
"utils/localization"
|
||||
], function( STATES, BASE_MVC, _l ){
|
||||
'use strict';
|
||||
import STATES from "mvc/dataset/states";
|
||||
import BASE_MVC from "mvc/base-mvc";
|
||||
import _l from "utils/localization";
|
||||
|
||||
var logNamespace = 'dataset';
|
||||
var logNamespace = "dataset";
|
||||
//==============================================================================
|
||||
var searchableMixin = BASE_MVC.SearchableModelMixin;
|
||||
/** @class base model for any DatasetAssociation (HDAs, LDDAs, DatasetCollectionDAs).
|
||||
* No knowledge of what type (HDA/LDDA/DCDA) should be needed here.
|
||||
* The DA's are made searchable (by attribute) by mixing in SearchableModelMixin.
|
||||
*/
|
||||
var DatasetAssociation = Backbone.Model
|
||||
.extend( BASE_MVC.LoggableMixin )
|
||||
.extend( BASE_MVC.mixin( searchableMixin, /** @lends DatasetAssociation.prototype */{
|
||||
_logNamespace : logNamespace,
|
||||
var DatasetAssociation = Backbone.Model.extend(BASE_MVC.LoggableMixin).extend(
|
||||
BASE_MVC.mixin(
|
||||
searchableMixin,
|
||||
/** @lends DatasetAssociation.prototype */ {
|
||||
_logNamespace: logNamespace,
|
||||
|
||||
/** default attributes for a model */
|
||||
defaults : {
|
||||
state : STATES.NEW,
|
||||
deleted : false,
|
||||
purged : false,
|
||||
name : '(unnamed dataset)',
|
||||
accessible : true,
|
||||
// sniffed datatype (sam, tabular, bed, etc.)
|
||||
data_type : '',
|
||||
file_ext : '',
|
||||
file_size : 0,
|
||||
/** default attributes for a model */
|
||||
defaults: {
|
||||
state: STATES.NEW,
|
||||
deleted: false,
|
||||
purged: false,
|
||||
name: "(unnamed dataset)",
|
||||
accessible: true,
|
||||
// sniffed datatype (sam, tabular, bed, etc.)
|
||||
data_type: "",
|
||||
file_ext: "",
|
||||
file_size: 0,
|
||||
|
||||
// array of associated file types (eg. [ 'bam_index', ... ])
|
||||
meta_files : [],
|
||||
// array of associated file types (eg. [ 'bam_index', ... ])
|
||||
meta_files: [],
|
||||
|
||||
misc_blurb : '',
|
||||
misc_info : '',
|
||||
misc_blurb: "",
|
||||
misc_info: "",
|
||||
|
||||
tags : []
|
||||
// do NOT default on annotation, as this default is valid and will be passed on 'save'
|
||||
// which is incorrect behavior when the model is only partially fetched (annos are not passed in summary data)
|
||||
//annotation : ''
|
||||
},
|
||||
tags: []
|
||||
// do NOT default on annotation, as this default is valid and will be passed on 'save'
|
||||
// which is incorrect behavior when the model is only partially fetched (annos are not passed in summary data)
|
||||
//annotation : ''
|
||||
},
|
||||
|
||||
/** instance vars and listeners */
|
||||
initialize : function( attributes, options ){
|
||||
this.debug( this + '(Dataset).initialize', attributes, options );
|
||||
/** instance vars and listeners */
|
||||
initialize: function(attributes, options) {
|
||||
this.debug(`${this}(Dataset).initialize`, attributes, options);
|
||||
|
||||
//!! this state is not in trans.app.model.Dataset.states - set it here -
|
||||
if( !this.get( 'accessible' ) ){
|
||||
this.set( 'state', STATES.NOT_VIEWABLE );
|
||||
}
|
||||
//!! this state is not in trans.app.model.Dataset.states - set it here -
|
||||
if (!this.get("accessible")) {
|
||||
this.set("state", STATES.NOT_VIEWABLE);
|
||||
}
|
||||
|
||||
/** Datasets rely/use some web controllers - have the model generate those URLs on startup */
|
||||
this.urls = this._generateUrls();
|
||||
/** Datasets rely/use some web controllers - have the model generate those URLs on startup */
|
||||
this.urls = this._generateUrls();
|
||||
|
||||
this._setUpListeners();
|
||||
},
|
||||
this._setUpListeners();
|
||||
},
|
||||
|
||||
/** returns misc. web urls for rendering things like re-run, display, etc. */
|
||||
_generateUrls : function(){
|
||||
var id = this.get( 'id' );
|
||||
if( !id ){ return {}; }
|
||||
var urls = {
|
||||
'purge' : 'datasets/' + id + '/purge_async',
|
||||
'display' : 'datasets/' + id + '/display/?preview=True',
|
||||
'edit' : 'datasets/' + id + '/edit',
|
||||
'download' : 'datasets/' + id + '/display' + this._downloadQueryParameters(),
|
||||
'report_error' : 'dataset/errors?id=' + id,
|
||||
'rerun' : 'tool_runner/rerun?id=' + id,
|
||||
'show_params' : 'datasets/' + id + '/show_params',
|
||||
'visualization' : 'visualization',
|
||||
'meta_download' : 'dataset/get_metadata_file?hda_id=' + id + '&metadata_name='
|
||||
};
|
||||
_.each( urls, function( value, key ){
|
||||
urls[ key ] = Galaxy.root + value;
|
||||
});
|
||||
this.urls = urls;
|
||||
return urls;
|
||||
},
|
||||
/** returns misc. web urls for rendering things like re-run, display, etc. */
|
||||
_generateUrls: function() {
|
||||
var id = this.get("id");
|
||||
if (!id) {
|
||||
return {};
|
||||
}
|
||||
var urls = {
|
||||
purge: `datasets/${id}/purge_async`,
|
||||
display: `datasets/${id}/display/?preview=True`,
|
||||
edit: `datasets/edit?dataset_id=${id}`,
|
||||
download: `datasets/${id}/display${this._downloadQueryParameters()}`,
|
||||
report_error: `dataset/errors?id=${id}`,
|
||||
rerun: `tool_runner/rerun?id=${id}`,
|
||||
show_params: `datasets/${id}/show_params`,
|
||||
visualization: "visualization",
|
||||
meta_download: `dataset/get_metadata_file?hda_id=${id}&metadata_name=`
|
||||
};
|
||||
_.each(urls, (value, key) => {
|
||||
urls[key] = Galaxy.root + value;
|
||||
});
|
||||
this.urls = urls;
|
||||
return urls;
|
||||
},
|
||||
|
||||
_downloadQueryParameters : function() {
|
||||
return '?to_ext=' + this.get( 'file_ext' );
|
||||
},
|
||||
_downloadQueryParameters: function() {
|
||||
return `?to_ext=${this.get("file_ext")}`;
|
||||
},
|
||||
|
||||
/** set up any event listeners
|
||||
* event: state:ready fired when this DA moves into/is already in a ready state
|
||||
*/
|
||||
_setUpListeners : function(){
|
||||
// if the state has changed and the new state is a ready state, fire an event
|
||||
this.on( 'change:state', function( currModel, newState ){
|
||||
this.log( this + ' has changed state:', currModel, newState );
|
||||
if( this.inReadyState() ){
|
||||
this.trigger( 'state:ready', currModel, newState, this.previous( 'state' ) );
|
||||
/** set up any event listeners
|
||||
* event: state:ready fired when this DA moves into/is already in a ready state
|
||||
*/
|
||||
_setUpListeners: function() {
|
||||
// if the state has changed and the new state is a ready state, fire an event
|
||||
this.on("change:state", function(currModel, newState) {
|
||||
this.log(`${this} has changed state:`, currModel, newState);
|
||||
if (this.inReadyState()) {
|
||||
this.trigger("state:ready", currModel, newState, this.previous("state"));
|
||||
}
|
||||
});
|
||||
// the download url (currently) relies on having a correct file extension
|
||||
this.on("change:id change:file_ext", function(currModel) {
|
||||
this._generateUrls();
|
||||
});
|
||||
},
|
||||
|
||||
// ........................................................................ common queries
|
||||
/** override to add urls */
|
||||
toJSON: function() {
|
||||
var json = Backbone.Model.prototype.toJSON.call(this);
|
||||
//console.warn( 'returning json?' );
|
||||
//return json;
|
||||
return _.extend(json, {
|
||||
urls: this.urls
|
||||
});
|
||||
},
|
||||
|
||||
/** Is this dataset deleted or purged? */
|
||||
isDeletedOrPurged: function() {
|
||||
return this.get("deleted") || this.get("purged");
|
||||
},
|
||||
|
||||
/** Is this dataset in a 'ready' state; where 'Ready' states are states where no
|
||||
* processing (for the ds) is left to do on the server.
|
||||
*/
|
||||
inReadyState: function() {
|
||||
var ready = _.contains(STATES.READY_STATES, this.get("state"));
|
||||
return this.isDeletedOrPurged() || ready;
|
||||
},
|
||||
|
||||
/** Does this model already contain detailed data (as opposed to just summary level data)? */
|
||||
hasDetails: function() {
|
||||
// if it's inaccessible assume it has everything it needs
|
||||
if (!this.get("accessible")) {
|
||||
return true;
|
||||
}
|
||||
return this.has("annotation");
|
||||
},
|
||||
|
||||
/** Convenience function to match dataset.has_data. */
|
||||
hasData: function() {
|
||||
return this.get("file_size") > 0;
|
||||
},
|
||||
|
||||
// ........................................................................ ajax
|
||||
fetch: function(options) {
|
||||
var dataset = this;
|
||||
return Backbone.Model.prototype.fetch.call(this, options).always(() => {
|
||||
dataset._generateUrls();
|
||||
});
|
||||
},
|
||||
|
||||
/** override to use actual Dates objects for create/update times */
|
||||
parse: function(response, options) {
|
||||
var parsed = Backbone.Model.prototype.parse.call(this, response, options);
|
||||
if (parsed.create_time) {
|
||||
parsed.create_time = new Date(parsed.create_time);
|
||||
}
|
||||
if (parsed.update_time) {
|
||||
parsed.update_time = new Date(parsed.update_time);
|
||||
}
|
||||
return parsed;
|
||||
},
|
||||
|
||||
/** override to wait by default */
|
||||
save: function(attrs, options) {
|
||||
options = options || {};
|
||||
options.wait = _.isUndefined(options.wait) ? true : options.wait;
|
||||
return Backbone.Model.prototype.save.call(this, attrs, options);
|
||||
},
|
||||
|
||||
//NOTE: subclasses of DA's will need to implement url and urlRoot in order to have these work properly
|
||||
/** save this dataset, _Mark_ing it as deleted (just a flag) */
|
||||
delete: function(options) {
|
||||
if (this.get("deleted")) {
|
||||
return jQuery.when();
|
||||
}
|
||||
return this.save({ deleted: true }, options);
|
||||
},
|
||||
/** save this dataset, _Mark_ing it as undeleted */
|
||||
undelete: function(options) {
|
||||
if (!this.get("deleted") || this.get("purged")) {
|
||||
return jQuery.when();
|
||||
}
|
||||
return this.save({ deleted: false }, options);
|
||||
},
|
||||
|
||||
/** remove the file behind this dataset from the filesystem (if permitted) */
|
||||
purge: function _purge(options) {
|
||||
//TODO: use, override model.destroy, HDA.delete({ purge: true })
|
||||
if (this.get("purged")) {
|
||||
return jQuery.when();
|
||||
}
|
||||
options = options || {};
|
||||
options.url = this.urls.purge;
|
||||
|
||||
//TODO: ideally this would be a DELETE call to the api
|
||||
// using purge async for now
|
||||
var hda = this;
|
||||
|
||||
var xhr = jQuery.ajax(options);
|
||||
xhr.done((message, status, responseObj) => {
|
||||
hda.set({ deleted: true, purged: true });
|
||||
});
|
||||
xhr.fail((xhr, status, message) => {
|
||||
// Exception messages are hidden within error page including: '...not allowed in this Galaxy instance.'
|
||||
// unbury and re-add to xhr
|
||||
var error = _l("Unable to purge dataset");
|
||||
var messageBuriedInUnfortunatelyFormattedError =
|
||||
"Removal of datasets by users " + "is not allowed in this Galaxy instance";
|
||||
if (xhr.responseJSON && xhr.responseJSON.error) {
|
||||
error = xhr.responseJSON.error;
|
||||
} else if (xhr.responseText.indexOf(messageBuriedInUnfortunatelyFormattedError) !== -1) {
|
||||
error = messageBuriedInUnfortunatelyFormattedError;
|
||||
}
|
||||
xhr.responseText = error;
|
||||
hda.trigger("error", hda, xhr, options, _l(error), {
|
||||
error: error
|
||||
});
|
||||
});
|
||||
return xhr;
|
||||
},
|
||||
|
||||
// ........................................................................ searching
|
||||
/** what attributes of an HDA will be used in a text search */
|
||||
searchAttributes: ["name", "file_ext", "genome_build", "misc_blurb", "misc_info", "annotation", "tags"],
|
||||
|
||||
/** our attr keys don't often match the labels we display to the user - so, when using
|
||||
* attribute specifiers ('name="bler"') in a term, allow passing in aliases for the
|
||||
* following attr keys.
|
||||
*/
|
||||
searchAliases: {
|
||||
title: "name",
|
||||
format: "file_ext",
|
||||
database: "genome_build",
|
||||
blurb: "misc_blurb",
|
||||
description: "misc_blurb",
|
||||
info: "misc_info",
|
||||
tag: "tags"
|
||||
},
|
||||
|
||||
// ........................................................................ misc
|
||||
/** String representation */
|
||||
toString: function() {
|
||||
var nameAndId = this.get("id") || "";
|
||||
if (this.get("name")) {
|
||||
nameAndId = `"${this.get("name")}",${nameAndId}`;
|
||||
}
|
||||
return `Dataset(${nameAndId})`;
|
||||
}
|
||||
});
|
||||
// the download url (currently) relies on having a correct file extension
|
||||
this.on( 'change:id change:file_ext', function( currModel ){
|
||||
this._generateUrls();
|
||||
});
|
||||
},
|
||||
|
||||
// ........................................................................ common queries
|
||||
/** override to add urls */
|
||||
toJSON : function(){
|
||||
var json = Backbone.Model.prototype.toJSON.call( this );
|
||||
//console.warn( 'returning json?' );
|
||||
//return json;
|
||||
return _.extend( json, {
|
||||
urls : this.urls
|
||||
});
|
||||
},
|
||||
|
||||
/** Is this dataset deleted or purged? */
|
||||
isDeletedOrPurged : function(){
|
||||
return ( this.get( 'deleted' ) || this.get( 'purged' ) );
|
||||
},
|
||||
|
||||
/** Is this dataset in a 'ready' state; where 'Ready' states are states where no
|
||||
* processing (for the ds) is left to do on the server.
|
||||
*/
|
||||
inReadyState : function(){
|
||||
var ready = _.contains( STATES.READY_STATES, this.get( 'state' ) );
|
||||
return ( this.isDeletedOrPurged() || ready );
|
||||
},
|
||||
|
||||
/** Does this model already contain detailed data (as opposed to just summary level data)? */
|
||||
hasDetails : function(){
|
||||
// if it's inaccessible assume it has everything it needs
|
||||
if( !this.get( 'accessible' ) ){ return true; }
|
||||
return this.has( 'annotation' );
|
||||
},
|
||||
|
||||
/** Convenience function to match dataset.has_data. */
|
||||
hasData : function(){
|
||||
return ( this.get( 'file_size' ) > 0 );
|
||||
},
|
||||
|
||||
// ........................................................................ ajax
|
||||
fetch : function( options ){
|
||||
var dataset = this;
|
||||
return Backbone.Model.prototype.fetch.call( this, options )
|
||||
.always( function(){
|
||||
dataset._generateUrls();
|
||||
});
|
||||
},
|
||||
|
||||
/** override to use actual Dates objects for create/update times */
|
||||
parse : function( response, options ){
|
||||
var parsed = Backbone.Model.prototype.parse.call( this, response, options );
|
||||
if( parsed.create_time ){
|
||||
parsed.create_time = new Date( parsed.create_time );
|
||||
}
|
||||
if( parsed.update_time ){
|
||||
parsed.update_time = new Date( parsed.update_time );
|
||||
}
|
||||
return parsed;
|
||||
},
|
||||
|
||||
/** override to wait by default */
|
||||
save : function( attrs, options ){
|
||||
options = options || {};
|
||||
options.wait = _.isUndefined( options.wait ) ? true : options.wait;
|
||||
return Backbone.Model.prototype.save.call( this, attrs, options );
|
||||
},
|
||||
|
||||
//NOTE: subclasses of DA's will need to implement url and urlRoot in order to have these work properly
|
||||
/** save this dataset, _Mark_ing it as deleted (just a flag) */
|
||||
'delete' : function( options ){
|
||||
if( this.get( 'deleted' ) ){ return jQuery.when(); }
|
||||
return this.save( { deleted: true }, options );
|
||||
},
|
||||
/** save this dataset, _Mark_ing it as undeleted */
|
||||
undelete : function( options ){
|
||||
if( !this.get( 'deleted' ) || this.get( 'purged' ) ){ return jQuery.when(); }
|
||||
return this.save( { deleted: false }, options );
|
||||
},
|
||||
|
||||
/** remove the file behind this dataset from the filesystem (if permitted) */
|
||||
purge : function _purge( options ){
|
||||
//TODO: use, override model.destroy, HDA.delete({ purge: true })
|
||||
if( this.get( 'purged' ) ){ return jQuery.when(); }
|
||||
options = options || {};
|
||||
options.url = this.urls.purge;
|
||||
|
||||
//TODO: ideally this would be a DELETE call to the api
|
||||
// using purge async for now
|
||||
var hda = this,
|
||||
xhr = jQuery.ajax( options );
|
||||
xhr.done( function( message, status, responseObj ){
|
||||
hda.set({ deleted: true, purged: true });
|
||||
});
|
||||
xhr.fail( function( xhr, status, message ){
|
||||
// Exception messages are hidden within error page including: '...not allowed in this Galaxy instance.'
|
||||
// unbury and re-add to xhr
|
||||
var error = _l( "Unable to purge dataset" );
|
||||
var messageBuriedInUnfortunatelyFormattedError = ( 'Removal of datasets by users '
|
||||
+ 'is not allowed in this Galaxy instance' );
|
||||
if( xhr.responseJSON && xhr.responseJSON.error ){
|
||||
error = xhr.responseJSON.error;
|
||||
} else if( xhr.responseText.indexOf( messageBuriedInUnfortunatelyFormattedError ) !== -1 ){
|
||||
error = messageBuriedInUnfortunatelyFormattedError;
|
||||
}
|
||||
xhr.responseText = error;
|
||||
hda.trigger( 'error', hda, xhr, options, _l( error ), { error: error } );
|
||||
});
|
||||
return xhr;
|
||||
},
|
||||
|
||||
// ........................................................................ searching
|
||||
/** what attributes of an HDA will be used in a text search */
|
||||
searchAttributes : [
|
||||
'name', 'file_ext', 'genome_build', 'misc_blurb', 'misc_info', 'annotation', 'tags'
|
||||
],
|
||||
|
||||
/** our attr keys don't often match the labels we display to the user - so, when using
|
||||
* attribute specifiers ('name="bler"') in a term, allow passing in aliases for the
|
||||
* following attr keys.
|
||||
*/
|
||||
searchAliases : {
|
||||
title : 'name',
|
||||
format : 'file_ext',
|
||||
database : 'genome_build',
|
||||
blurb : 'misc_blurb',
|
||||
description : 'misc_blurb',
|
||||
info : 'misc_info',
|
||||
tag : 'tags'
|
||||
},
|
||||
|
||||
// ........................................................................ misc
|
||||
/** String representation */
|
||||
toString : function(){
|
||||
var nameAndId = this.get( 'id' ) || '';
|
||||
if( this.get( 'name' ) ){
|
||||
nameAndId = '"' + this.get( 'name' ) + '",' + nameAndId;
|
||||
}
|
||||
return 'Dataset(' + nameAndId + ')';
|
||||
}
|
||||
}));
|
||||
|
||||
)
|
||||
);
|
||||
|
||||
//==============================================================================
|
||||
/** @class Backbone collection for dataset associations.
|
||||
*/
|
||||
var DatasetAssociationCollection = Backbone.Collection.extend( BASE_MVC.LoggableMixin ).extend(
|
||||
/** @lends HistoryContents.prototype */{
|
||||
_logNamespace : logNamespace,
|
||||
var DatasetAssociationCollection = Backbone.Collection.extend(BASE_MVC.LoggableMixin).extend(
|
||||
/** @lends HistoryContents.prototype */ {
|
||||
_logNamespace: logNamespace,
|
||||
|
||||
model : DatasetAssociation,
|
||||
model: DatasetAssociation,
|
||||
|
||||
/** root api url */
|
||||
urlRoot : Galaxy.root + 'api/datasets',
|
||||
/** root api url */
|
||||
urlRoot: `${Galaxy.root}api/datasets`,
|
||||
|
||||
/** url fn */
|
||||
url : function(){
|
||||
return this.urlRoot;
|
||||
},
|
||||
/** url fn */
|
||||
url: function() {
|
||||
return this.urlRoot;
|
||||
},
|
||||
|
||||
// ........................................................................ common queries
|
||||
/** Get the ids of every item in this collection
|
||||
* @returns array of encoded ids
|
||||
*/
|
||||
ids : function(){
|
||||
return this.map( function( item ){ return item.get('id'); });
|
||||
},
|
||||
// ........................................................................ common queries
|
||||
/** Get the ids of every item in this collection
|
||||
* @returns array of encoded ids
|
||||
*/
|
||||
ids: function() {
|
||||
return this.map(item => item.get("id"));
|
||||
},
|
||||
|
||||
/** Get contents that are not ready
|
||||
* @returns array of content models
|
||||
*/
|
||||
notReady : function(){
|
||||
return this.filter( function( content ){
|
||||
return !content.inReadyState();
|
||||
});
|
||||
},
|
||||
/** Get contents that are not ready
|
||||
* @returns array of content models
|
||||
*/
|
||||
notReady: function() {
|
||||
return this.filter(content => !content.inReadyState());
|
||||
},
|
||||
|
||||
/** return true if any datasets don't have details */
|
||||
haveDetails : function(){
|
||||
return this.all( function( dataset ){ return dataset.hasDetails(); });
|
||||
},
|
||||
/** return true if any datasets don't have details */
|
||||
haveDetails: function() {
|
||||
return this.all(dataset => dataset.hasDetails());
|
||||
},
|
||||
|
||||
// ........................................................................ ajax
|
||||
/** using a queue, perform ajaxFn on each of the models in this collection */
|
||||
ajaxQueue : function( ajaxFn, options ){
|
||||
var deferred = jQuery.Deferred(),
|
||||
startingLength = this.length,
|
||||
responses = [];
|
||||
// ........................................................................ ajax
|
||||
/** using a queue, perform ajaxFn on each of the models in this collection */
|
||||
ajaxQueue: function(ajaxFn, options) {
|
||||
var deferred = jQuery.Deferred();
|
||||
var startingLength = this.length;
|
||||
var responses = [];
|
||||
|
||||
if (!startingLength) {
|
||||
deferred.resolve([]);
|
||||
return deferred;
|
||||
}
|
||||
|
||||
// use reverse order (stylistic choice)
|
||||
var ajaxFns = this.chain()
|
||||
.reverse()
|
||||
.map((dataset, i) => () => {
|
||||
var xhr = ajaxFn.call(dataset, options);
|
||||
// if successful, notify using the deferred to allow tracking progress
|
||||
xhr.done(response => {
|
||||
deferred.notify({
|
||||
curr: i,
|
||||
total: startingLength,
|
||||
response: response,
|
||||
model: dataset
|
||||
});
|
||||
});
|
||||
// (regardless of previous error or success) if not last ajax call, shift and call the next
|
||||
// if last fn, resolve deferred
|
||||
xhr.always(response => {
|
||||
responses.push(response);
|
||||
if (ajaxFns.length) {
|
||||
ajaxFns.shift()();
|
||||
} else {
|
||||
deferred.resolve(responses);
|
||||
}
|
||||
});
|
||||
})
|
||||
.value();
|
||||
// start the queue
|
||||
ajaxFns.shift()();
|
||||
|
||||
if( !startingLength ){
|
||||
deferred.resolve([]);
|
||||
return deferred;
|
||||
},
|
||||
|
||||
// ........................................................................ sorting/filtering
|
||||
/** return a new collection of datasets whose attributes contain the substring matchesWhat */
|
||||
matches: function(matchesWhat) {
|
||||
return this.filter(dataset => dataset.matches(matchesWhat));
|
||||
},
|
||||
|
||||
/** String representation. */
|
||||
toString: function() {
|
||||
return ["DatasetAssociationCollection(", this.length, ")"].join("");
|
||||
}
|
||||
|
||||
// use reverse order (stylistic choice)
|
||||
var ajaxFns = this.chain().reverse().map( function( dataset, i ){
|
||||
return function(){
|
||||
var xhr = ajaxFn.call( dataset, options );
|
||||
// if successful, notify using the deferred to allow tracking progress
|
||||
xhr.done( function( response ){
|
||||
deferred.notify({ curr: i, total: startingLength, response: response, model: dataset });
|
||||
});
|
||||
// (regardless of previous error or success) if not last ajax call, shift and call the next
|
||||
// if last fn, resolve deferred
|
||||
xhr.always( function( response ){
|
||||
responses.push( response );
|
||||
if( ajaxFns.length ){
|
||||
ajaxFns.shift()();
|
||||
} else {
|
||||
deferred.resolve( responses );
|
||||
}
|
||||
});
|
||||
};
|
||||
}).value();
|
||||
// start the queue
|
||||
ajaxFns.shift()();
|
||||
|
||||
return deferred;
|
||||
},
|
||||
|
||||
// ........................................................................ sorting/filtering
|
||||
/** return a new collection of datasets whose attributes contain the substring matchesWhat */
|
||||
matches : function( matchesWhat ){
|
||||
return this.filter( function( dataset ){
|
||||
return dataset.matches( matchesWhat );
|
||||
});
|
||||
},
|
||||
|
||||
/** String representation. */
|
||||
toString : function(){
|
||||
return ([ 'DatasetAssociationCollection(', this.length, ')' ].join( '' ));
|
||||
}
|
||||
});
|
||||
|
||||
);
|
||||
|
||||
//==============================================================================
|
||||
return {
|
||||
DatasetAssociation : DatasetAssociation,
|
||||
DatasetAssociationCollection : DatasetAssociationCollection
|
||||
};
|
||||
});
|
||||
export default {
|
||||
DatasetAssociation: DatasetAssociation,
|
||||
DatasetAssociationCollection: DatasetAssociationCollection
|
||||
};
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
define([
|
||||
], function(){
|
||||
|
||||
'use strict';
|
||||
//==============================================================================
|
||||
/** Map of possible HDA/collection/job states to their string equivalents.
|
||||
* A port of galaxy.model.Dataset.states.
|
||||
@@ -9,33 +5,33 @@ define([
|
||||
var STATES = {
|
||||
// NOT ready states
|
||||
/** is uploading and not ready */
|
||||
UPLOAD : 'upload',
|
||||
UPLOAD: "upload",
|
||||
/** the job that will produce the dataset queued in the runner */
|
||||
QUEUED : 'queued',
|
||||
QUEUED: "queued",
|
||||
/** the job that will produce the dataset is running */
|
||||
RUNNING : 'running',
|
||||
RUNNING: "running",
|
||||
/** metadata for the dataset is being discovered/set */
|
||||
SETTING_METADATA : 'setting_metadata',
|
||||
SETTING_METADATA: "setting_metadata",
|
||||
|
||||
// ready states
|
||||
/** was created without a tool */
|
||||
NEW : 'new',
|
||||
NEW: "new",
|
||||
/** has no data */
|
||||
EMPTY : 'empty',
|
||||
EMPTY: "empty",
|
||||
/** has successfully completed running */
|
||||
OK : 'ok',
|
||||
OK: "ok",
|
||||
|
||||
/** the job that will produce the dataset paused */
|
||||
PAUSED : 'paused',
|
||||
PAUSED: "paused",
|
||||
/** metadata discovery/setting failed or errored (but otherwise ok) */
|
||||
FAILED_METADATA : 'failed_metadata',
|
||||
//TODO: not in trans.app.model.Dataset.states - is in database
|
||||
FAILED_METADATA: "failed_metadata",
|
||||
//TODO: not in trans.app.model.Dataset.states - is in database
|
||||
/** not accessible to the current user (i.e. due to permissions) */
|
||||
NOT_VIEWABLE : 'noPermission',
|
||||
NOT_VIEWABLE: "noPermission",
|
||||
/** deleted while uploading */
|
||||
DISCARDED : 'discarded',
|
||||
DISCARDED: "discarded",
|
||||
/** the tool producing this dataset failed */
|
||||
ERROR : 'error'
|
||||
ERROR: "error"
|
||||
};
|
||||
|
||||
STATES.READY_STATES = [
|
||||
@@ -48,15 +44,7 @@ STATES.READY_STATES = [
|
||||
STATES.ERROR
|
||||
];
|
||||
|
||||
STATES.NOT_READY_STATES = [
|
||||
STATES.UPLOAD,
|
||||
STATES.QUEUED,
|
||||
STATES.RUNNING,
|
||||
STATES.SETTING_METADATA,
|
||||
STATES.NEW
|
||||
];
|
||||
|
||||
STATES.NOT_READY_STATES = [STATES.UPLOAD, STATES.QUEUED, STATES.RUNNING, STATES.SETTING_METADATA, STATES.NEW];
|
||||
|
||||
//==============================================================================
|
||||
return STATES;
|
||||
});
|
||||
export default STATES;
|
||||
|
||||
@@ -1,240 +1,244 @@
|
||||
/* This class maps the form dom to an api compatible javascript dictionary. */
|
||||
define([ 'utils/utils' ], function( Utils ) {
|
||||
var Manager = Backbone.Model.extend({
|
||||
initialize: function( app ) {
|
||||
this.app = app;
|
||||
},
|
||||
export var Manager = Backbone.Model.extend({
|
||||
initialize: function(app) {
|
||||
this.app = app;
|
||||
},
|
||||
|
||||
/** Creates a checksum. */
|
||||
checksum: function() {
|
||||
var sum = '';
|
||||
var self = this;
|
||||
this.app.section.$el.find( '.section-row' ).each( function() {
|
||||
var id = $(this).attr( 'id' );
|
||||
var field = self.app.field_list[ id ];
|
||||
if ( field ) {
|
||||
sum += id + ':' + JSON.stringify( field.value && field.value() ) + ':' + field.collapsed + ';';
|
||||
}
|
||||
});
|
||||
return sum;
|
||||
},
|
||||
|
||||
/** Convert dom into a dictionary of flat id/value pairs used e.g. on job submission. */
|
||||
create: function() {
|
||||
var self = this;
|
||||
|
||||
// get raw dictionary from dom
|
||||
var dict = {};
|
||||
this._iterate( this.app.section.$el, dict );
|
||||
|
||||
// add to result dictionary, label elements
|
||||
var result_dict = {};
|
||||
this.flat_dict = {};
|
||||
function add( flat_id, input_id, input_value ) {
|
||||
self.flat_dict[ flat_id ] = input_id;
|
||||
result_dict[ flat_id ] = input_value;
|
||||
self.app.element_list[ input_id ] && self.app.element_list[ input_id ].$el.attr( 'tour_id', flat_id );
|
||||
}
|
||||
// converter between raw dictionary and job dictionary
|
||||
function convert( identifier, head ) {
|
||||
for ( var index in head ) {
|
||||
var node = head[ index ];
|
||||
if ( node.input ) {
|
||||
var input = node.input;
|
||||
var flat_id = identifier;
|
||||
if ( identifier != '' ) {
|
||||
flat_id += '|';
|
||||
}
|
||||
flat_id += input.name;
|
||||
switch ( input.type ) {
|
||||
case 'repeat':
|
||||
var section_label = 'section-';
|
||||
var block_indices = [];
|
||||
var block_prefix = null;
|
||||
for ( var block_label in node ) {
|
||||
var pos = block_label.indexOf( section_label );
|
||||
if ( pos != -1 ) {
|
||||
pos += section_label.length;
|
||||
block_indices.push( parseInt( block_label.substr( pos ) ));
|
||||
if ( !block_prefix ) {
|
||||
block_prefix = block_label.substr( 0, pos );
|
||||
}
|
||||
}
|
||||
}
|
||||
block_indices.sort( function( a, b ) { return a - b; });
|
||||
var index = 0;
|
||||
for ( var i in block_indices ) {
|
||||
convert( flat_id + '_' + index++, node[ block_prefix + block_indices[ i ] ]);
|
||||
}
|
||||
break;
|
||||
case 'conditional':
|
||||
var value = self.app.field_list[ input.id ].value();
|
||||
add( flat_id + '|' + input.test_param.name, input.id, value );
|
||||
var selectedCase = matchCase( input, value );
|
||||
if ( selectedCase != -1 ) {
|
||||
convert( flat_id, head[ input.id + '-section-' + selectedCase ] );
|
||||
}
|
||||
break;
|
||||
case 'section':
|
||||
convert( !input.flat && flat_id || '', node );
|
||||
break;
|
||||
default:
|
||||
var field = self.app.field_list[ input.id ];
|
||||
if ( field && field.value ) {
|
||||
var value = field.value();
|
||||
if ( input.ignore === undefined || input.ignore != value ) {
|
||||
if ( field.collapsed && input.collapsible_value ) {
|
||||
value = input.collapsible_value;
|
||||
}
|
||||
add( flat_id, input.id, value );
|
||||
if ( input.payload ) {
|
||||
for ( var p_id in input.payload ) {
|
||||
add( p_id, input.id, input.payload[ p_id ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
convert( '', dict );
|
||||
return result_dict;
|
||||
},
|
||||
|
||||
/** Matches flat ids to corresponding input element
|
||||
* @param{string} flat_id - Flat input id to be looked up.
|
||||
*/
|
||||
match: function ( flat_id ) {
|
||||
return this.flat_dict && this.flat_dict[ flat_id ];
|
||||
},
|
||||
|
||||
/** Match conditional values to selected cases
|
||||
*/
|
||||
matchCase: function( input, value ) {
|
||||
return matchCase( input, value );
|
||||
},
|
||||
|
||||
/** Matches a new tool model to the current input elements e.g. used to update dynamic options
|
||||
*/
|
||||
matchModel: function( model, callback ) {
|
||||
var self = this;
|
||||
visitInputs( model.inputs, function( input, name ) {
|
||||
self.flat_dict[ name ] && callback ( input, self.flat_dict[ name ] );
|
||||
});
|
||||
},
|
||||
|
||||
/** Matches identifier from api response to input elements e.g. used to display validation errors
|
||||
*/
|
||||
matchResponse: function( response ) {
|
||||
var result = {};
|
||||
var self = this;
|
||||
function search ( id, head ) {
|
||||
if ( typeof head === 'string' ) {
|
||||
var input_id = self.flat_dict[ id ];
|
||||
input_id && ( result[ input_id ] = head );
|
||||
} else {
|
||||
for ( var i in head ) {
|
||||
var new_id = i;
|
||||
if ( id !== '' ) {
|
||||
var separator = '|';
|
||||
if ( head instanceof Array ) {
|
||||
separator = '_';
|
||||
}
|
||||
new_id = id + separator + new_id;
|
||||
}
|
||||
search ( new_id, head[ i ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
search( '', response );
|
||||
return result;
|
||||
},
|
||||
|
||||
/** Map dom tree to dictionary tree with input elements.
|
||||
*/
|
||||
_iterate: function( parent, dict ) {
|
||||
var self = this;
|
||||
var children = $( parent ).children();
|
||||
children.each( function() {
|
||||
var child = this;
|
||||
var id = $( child ).attr( 'id' );
|
||||
if ( $( child ).hasClass( 'section-row' ) ) {
|
||||
var input = self.app.input_list[ id ];
|
||||
dict[ id ] = ( input && { input : input } ) || {};
|
||||
self._iterate( child, dict[ id ] );
|
||||
} else {
|
||||
self._iterate( child, dict );
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/** Match conditional values to selected cases
|
||||
* @param{dict} input - Definition of conditional input parameter
|
||||
* @param{dict} value - Current value
|
||||
*/
|
||||
var matchCase = function( input, value ) {
|
||||
if ( input.test_param.type == 'boolean' ) {
|
||||
if ( value == 'true' ) {
|
||||
value = input.test_param.truevalue || 'true';
|
||||
} else {
|
||||
value = input.test_param.falsevalue || 'false';
|
||||
}
|
||||
}
|
||||
for ( var i in input.cases ) {
|
||||
if ( input.cases[ i ].value == value ) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
|
||||
/** Visits tool inputs
|
||||
* @param{dict} inputs - Nested dictionary of input elements
|
||||
* @param{dict} callback - Called with the mapped dictionary object and corresponding model node
|
||||
*/
|
||||
var visitInputs = function( inputs, callback, prefix, context ) {
|
||||
context = $.extend( true, {}, context );
|
||||
_.each( inputs, function ( input ) {
|
||||
if ( input && input.type && input.name ) {
|
||||
context[ input.name ] = input;
|
||||
/** Creates a checksum. */
|
||||
checksum: function() {
|
||||
var sum = "";
|
||||
var self = this;
|
||||
this.app.section.$el.find(".section-row").each(function() {
|
||||
var id = $(this).attr("id");
|
||||
var field = self.app.field_list[id];
|
||||
if (field) {
|
||||
sum += `${id}:${JSON.stringify(field.value && field.value())}:${field.collapsed};`;
|
||||
}
|
||||
});
|
||||
for ( var key in inputs ) {
|
||||
var node = inputs[ key ];
|
||||
node.name = node.name || key;
|
||||
var name = prefix ? prefix + '|' + node.name : node.name;
|
||||
switch ( node.type ) {
|
||||
case 'repeat':
|
||||
_.each( node.cache, function( cache, j ) {
|
||||
visitInputs( cache, callback, name + '_' + j, context );
|
||||
});
|
||||
break;
|
||||
case 'conditional':
|
||||
if ( node.test_param ) {
|
||||
callback( node.test_param, name + '|' + node.test_param.name, context );
|
||||
var selectedCase = matchCase( node, node.test_param.value );
|
||||
if ( selectedCase != -1 ) {
|
||||
visitInputs( node.cases[ selectedCase ].inputs, callback, name, context );
|
||||
} else {
|
||||
Galaxy.emit.debug( 'form-data::visitInputs() - Invalid case for ' + name + '.' );
|
||||
}
|
||||
} else {
|
||||
Galaxy.emit.debug( 'form-data::visitInputs() - Conditional test parameter missing for ' + name + '.' );
|
||||
}
|
||||
break;
|
||||
case 'section':
|
||||
visitInputs( node.inputs, callback, name, context )
|
||||
break;
|
||||
default:
|
||||
callback( node, name, context );
|
||||
return sum;
|
||||
},
|
||||
|
||||
/** Convert dom into a dictionary of flat id/value pairs used e.g. on job submission. */
|
||||
create: function() {
|
||||
var self = this;
|
||||
|
||||
// get raw dictionary from dom
|
||||
var dict = {};
|
||||
this._iterate(this.app.section.$el, dict);
|
||||
|
||||
// add to result dictionary, label elements
|
||||
var result_dict = {};
|
||||
this.flat_dict = {};
|
||||
function add(flat_id, input_id, input_value) {
|
||||
self.flat_dict[flat_id] = input_id;
|
||||
result_dict[flat_id] = input_value;
|
||||
if (self.app.element_list[input_id]) {
|
||||
self.app.element_list[input_id].$el.attr("tour_id", flat_id);
|
||||
}
|
||||
}
|
||||
};
|
||||
// converter between raw dictionary and job dictionary
|
||||
function convert(identifier, head) {
|
||||
for (var index in head) {
|
||||
var node = head[index];
|
||||
if (node.input) {
|
||||
var input = node.input;
|
||||
var flat_id = identifier;
|
||||
if (identifier !== "") {
|
||||
flat_id += "|";
|
||||
}
|
||||
flat_id += input.name;
|
||||
switch (input.type) {
|
||||
case "repeat":
|
||||
var section_label = "section-";
|
||||
var block_indices = [];
|
||||
var block_prefix = null;
|
||||
for (var block_label in node) {
|
||||
var pos = block_label.indexOf(section_label);
|
||||
if (pos != -1) {
|
||||
pos += section_label.length;
|
||||
block_indices.push(parseInt(block_label.substr(pos)));
|
||||
if (!block_prefix) {
|
||||
block_prefix = block_label.substr(0, pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
block_indices.sort((a, b) => a - b);
|
||||
index = 0;
|
||||
for (var i in block_indices) {
|
||||
convert(`${flat_id}_${index++}`, node[block_prefix + block_indices[i]]);
|
||||
}
|
||||
break;
|
||||
case "conditional":
|
||||
var value = self.app.field_list[input.id].value();
|
||||
add(`${flat_id}|${input.test_param.name}`, input.id, value);
|
||||
var selectedCase = matchCase(input, value);
|
||||
if (selectedCase != -1) {
|
||||
convert(flat_id, head[`${input.id}-section-${selectedCase}`]);
|
||||
}
|
||||
break;
|
||||
case "section":
|
||||
convert((!input.flat && flat_id) || "", node);
|
||||
break;
|
||||
default:
|
||||
var field = self.app.field_list[input.id];
|
||||
if (field && field.value) {
|
||||
value = field.value();
|
||||
if (input.ignore === undefined || input.ignore != value) {
|
||||
if (field.collapsed && input.collapsible_value) {
|
||||
value = input.collapsible_value;
|
||||
}
|
||||
add(flat_id, input.id, value);
|
||||
if (input.payload) {
|
||||
for (var p_id in input.payload) {
|
||||
add(p_id, input.id, input.payload[p_id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
convert("", dict);
|
||||
return result_dict;
|
||||
},
|
||||
|
||||
return {
|
||||
Manager : Manager,
|
||||
visitInputs : visitInputs
|
||||
/** Matches flat ids to corresponding input element
|
||||
* @param{string} flat_id - Flat input id to be looked up.
|
||||
*/
|
||||
match: function(flat_id) {
|
||||
return this.flat_dict && this.flat_dict[flat_id];
|
||||
},
|
||||
|
||||
/** Match conditional values to selected cases
|
||||
*/
|
||||
matchCase: function(input, value) {
|
||||
return matchCase(input, value);
|
||||
},
|
||||
|
||||
/** Matches a new tool model to the current input elements e.g. used to update dynamic options
|
||||
*/
|
||||
matchModel: function(model, callback) {
|
||||
var self = this;
|
||||
visitInputs(model.inputs, (input, name) => {
|
||||
if (self.flat_dict[name]) {
|
||||
callback(input, self.flat_dict[name]);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/** Matches identifier from api response to input elements e.g. used to display validation errors
|
||||
*/
|
||||
matchResponse: function(response) {
|
||||
var result = {};
|
||||
var self = this;
|
||||
function search(id, head) {
|
||||
if (typeof head === "string") {
|
||||
var input_id = self.flat_dict[id];
|
||||
if (input_id) {
|
||||
result[input_id] = head;
|
||||
}
|
||||
} else {
|
||||
for (var i in head) {
|
||||
var new_id = i;
|
||||
if (id !== "") {
|
||||
var separator = "|";
|
||||
if (head instanceof Array) {
|
||||
separator = "_";
|
||||
}
|
||||
new_id = id + separator + new_id;
|
||||
}
|
||||
search(new_id, head[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
search("", response);
|
||||
return result;
|
||||
},
|
||||
|
||||
/** Map dom tree to dictionary tree with input elements.
|
||||
*/
|
||||
_iterate: function(parent, dict) {
|
||||
var self = this;
|
||||
var children = $(parent).children();
|
||||
children.each(function() {
|
||||
var child = this;
|
||||
var id = $(child).attr("id");
|
||||
if ($(child).hasClass("section-row")) {
|
||||
var input = self.app.input_list[id];
|
||||
dict[id] = (input && { input: input }) || {};
|
||||
self._iterate(child, dict[id]);
|
||||
} else {
|
||||
self._iterate(child, dict);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/** Match conditional values to selected cases
|
||||
* @param{dict} input - Definition of conditional input parameter
|
||||
* @param{dict} value - Current value
|
||||
*/
|
||||
export var matchCase = (input, value) => {
|
||||
if (input.test_param.type == "boolean") {
|
||||
if (value == "true") {
|
||||
value = input.test_param.truevalue || "true";
|
||||
} else {
|
||||
value = input.test_param.falsevalue || "false";
|
||||
}
|
||||
}
|
||||
for (var i in input.cases) {
|
||||
if (input.cases[i].value == value) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
|
||||
/** Visits tool inputs
|
||||
* @param{dict} inputs - Nested dictionary of input elements
|
||||
* @param{dict} callback - Called with the mapped dictionary object and corresponding model node
|
||||
*/
|
||||
export var visitInputs = (inputs, callback, prefix, context) => {
|
||||
context = $.extend(true, {}, context);
|
||||
_.each(inputs, input => {
|
||||
if (input && input.type && input.name) {
|
||||
context[input.name] = input;
|
||||
}
|
||||
});
|
||||
for (var key in inputs) {
|
||||
var node = inputs[key];
|
||||
node.name = node.name || key;
|
||||
var name = prefix ? `${prefix}|${node.name}` : node.name;
|
||||
switch (node.type) {
|
||||
case "repeat":
|
||||
_.each(node.cache, (cache, j) => {
|
||||
visitInputs(cache, callback, `${name}_${j}`, context);
|
||||
});
|
||||
break;
|
||||
case "conditional":
|
||||
if (node.test_param) {
|
||||
callback(node.test_param, `${name}|${node.test_param.name}`, context);
|
||||
var selectedCase = matchCase(node, node.test_param.value);
|
||||
if (selectedCase != -1) {
|
||||
visitInputs(node.cases[selectedCase].inputs, callback, name, context);
|
||||
} else {
|
||||
Galaxy.emit.debug(`form-data::visitInputs() - Invalid case for ${name}.`);
|
||||
}
|
||||
} else {
|
||||
Galaxy.emit.debug(`form-data::visitInputs() - Conditional test parameter missing for ${name}.`);
|
||||
}
|
||||
break;
|
||||
case "section":
|
||||
visitInputs(node.inputs, callback, name, context);
|
||||
break;
|
||||
default:
|
||||
callback(node, name, context);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
Manager: Manager,
|
||||
visitInputs: visitInputs
|
||||
};
|
||||
|
||||
@@ -1,131 +1,154 @@
|
||||
/**
|
||||
This class creates a form input element wrapper
|
||||
*/
|
||||
define([], function() {
|
||||
return Backbone.View.extend({
|
||||
initialize: function( app, options ) {
|
||||
this.app = app;
|
||||
this.app_options = app.model ? app.model.attributes : {};
|
||||
this.field = options && options.field || new Backbone.View();
|
||||
this.model = options && options.model || new Backbone.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',
|
||||
always_refresh : this.app_options.always_refresh
|
||||
}).set( options );
|
||||
|
||||
// set element and link components
|
||||
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' );
|
||||
export default Backbone.View.extend({
|
||||
initialize: function(app, options) {
|
||||
this.app = app;
|
||||
this.app_options = app.model ? app.model.attributes : {};
|
||||
this.field = (options && options.field) || new Backbone.View();
|
||||
this.model =
|
||||
(options && options.model) ||
|
||||
new Backbone.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",
|
||||
always_refresh: this.app_options.always_refresh
|
||||
}).set(options);
|
||||
|
||||
// add field element
|
||||
this.$field.prepend( this.field.$el );
|
||||
// set element and link components
|
||||
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");
|
||||
|
||||
// decide wether to expand or collapse fields
|
||||
var collapsible_value = this.model.get( 'collapsible_value' );
|
||||
this.field.collapsed = collapsible_value !== undefined && JSON.stringify( this.model.get( 'value' ) ) == JSON.stringify( collapsible_value );
|
||||
this.listenTo( this.model, 'change', this.render, this );
|
||||
this.render();
|
||||
// add field element
|
||||
this.$field.prepend(this.field.$el);
|
||||
|
||||
// add click handler
|
||||
var self = this;
|
||||
this.$collapsible.on( 'click', function() {
|
||||
self.field.collapsed = !self.field.collapsed;
|
||||
app.trigger && app.trigger( 'change' );
|
||||
self.render();
|
||||
// decide wether to expand or collapse fields
|
||||
var collapsible_value = this.model.get("collapsible_value");
|
||||
this.field.collapsed =
|
||||
collapsible_value !== undefined &&
|
||||
JSON.stringify(this.model.get("value")) == JSON.stringify(collapsible_value);
|
||||
this.listenTo(this.model, "change", this.render, this);
|
||||
this.render();
|
||||
|
||||
// add click handler
|
||||
var self = this;
|
||||
this.$collapsible.on("click", () => {
|
||||
self.field.collapsed = !self.field.collapsed;
|
||||
app.trigger && app.trigger("change");
|
||||
self.render();
|
||||
});
|
||||
|
||||
// hide error on value change
|
||||
if (this.field.model && !this.model.get("always_refresh")) {
|
||||
this.listenTo(this.field.model, "change:value", () => {
|
||||
self.reset();
|
||||
});
|
||||
|
||||
// hide error on value change
|
||||
if ( this.field.model && !this.model.get( 'always_refresh' ) ) {
|
||||
this.listenTo( this.field.model, 'change:value', function() { self.reset() } );
|
||||
}
|
||||
},
|
||||
|
||||
/** Set backdrop for input element */
|
||||
backdrop: function() {
|
||||
this.model.set( 'backdrop', true );
|
||||
},
|
||||
|
||||
/** Set error text */
|
||||
error: function( text ) {
|
||||
this.model.set( 'error_text', text );
|
||||
},
|
||||
|
||||
/** Reset this view */
|
||||
reset: function() {
|
||||
!this.model.get( 'fixed' ) && this.model.set( 'error_text', null );
|
||||
},
|
||||
|
||||
render: function() {
|
||||
// render help
|
||||
$( '.tooltip' ).hide();
|
||||
var help_text = this.model.get( 'help', '' );
|
||||
var help_argument = this.model.get( 'argument' );
|
||||
if ( help_argument && help_text.indexOf( '(' + help_argument + ')' ) == -1 ) {
|
||||
help_text += ' (' + help_argument + ')';
|
||||
}
|
||||
this.$info.html( help_text );
|
||||
// render visibility
|
||||
this.$el[ this.model.get( 'hidden' ) ? 'hide' : 'show' ]();
|
||||
// render preview view for collapsed fields
|
||||
this.$preview[ ( this.field.collapsed && this.model.get( 'collapsible_preview' ) || this.model.get( 'disabled' ) ) ? 'show' : 'hide' ]()
|
||||
.html( _.escape( this.model.get( 'text_value' ) ) );
|
||||
// render error messages
|
||||
var error_text = this.model.get( 'error_text' );
|
||||
this.$error[ error_text ? 'show' : 'hide' ]();
|
||||
this.$el[ error_text ? 'addClass' : 'removeClass' ]( 'ui-error' );
|
||||
this.$error_text.html( error_text );
|
||||
// render backdrop
|
||||
this.$backdrop[ this.model.get( 'backdrop' ) ? 'show' : 'hide' ]();
|
||||
// render input field
|
||||
this.field.collapsed || this.model.get( 'disabled' ) ? this.$field.hide() : this.$field.show();
|
||||
// render input field color and style
|
||||
this.field.model && this.field.model.set( { 'color': this.model.get( 'color' ), 'style': this.model.get( 'style' ) } );
|
||||
// render collapsible options
|
||||
if ( !this.model.get( 'disabled' ) && this.model.get( 'collapsible_value' ) !== undefined ) {
|
||||
var collapsible_state = 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_' + collapsible_state ) )
|
||||
.attr( 'data-original-title', this.model.get( 'text_' + collapsible_state ) )
|
||||
.tooltip( { placement: 'bottom' } );
|
||||
} else {
|
||||
this.$title_text.show().text( this.model.get( 'label' ) );
|
||||
this.$collapsible.hide();
|
||||
}
|
||||
},
|
||||
|
||||
_template: function() {
|
||||
return $( '<div/>' ).addClass( 'ui-form-element' )
|
||||
.append( $( '<div/>' ).addClass( 'ui-form-error ui-error' )
|
||||
.append( $( '<span/>' ).addClass( 'fa fa-arrow-down' ) )
|
||||
.append( $( '<span/>' ).addClass( 'ui-form-error-text' ) )
|
||||
)
|
||||
.append( $( '<div/>' ).addClass( 'ui-form-title' )
|
||||
.append( $( '<div/>' ).addClass( 'ui-form-collapsible' )
|
||||
.append( $( '<i/>' ).addClass( 'ui-form-collapsible-icon' ) )
|
||||
.append( $( '<span/>' ).addClass( 'ui-form-collapsible-text' ) )
|
||||
)
|
||||
.append( $( '<span/>' ).addClass( 'ui-form-title-text' ) )
|
||||
)
|
||||
.append( $( '<div/>' ).addClass( 'ui-form-field' )
|
||||
.append( $( '<span/>' ).addClass( 'ui-form-info' ) )
|
||||
.append( $( '<div/>' ).addClass( 'ui-form-backdrop' ) )
|
||||
)
|
||||
.append( $( '<div/>' ).addClass( 'ui-form-preview' ) );
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/** Set backdrop for input element */
|
||||
backdrop: function() {
|
||||
this.model.set("backdrop", true);
|
||||
},
|
||||
|
||||
/** Set error text */
|
||||
error: function(text) {
|
||||
this.model.set("error_text", text);
|
||||
},
|
||||
|
||||
/** Reset this view */
|
||||
reset: function() {
|
||||
!this.model.get("fixed") && this.model.set("error_text", null);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
// render help
|
||||
$(".tooltip").hide();
|
||||
var help_text = this.model.get("help", "");
|
||||
var help_argument = this.model.get("argument");
|
||||
if (help_argument && help_text.indexOf(`(${help_argument})`) == -1) {
|
||||
help_text += ` (${help_argument})`;
|
||||
}
|
||||
this.$info.html(help_text);
|
||||
// render visibility
|
||||
this.$el[this.model.get("hidden") ? "hide" : "show"]();
|
||||
// render preview view for collapsed fields
|
||||
this.$preview[
|
||||
(this.field.collapsed && this.model.get("collapsible_preview")) || this.model.get("disabled")
|
||||
? "show"
|
||||
: "hide"
|
||||
]().html(_.escape(this.model.get("text_value")));
|
||||
// render error messages
|
||||
var error_text = this.model.get("error_text");
|
||||
this.$error[error_text ? "show" : "hide"]();
|
||||
this.$el[error_text ? "addClass" : "removeClass"]("ui-error");
|
||||
this.$error_text.html(error_text);
|
||||
// render backdrop
|
||||
this.$backdrop[this.model.get("backdrop") ? "show" : "hide"]();
|
||||
// render input field
|
||||
this.field.collapsed || this.model.get("disabled") ? this.$field.hide() : this.$field.show();
|
||||
// render input field color and style
|
||||
this.field.model &&
|
||||
this.field.model.set({
|
||||
color: this.model.get("color"),
|
||||
style: this.model.get("style")
|
||||
});
|
||||
// render collapsible options
|
||||
if (!this.model.get("disabled") && this.model.get("collapsible_value") !== undefined) {
|
||||
var collapsible_state = 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_${collapsible_state}`))
|
||||
.attr("data-original-title", this.model.get(`text_${collapsible_state}`))
|
||||
.tooltip({ placement: "bottom" });
|
||||
} else {
|
||||
this.$title_text.show().text(this.model.get("label"));
|
||||
this.$collapsible.hide();
|
||||
}
|
||||
},
|
||||
|
||||
_template: function() {
|
||||
return $("<div/>")
|
||||
.addClass("ui-form-element")
|
||||
.append(
|
||||
$("<div/>")
|
||||
.addClass("ui-form-error ui-error")
|
||||
.append($("<span/>").addClass("fa fa-arrow-down"))
|
||||
.append($("<span/>").addClass("ui-form-error-text"))
|
||||
)
|
||||
.append(
|
||||
$("<div/>")
|
||||
.addClass("ui-form-title")
|
||||
.append(
|
||||
$("<div/>")
|
||||
.addClass("ui-form-collapsible")
|
||||
.append($("<i/>").addClass("ui-form-collapsible-icon"))
|
||||
.append($("<span/>").addClass("ui-form-collapsible-text"))
|
||||
)
|
||||
.append($("<span/>").addClass("ui-form-title-text"))
|
||||
)
|
||||
.append(
|
||||
$("<div/>")
|
||||
.addClass("ui-form-field")
|
||||
.append($("<span/>").addClass("ui-form-info"))
|
||||
.append($("<div/>").addClass("ui-form-backdrop"))
|
||||
)
|
||||
.append($("<div/>").addClass("ui-form-preview"));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,230 +1,240 @@
|
||||
/**
|
||||
This class creates input elements. New input parameter types should be added to the types dictionary.
|
||||
*/
|
||||
define(['utils/utils',
|
||||
'mvc/ui/ui-misc',
|
||||
'mvc/ui/ui-select-content',
|
||||
'mvc/ui/ui-select-library',
|
||||
'mvc/ui/ui-select-ftp',
|
||||
'mvc/ui/ui-color-picker'],
|
||||
function( Utils, Ui, SelectContent, SelectLibrary, SelectFtp, ColorPicker ) {
|
||||
import Utils from "utils/utils";
|
||||
import Ui from "mvc/ui/ui-misc";
|
||||
import SelectContent from "mvc/ui/ui-select-content";
|
||||
import SelectLibrary from "mvc/ui/ui-select-library";
|
||||
import SelectFtp from "mvc/ui/ui-select-ftp";
|
||||
import SelectGenomeSpace from "mvc/ui/ui-select-genomespace";
|
||||
import ColorPicker from "mvc/ui/ui-color-picker";
|
||||
// create form view
|
||||
export default Backbone.Model.extend({
|
||||
/** Available parameter types */
|
||||
types: {
|
||||
text: "_fieldText",
|
||||
password: "_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",
|
||||
upload: "_fieldUpload",
|
||||
genomespacefile: "_fieldGenomeSpace"
|
||||
},
|
||||
|
||||
// create form view
|
||||
return Backbone.Model.extend({
|
||||
/** Available parameter types */
|
||||
types: {
|
||||
'text' : '_fieldText',
|
||||
'password' : '_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',
|
||||
'upload' : '_fieldUpload'
|
||||
},
|
||||
/** Returns an input field for a given field type */
|
||||
create: function(input_def) {
|
||||
var fieldClass = this.types[input_def.type];
|
||||
var field = typeof this[fieldClass] === "function" ? this[fieldClass].call(this, input_def) : null;
|
||||
if (!field) {
|
||||
field = input_def.options ? this._fieldSelect(input_def) : this._fieldText(input_def);
|
||||
Galaxy.emit.debug("form-parameters::_addRow()", `Auto matched field type (${input_def.type}).`);
|
||||
}
|
||||
if (input_def.value === undefined) {
|
||||
input_def.value = null;
|
||||
}
|
||||
field.value(input_def.value);
|
||||
return field;
|
||||
},
|
||||
|
||||
/** Returns an input field for a given field type */
|
||||
create: function( input_def ) {
|
||||
var fieldClass = this.types[ input_def.type ];
|
||||
var field = typeof( this[ fieldClass ] ) === 'function' ? this[ fieldClass ].call( this, input_def ) : null;
|
||||
if ( !field ) {
|
||||
field = input_def.options ? this._fieldSelect( input_def ) : this._fieldText( input_def );
|
||||
Galaxy.emit.debug('form-parameters::_addRow()', 'Auto matched field type (' + input_def.type + ').');
|
||||
}
|
||||
input_def.value === undefined && ( input_def.value = null );
|
||||
field.value( input_def.value );
|
||||
return field;
|
||||
},
|
||||
/** Data input field */
|
||||
_fieldData: function(input_def) {
|
||||
return new SelectContent.View({
|
||||
id: `field-${input_def.id}`,
|
||||
extensions: input_def.extensions,
|
||||
optional: input_def.optional,
|
||||
multiple: input_def.multiple,
|
||||
type: input_def.type,
|
||||
flavor: input_def.flavor,
|
||||
data: input_def.options,
|
||||
onchange: input_def.onchange
|
||||
});
|
||||
},
|
||||
|
||||
/** Data input field */
|
||||
_fieldData: function( input_def ) {
|
||||
return new SelectContent.View({
|
||||
id : 'field-' + input_def.id,
|
||||
extensions : input_def.extensions,
|
||||
optional : input_def.optional,
|
||||
multiple : input_def.multiple,
|
||||
type : input_def.type,
|
||||
flavor : input_def.flavor,
|
||||
data : input_def.options,
|
||||
onchange : input_def.onchange
|
||||
});
|
||||
},
|
||||
/** Select/Checkbox/Radio options field */
|
||||
_fieldSelect: function(input_def) {
|
||||
// show text field e.g. in workflow editor
|
||||
if (input_def.is_workflow) {
|
||||
return this._fieldText(input_def);
|
||||
}
|
||||
|
||||
/** Select/Checkbox/Radio options field */
|
||||
_fieldSelect: function ( input_def ) {
|
||||
// show text field e.g. in workflow editor
|
||||
if( input_def.is_workflow ) {
|
||||
return this._fieldText( input_def );
|
||||
}
|
||||
// customize properties
|
||||
if (input_def.type == "data_column") {
|
||||
input_def.error_text = "Missing columns in referenced dataset.";
|
||||
}
|
||||
|
||||
// customize properties
|
||||
if ( input_def.type == 'data_column' ) {
|
||||
input_def.error_text = 'Missing columns in referenced dataset.'
|
||||
}
|
||||
|
||||
// identify available options
|
||||
var data = input_def.data;
|
||||
if( !data ) {
|
||||
data = [];
|
||||
_.each( input_def.options, function( option ) {
|
||||
data.push( { label: option[ 0 ], value: option[ 1 ] } );
|
||||
});
|
||||
}
|
||||
|
||||
// identify display type
|
||||
var SelectClass = Ui.Select;
|
||||
switch ( input_def.display ) {
|
||||
case 'checkboxes':
|
||||
SelectClass = Ui.Checkbox;
|
||||
break;
|
||||
case 'radio':
|
||||
SelectClass = Ui.Radio;
|
||||
break;
|
||||
case 'radiobutton':
|
||||
SelectClass = Ui.RadioButton;
|
||||
break;
|
||||
}
|
||||
|
||||
// create select field
|
||||
return new SelectClass.View({
|
||||
id : 'field-' + input_def.id,
|
||||
data : data,
|
||||
error_text : input_def.error_text || 'No options available',
|
||||
multiple : input_def.multiple,
|
||||
optional : input_def.optional,
|
||||
onchange : input_def.onchange,
|
||||
searchable : input_def.flavor !== 'workflow'
|
||||
});
|
||||
},
|
||||
|
||||
/** Drill down options field */
|
||||
_fieldDrilldown: function ( input_def ) {
|
||||
// show text field e.g. in workflow editor
|
||||
if( input_def.is_workflow ) {
|
||||
return this._fieldText( input_def );
|
||||
}
|
||||
|
||||
// create drill down field
|
||||
return new Ui.Drilldown.View({
|
||||
id : 'field-' + input_def.id,
|
||||
data : input_def.options,
|
||||
display : input_def.display,
|
||||
optional : input_def.optional,
|
||||
onchange : input_def.onchange
|
||||
});
|
||||
},
|
||||
|
||||
/** Text input field */
|
||||
_fieldText: function( input_def ) {
|
||||
// field replaces e.g. a select field
|
||||
if ( input_def.options && input_def.data ) {
|
||||
input_def.area = input_def.multiple;
|
||||
if ( Utils.isEmpty( input_def.value ) ) {
|
||||
input_def.value = null;
|
||||
} else {
|
||||
if ( $.isArray( input_def.value ) ) {
|
||||
var str_value = '';
|
||||
for ( var i in input_def.value ) {
|
||||
str_value += String( input_def.value[ i ] );
|
||||
if ( !input_def.multiple ) {
|
||||
break;
|
||||
}
|
||||
str_value += '\n';
|
||||
}
|
||||
input_def.value = str_value;
|
||||
}
|
||||
}
|
||||
}
|
||||
// create input element
|
||||
return new Ui.Input({
|
||||
id : 'field-' + input_def.id,
|
||||
type : input_def.type,
|
||||
area : input_def.area,
|
||||
readonly : input_def.readonly,
|
||||
placeholder : input_def.placeholder,
|
||||
datalist : input_def.datalist,
|
||||
onchange : input_def.onchange
|
||||
});
|
||||
},
|
||||
|
||||
/** Slider field */
|
||||
_fieldSlider: function( input_def ) {
|
||||
return new Ui.Slider.View({
|
||||
id : 'field-' + input_def.id,
|
||||
precise : input_def.type == 'float',
|
||||
is_workflow : input_def.is_workflow,
|
||||
min : input_def.min,
|
||||
max : input_def.max,
|
||||
onchange : input_def.onchange
|
||||
});
|
||||
},
|
||||
|
||||
/** Hidden field */
|
||||
_fieldHidden: function( input_def ) {
|
||||
return new Ui.Hidden({
|
||||
id : 'field-' + input_def.id,
|
||||
info : input_def.info
|
||||
});
|
||||
},
|
||||
|
||||
/** Boolean field */
|
||||
_fieldBoolean: function( input_def ) {
|
||||
return new Ui.RadioButton.View({
|
||||
id : 'field-' + input_def.id,
|
||||
data : [ { label : 'Yes', value : 'true' },
|
||||
{ label : 'No', value : 'false' }],
|
||||
onchange : input_def.onchange
|
||||
});
|
||||
},
|
||||
|
||||
/** Color picker field */
|
||||
_fieldColor: function( input_def ) {
|
||||
return new ColorPicker({
|
||||
id : 'field-' + input_def.id,
|
||||
onchange : input_def.onchange
|
||||
});
|
||||
},
|
||||
|
||||
/** Library dataset field */
|
||||
_fieldLibrary: function( input_def ) {
|
||||
return new SelectLibrary.View({
|
||||
id : 'field-' + input_def.id,
|
||||
optional : input_def.optional,
|
||||
multiple : input_def.multiple,
|
||||
onchange : input_def.onchange
|
||||
});
|
||||
},
|
||||
|
||||
/** FTP file field */
|
||||
_fieldFtp: function( input_def ) {
|
||||
return new SelectFtp.View({
|
||||
id : 'field-' + input_def.id,
|
||||
optional : input_def.optional,
|
||||
multiple : input_def.multiple,
|
||||
onchange : input_def.onchange
|
||||
});
|
||||
},
|
||||
|
||||
/** Upload file field */
|
||||
_fieldUpload: function( input_def ) {
|
||||
return new Ui.Upload({
|
||||
id : 'field-' + input_def.id,
|
||||
onchange : input_def.onchange
|
||||
// identify available options
|
||||
var data = input_def.data;
|
||||
if (!data) {
|
||||
data = [];
|
||||
_.each(input_def.options, option => {
|
||||
data.push({ label: option[0], value: option[1] });
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
View: View
|
||||
};
|
||||
});
|
||||
// identify display type
|
||||
var SelectClass = Ui.Select;
|
||||
switch (input_def.display) {
|
||||
case "checkboxes":
|
||||
SelectClass = Ui.Checkbox;
|
||||
break;
|
||||
case "radio":
|
||||
SelectClass = Ui.Radio;
|
||||
break;
|
||||
case "radiobutton":
|
||||
SelectClass = Ui.RadioButton;
|
||||
break;
|
||||
}
|
||||
|
||||
// create select field
|
||||
return new SelectClass.View({
|
||||
id: `field-${input_def.id}`,
|
||||
data: data,
|
||||
error_text: input_def.error_text || "No options available",
|
||||
readonly: input_def.readonly,
|
||||
multiple: input_def.multiple,
|
||||
optional: input_def.optional,
|
||||
onchange: input_def.onchange,
|
||||
individual: input_def.individual,
|
||||
searchable: input_def.flavor !== "workflow"
|
||||
});
|
||||
},
|
||||
|
||||
/** Drill down options field */
|
||||
_fieldDrilldown: function(input_def) {
|
||||
// show text field e.g. in workflow editor
|
||||
if (input_def.is_workflow) {
|
||||
return this._fieldText(input_def);
|
||||
}
|
||||
|
||||
// create drill down field
|
||||
return new Ui.Drilldown.View({
|
||||
id: `field-${input_def.id}`,
|
||||
data: input_def.options,
|
||||
display: input_def.display,
|
||||
optional: input_def.optional,
|
||||
onchange: input_def.onchange
|
||||
});
|
||||
},
|
||||
|
||||
/** Text input field */
|
||||
_fieldText: function(input_def) {
|
||||
// field replaces e.g. a select field
|
||||
if (input_def.options && input_def.data) {
|
||||
input_def.area = input_def.multiple;
|
||||
if (Utils.isEmpty(input_def.value)) {
|
||||
input_def.value = null;
|
||||
} else {
|
||||
if ($.isArray(input_def.value)) {
|
||||
var str_value = "";
|
||||
for (var i in input_def.value) {
|
||||
str_value += String(input_def.value[i]);
|
||||
if (!input_def.multiple) {
|
||||
break;
|
||||
}
|
||||
str_value += "\n";
|
||||
}
|
||||
input_def.value = str_value;
|
||||
}
|
||||
}
|
||||
}
|
||||
// create input element
|
||||
return new Ui.Input({
|
||||
id: `field-${input_def.id}`,
|
||||
type: input_def.type,
|
||||
area: input_def.area,
|
||||
readonly: input_def.readonly,
|
||||
placeholder: input_def.placeholder,
|
||||
datalist: input_def.datalist,
|
||||
onchange: input_def.onchange
|
||||
});
|
||||
},
|
||||
|
||||
/** Slider field */
|
||||
_fieldSlider: function(input_def) {
|
||||
return new Ui.Slider.View({
|
||||
id: `field-${input_def.id}`,
|
||||
precise: input_def.type == "float",
|
||||
is_workflow: input_def.is_workflow,
|
||||
min: input_def.min,
|
||||
max: input_def.max,
|
||||
onchange: input_def.onchange
|
||||
});
|
||||
},
|
||||
|
||||
/** Hidden field */
|
||||
_fieldHidden: function(input_def) {
|
||||
return new Ui.Hidden({
|
||||
id: `field-${input_def.id}`,
|
||||
info: input_def.info
|
||||
});
|
||||
},
|
||||
|
||||
/** Boolean field */
|
||||
_fieldBoolean: function(input_def) {
|
||||
return new Ui.RadioButton.View({
|
||||
id: `field-${input_def.id}`,
|
||||
data: [{ label: "Yes", value: "true" }, { label: "No", value: "false" }],
|
||||
onchange: input_def.onchange
|
||||
});
|
||||
},
|
||||
|
||||
/** Color picker field */
|
||||
_fieldColor: function(input_def) {
|
||||
return new ColorPicker({
|
||||
id: `field-${input_def.id}`,
|
||||
onchange: input_def.onchange
|
||||
});
|
||||
},
|
||||
|
||||
/** Library dataset field */
|
||||
_fieldLibrary: function(input_def) {
|
||||
return new SelectLibrary.View({
|
||||
id: `field-${input_def.id}`,
|
||||
optional: input_def.optional,
|
||||
multiple: input_def.multiple,
|
||||
onchange: input_def.onchange
|
||||
});
|
||||
},
|
||||
|
||||
/** FTP file field */
|
||||
_fieldFtp: function(input_def) {
|
||||
return new SelectFtp.View({
|
||||
id: `field-${input_def.id}`,
|
||||
optional: input_def.optional,
|
||||
multiple: input_def.multiple,
|
||||
onchange: input_def.onchange
|
||||
});
|
||||
},
|
||||
|
||||
/** GenomeSpace file select field
|
||||
*/
|
||||
_fieldGenomeSpace: function(input_def) {
|
||||
var self = this;
|
||||
return new SelectGenomeSpace.View({
|
||||
id: `field-${input_def.id}`,
|
||||
onchange: function() {
|
||||
self.app.trigger("change");
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/** Upload file field */
|
||||
_fieldUpload: function(input_def) {
|
||||
return new Ui.Upload({
|
||||
id: `field-${input_def.id}`,
|
||||
onchange: input_def.onchange
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,96 +1,118 @@
|
||||
import _l from "utils/localization";
|
||||
/** This class creates a ui component which enables the dynamic creation of portlets */
|
||||
define( [ 'utils/utils', 'mvc/ui/ui-portlet', 'mvc/ui/ui-misc' ],
|
||||
function( Utils, Portlet, Ui ) {
|
||||
var View = Backbone.View.extend({
|
||||
initialize: function( options ) {
|
||||
this.list = {};
|
||||
this.options = Utils.merge( options, {
|
||||
title : 'Repeat',
|
||||
empty_text : 'Not available.',
|
||||
max : null,
|
||||
min : null
|
||||
});
|
||||
this.button_new = new Ui.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() { options.onnew && options.onnew() }
|
||||
});
|
||||
this.setElement( $( '<div/>' ).append( this.$list = $( '<div/>' ) )
|
||||
.append( $( '<div/>' ).append( this.button_new.$el ) ) );
|
||||
},
|
||||
import Utils from "utils/utils";
|
||||
import Portlet from "mvc/ui/ui-portlet";
|
||||
import Ui from "mvc/ui/ui-misc";
|
||||
|
||||
/** Number of repeat blocks */
|
||||
size: function() {
|
||||
return _.size( this.list );
|
||||
},
|
||||
|
||||
/** Add new repeat block */
|
||||
add: function( options ) {
|
||||
if ( !options.id || this.list[ options.id ] ) {
|
||||
Galaxy.emit.debug( 'form-repeat::add()', 'Duplicate or invalid repeat block id.' );
|
||||
return;
|
||||
export var View = Backbone.View.extend({
|
||||
initialize: function(options) {
|
||||
this.list = {};
|
||||
this.options = Utils.merge(options, {
|
||||
title: _l("Repeat"),
|
||||
empty_text: "Not available.",
|
||||
max: null,
|
||||
min: null
|
||||
});
|
||||
this.button_new = new Ui.ButtonIcon({
|
||||
icon: "fa-plus",
|
||||
title: `Insert ${this.options.title}`,
|
||||
tooltip: `Add new ${this.options.title} block`,
|
||||
cls: "ui-button-icon ui-clear-float form-repeat-add",
|
||||
onclick: function() {
|
||||
if (options.onnew) {
|
||||
options.onnew();
|
||||
}
|
||||
}
|
||||
var button_delete = new Ui.ButtonIcon({
|
||||
icon : 'fa-trash-o',
|
||||
tooltip : 'Delete this repeat block',
|
||||
cls : 'ui-button-icon-plain form-repeat-delete',
|
||||
onclick : function() { options.ondel && options.ondel() }
|
||||
});
|
||||
var portlet = new Portlet.View({
|
||||
id : options.id,
|
||||
title : 'placeholder',
|
||||
cls : options.cls || 'ui-portlet-repeat',
|
||||
operations : { button_delete: button_delete }
|
||||
});
|
||||
portlet.append( options.$el );
|
||||
portlet.$el.addClass( 'section-row' ).hide();
|
||||
this.list[ options.id ] = portlet;
|
||||
this.$list.append( portlet.$el.fadeIn( 'fast' ) );
|
||||
this.options.max > 0 && this.size() >= this.options.max && this.button_new.disable();
|
||||
this._refresh();
|
||||
},
|
||||
});
|
||||
this.setElement(
|
||||
$("<div/>")
|
||||
.append((this.$list = $("<div/>")))
|
||||
.append($("<div/>").append(this.button_new.$el))
|
||||
);
|
||||
},
|
||||
|
||||
/** Delete repeat block */
|
||||
del: function( id ) {
|
||||
if ( !this.list[ id ] ) {
|
||||
Galaxy.emit.debug( 'form-repeat::del()', 'Invalid repeat block id.' );
|
||||
return;
|
||||
}
|
||||
this.$list.find( '#' + id ).remove();
|
||||
delete this.list[ id ];
|
||||
this.button_new.enable();
|
||||
this._refresh();
|
||||
},
|
||||
/** Number of repeat blocks */
|
||||
size: function() {
|
||||
return _.size(this.list);
|
||||
},
|
||||
|
||||
/** Remove all */
|
||||
delAll: function() {
|
||||
for( var id in this.list ) {
|
||||
this.del( id );
|
||||
}
|
||||
},
|
||||
|
||||
/** Hides add/del options */
|
||||
hideOptions: function() {
|
||||
this.button_new.$el.hide();
|
||||
_.each( this.list, function( portlet ) { portlet.hideOperation( 'button_delete' ) } );
|
||||
_.isEmpty( this.list ) && this.$el.append( $( '<div/>' ).addClass( 'ui-form-info' ).html( this.options.empty_text ) );
|
||||
},
|
||||
|
||||
/** Refresh view */
|
||||
_refresh: function() {
|
||||
var index = 0;
|
||||
for ( var id in this.list ) {
|
||||
var portlet = this.list[ id ];
|
||||
portlet.title( ++index + ': ' + this.options.title );
|
||||
portlet[ this.size() > this.options.min ? 'showOperation' : 'hideOperation' ]( 'button_delete' );
|
||||
}
|
||||
/** Add new repeat block */
|
||||
add: function(options) {
|
||||
if (!options.id || this.list[options.id]) {
|
||||
Galaxy.emit.debug("form-repeat::add()", "Duplicate or invalid repeat block id.");
|
||||
return;
|
||||
}
|
||||
});
|
||||
var button_delete = new Ui.ButtonIcon({
|
||||
icon: "fa-trash-o",
|
||||
tooltip: _l("Delete this repeat block"),
|
||||
cls: "ui-button-icon-plain form-repeat-delete",
|
||||
onclick: function() {
|
||||
if (options.ondel) {
|
||||
options.ondel();
|
||||
}
|
||||
}
|
||||
});
|
||||
var portlet = new Portlet.View({
|
||||
id: options.id,
|
||||
title: _l("placeholder"),
|
||||
cls: options.cls || "ui-portlet-repeat",
|
||||
operations: { button_delete: button_delete }
|
||||
});
|
||||
portlet.append(options.$el);
|
||||
portlet.$el.addClass("section-row").hide();
|
||||
this.list[options.id] = portlet;
|
||||
this.$list.append(portlet.$el.fadeIn("fast"));
|
||||
if (this.options.max > 0 && this.size() >= this.options.max) {
|
||||
this.button_new.disable();
|
||||
}
|
||||
this._refresh();
|
||||
},
|
||||
|
||||
return {
|
||||
View : View
|
||||
/** Delete repeat block */
|
||||
del: function(id) {
|
||||
if (!this.list[id]) {
|
||||
Galaxy.emit.debug("form-repeat::del()", "Invalid repeat block id.");
|
||||
return;
|
||||
}
|
||||
this.$list.find(`#${id}`).remove();
|
||||
delete this.list[id];
|
||||
this.button_new.enable();
|
||||
this._refresh();
|
||||
},
|
||||
|
||||
/** Remove all */
|
||||
delAll: function() {
|
||||
for (var id in this.list) {
|
||||
this.del(id);
|
||||
}
|
||||
},
|
||||
|
||||
/** Hides add/del options */
|
||||
hideOptions: function() {
|
||||
this.button_new.$el.hide();
|
||||
_.each(this.list, portlet => {
|
||||
portlet.hideOperation("button_delete");
|
||||
});
|
||||
if (_.isEmpty(this.list)) {
|
||||
this.$el.append(
|
||||
$("<div/>")
|
||||
.addClass("ui-form-info")
|
||||
.html(this.options.empty_text)
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
/** Refresh view */
|
||||
_refresh: function() {
|
||||
var index = 0;
|
||||
for (var id in this.list) {
|
||||
var portlet = this.list[id];
|
||||
portlet.title(`${++index}: ${this.options.title}`);
|
||||
portlet[this.size() > this.options.min ? "showOperation" : "hideOperation"]("button_delete");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default {
|
||||
View: View
|
||||
};
|
||||
|
||||
@@ -1,176 +1,202 @@
|
||||
/**
|
||||
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.
|
||||
*/
|
||||
define([ 'utils/utils', 'mvc/ui/ui-misc', 'mvc/ui/ui-portlet', 'mvc/form/form-repeat', 'mvc/form/form-input', 'mvc/form/form-parameters' ],
|
||||
function( Utils, Ui, Portlet, Repeat, InputElement, Parameters ) {
|
||||
var View = Backbone.View.extend({
|
||||
initialize: function( app, options ) {
|
||||
this.app = app;
|
||||
this.inputs = options.inputs;
|
||||
this.parameters = new Parameters();
|
||||
this.setElement( $( '<div/>' ) );
|
||||
this.render();
|
||||
},
|
||||
import Utils from "utils/utils";
|
||||
import Ui from "mvc/ui/ui-misc";
|
||||
import Portlet from "mvc/ui/ui-portlet";
|
||||
import Repeat from "mvc/form/form-repeat";
|
||||
import InputElement from "mvc/form/form-input";
|
||||
import Parameters from "mvc/form/form-parameters";
|
||||
var View = Backbone.View.extend({
|
||||
initialize: function(app, options) {
|
||||
this.app = app;
|
||||
this.inputs = options.inputs;
|
||||
this.parameters = new Parameters();
|
||||
this.setElement($("<div/>"));
|
||||
this.render();
|
||||
},
|
||||
|
||||
/** Render section view */
|
||||
render: function() {
|
||||
var self = this;
|
||||
this.$el.empty();
|
||||
_.each( this.inputs, function( input ) { self.add( input ) } );
|
||||
},
|
||||
/** Render section view */
|
||||
render: function() {
|
||||
var self = this;
|
||||
this.$el.empty();
|
||||
_.each(this.inputs, input => {
|
||||
self.add(input);
|
||||
});
|
||||
},
|
||||
|
||||
/** Add a new input element */
|
||||
add: function( input ) {
|
||||
var input_def = jQuery.extend( true, {}, input );
|
||||
input_def.id = input.id = Utils.uid();
|
||||
this.app.input_list[ input_def.id ] = input_def;
|
||||
switch( input_def.type ) {
|
||||
case 'conditional':
|
||||
this._addConditional( input_def );
|
||||
break;
|
||||
case 'repeat':
|
||||
this._addRepeat( input_def );
|
||||
break;
|
||||
case 'section':
|
||||
this._addSection( input_def );
|
||||
break;
|
||||
default:
|
||||
this._addRow( input_def );
|
||||
}
|
||||
},
|
||||
/** Add a new input element */
|
||||
add: function(input) {
|
||||
var input_def = jQuery.extend(true, {}, input);
|
||||
input_def.id = input.id = Utils.uid();
|
||||
this.app.input_list[input_def.id] = input_def;
|
||||
switch (input_def.type) {
|
||||
case "conditional":
|
||||
this._addConditional(input_def);
|
||||
break;
|
||||
case "repeat":
|
||||
this._addRepeat(input_def);
|
||||
break;
|
||||
case "section":
|
||||
this._addSection(input_def);
|
||||
break;
|
||||
default:
|
||||
this._addRow(input_def);
|
||||
}
|
||||
},
|
||||
|
||||
/** Add a conditional block */
|
||||
_addConditional: function( input_def ) {
|
||||
var self = this;
|
||||
input_def.test_param.id = input_def.id;
|
||||
this.app.model.get( 'sustain_conditionals' ) && ( input_def.test_param.disabled = true );
|
||||
var field = this._addRow( input_def.test_param );
|
||||
/** Add a conditional block */
|
||||
_addConditional: function(input_def) {
|
||||
var self = this;
|
||||
input_def.test_param.id = input_def.id;
|
||||
this.app.model.get("sustain_conditionals") && (input_def.test_param.disabled = true);
|
||||
var field = this._addRow(input_def.test_param);
|
||||
|
||||
// set onchange event for test parameter
|
||||
field.model && field.model.set( 'onchange', function( value ) {
|
||||
var selectedCase = self.app.data.matchCase( input_def, value );
|
||||
for ( var i in input_def.cases ) {
|
||||
var case_def = input_def.cases[ i ];
|
||||
var section_row = self.$( '#' + input_def.id + '-section-' + i );
|
||||
// set onchange event for test parameter
|
||||
field.model &&
|
||||
field.model.set("onchange", value => {
|
||||
var selectedCase = self.app.data.matchCase(input_def, value);
|
||||
for (var i in input_def.cases) {
|
||||
var case_def = input_def.cases[i];
|
||||
var section_row = self.$(`#${input_def.id}-section-${i}`);
|
||||
var nonhidden = false;
|
||||
for ( var j in case_def.inputs ) {
|
||||
if ( !case_def.inputs[ j ].hidden ) {
|
||||
for (var j in case_def.inputs) {
|
||||
if (!case_def.inputs[j].hidden) {
|
||||
nonhidden = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( i == selectedCase && nonhidden ) {
|
||||
section_row.fadeIn( 'fast' );
|
||||
if (i == selectedCase && nonhidden) {
|
||||
section_row.fadeIn("fast");
|
||||
} else {
|
||||
section_row.hide();
|
||||
}
|
||||
}
|
||||
self.app.trigger( 'change' );
|
||||
self.app.trigger("change");
|
||||
});
|
||||
|
||||
// add conditional sub sections
|
||||
for ( var i in input_def.cases ) {
|
||||
var sub_section = new View( this.app, { inputs: input_def.cases[ i ].inputs } );
|
||||
this._append( sub_section.$el.addClass( 'ui-form-section' ), input_def.id + '-section-' + i );
|
||||
}
|
||||
|
||||
// trigger refresh on conditional input field after all input elements have been created
|
||||
field.trigger( 'change' );
|
||||
},
|
||||
|
||||
/** Add a repeat block */
|
||||
_addRepeat: function( input_def ) {
|
||||
var self = this;
|
||||
var block_index = 0;
|
||||
|
||||
// create repeat block element
|
||||
var repeat = new Repeat.View({
|
||||
title : input_def.title || 'Repeat',
|
||||
min : input_def.min,
|
||||
max : input_def.max,
|
||||
onnew : function() { create( input_def.inputs ); self.app.trigger( 'change' ); }
|
||||
// add conditional sub sections
|
||||
for (var i in input_def.cases) {
|
||||
var sub_section = new View(this.app, {
|
||||
inputs: input_def.cases[i].inputs
|
||||
});
|
||||
|
||||
// helper function to create new repeat blocks
|
||||
function create ( inputs ) {
|
||||
var sub_section_id = input_def.id + '-section-' + ( block_index++ );
|
||||
var sub_section = new View( self.app, { inputs: inputs } );
|
||||
repeat.add( { id : sub_section_id,
|
||||
$el : sub_section.$el,
|
||||
ondel : function() { repeat.del( sub_section_id ); self.app.trigger( 'change' ); } } );
|
||||
}
|
||||
|
||||
//
|
||||
// add parsed/minimum number of repeat blocks
|
||||
//
|
||||
var n_cache = _.size( input_def.cache );
|
||||
for ( var i = 0; i < Math.max( Math.max( n_cache, input_def.min || 0 ), input_def.default || 0 ); i++ ) {
|
||||
create( i < n_cache ? input_def.cache[ i ] : input_def.inputs );
|
||||
}
|
||||
|
||||
// hide options
|
||||
this.app.model.get( 'sustain_repeats' ) && repeat.hideOptions();
|
||||
|
||||
// create input field wrapper
|
||||
var input_element = new InputElement( this.app, {
|
||||
label : input_def.title || input_def.name,
|
||||
help : input_def.help,
|
||||
field : repeat
|
||||
});
|
||||
this._append( input_element.$el, input_def.id );
|
||||
},
|
||||
|
||||
/** Add a customized section */
|
||||
_addSection: function( input_def ) {
|
||||
var portlet = new Portlet.View({
|
||||
title : input_def.title || input_def.name,
|
||||
cls : 'ui-portlet-section',
|
||||
collapsible : true,
|
||||
collapsible_button : true,
|
||||
collapsed : !input_def.expanded
|
||||
});
|
||||
portlet.append( new View( this.app, { inputs: input_def.inputs } ).$el );
|
||||
portlet.append( $( '<div/>' ).addClass( 'ui-form-info' ).html( input_def.help ) );
|
||||
this.app.on( 'expand', function( input_id ) { ( portlet.$( '#' + input_id ).length > 0 ) && portlet.expand(); } );
|
||||
this._append( portlet.$el, input_def.id );
|
||||
},
|
||||
|
||||
/** Add a single input field element */
|
||||
_addRow: function( input_def ) {
|
||||
var self = this;
|
||||
var id = input_def.id;
|
||||
input_def.onchange = input_def.onchange || function() { self.app.trigger( 'change', id ) };
|
||||
var field = this.parameters.create( input_def );
|
||||
this.app.field_list[ id ] = field;
|
||||
var input_element = new InputElement( this.app, {
|
||||
name : input_def.name,
|
||||
label : input_def.hide_label ? '' : input_def.label || input_def.name,
|
||||
value : input_def.value,
|
||||
text_value : input_def.text_value,
|
||||
collapsible_value : input_def.collapsible_value,
|
||||
collapsible_preview : input_def.collapsible_preview,
|
||||
help : input_def.help,
|
||||
argument : input_def.argument,
|
||||
disabled : input_def.disabled,
|
||||
color : input_def.color,
|
||||
style : input_def.style,
|
||||
backdrop : input_def.backdrop,
|
||||
hidden : input_def.hidden,
|
||||
fixed : input_def.fixed,
|
||||
field : field
|
||||
});
|
||||
this.app.element_list[ id ] = input_element;
|
||||
this._append( input_element.$el, input_def.id );
|
||||
return field;
|
||||
},
|
||||
|
||||
/** Append a new element to the form i.e. input element, repeat block, conditionals etc. */
|
||||
_append: function( $el, id ) {
|
||||
this.$el.append( $el.addClass( 'section-row' ).attr( 'id', id ) );
|
||||
this._append(sub_section.$el.addClass("ui-form-section"), `${input_def.id}-section-${i}`);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
View: View
|
||||
};
|
||||
});
|
||||
// trigger refresh on conditional input field after all input elements have been created
|
||||
field.trigger("change");
|
||||
},
|
||||
|
||||
/** Add a repeat block */
|
||||
_addRepeat: function(input_def) {
|
||||
var self = this;
|
||||
var block_index = 0;
|
||||
|
||||
// create repeat block element
|
||||
var repeat = new Repeat.View({
|
||||
title: input_def.title || "Repeat",
|
||||
min: input_def.min,
|
||||
max: input_def.max,
|
||||
onnew: function() {
|
||||
create(input_def.inputs);
|
||||
self.app.trigger("change");
|
||||
}
|
||||
});
|
||||
|
||||
// helper function to create new repeat blocks
|
||||
function create(inputs) {
|
||||
var sub_section_id = `${input_def.id}-section-${block_index++}`;
|
||||
var sub_section = new View(self.app, { inputs: inputs });
|
||||
repeat.add({
|
||||
id: sub_section_id,
|
||||
$el: sub_section.$el,
|
||||
ondel: function() {
|
||||
repeat.del(sub_section_id);
|
||||
self.app.trigger("change");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
// add parsed/minimum number of repeat blocks
|
||||
//
|
||||
var n_cache = _.size(input_def.cache);
|
||||
for (var i = 0; i < Math.max(Math.max(n_cache, input_def.min || 0), input_def.default || 0); i++) {
|
||||
create(i < n_cache ? input_def.cache[i] : input_def.inputs);
|
||||
}
|
||||
|
||||
// hide options
|
||||
this.app.model.get("sustain_repeats") && repeat.hideOptions();
|
||||
|
||||
// create input field wrapper
|
||||
var input_element = new InputElement(this.app, {
|
||||
label: input_def.title || input_def.name,
|
||||
help: input_def.help,
|
||||
field: repeat
|
||||
});
|
||||
this._append(input_element.$el, input_def.id);
|
||||
},
|
||||
|
||||
/** Add a customized section */
|
||||
_addSection: function(input_def) {
|
||||
var portlet = new Portlet.View({
|
||||
title: input_def.title || input_def.name,
|
||||
cls: "ui-portlet-section",
|
||||
collapsible: true,
|
||||
collapsible_button: true,
|
||||
collapsed: !input_def.expanded
|
||||
});
|
||||
portlet.append(new View(this.app, { inputs: input_def.inputs }).$el);
|
||||
portlet.append(
|
||||
$("<div/>")
|
||||
.addClass("ui-form-info")
|
||||
.html(input_def.help)
|
||||
);
|
||||
this.app.on("expand", input_id => {
|
||||
portlet.$(`#${input_id}`).length > 0 && portlet.expand();
|
||||
});
|
||||
this._append(portlet.$el, input_def.id);
|
||||
},
|
||||
|
||||
/** Add a single input field element */
|
||||
_addRow: function(input_def) {
|
||||
var self = this;
|
||||
var id = input_def.id;
|
||||
input_def.onchange =
|
||||
input_def.onchange ||
|
||||
(() => {
|
||||
self.app.trigger("change", id);
|
||||
});
|
||||
var field = this.parameters.create(input_def);
|
||||
this.app.field_list[id] = field;
|
||||
var input_element = new InputElement(this.app, {
|
||||
name: input_def.name,
|
||||
label: input_def.hide_label ? "" : input_def.label || input_def.name,
|
||||
value: input_def.value,
|
||||
text_value: input_def.text_value,
|
||||
collapsible_value: input_def.collapsible_value,
|
||||
collapsible_preview: input_def.collapsible_preview,
|
||||
help: input_def.help,
|
||||
argument: input_def.argument,
|
||||
disabled: input_def.disabled,
|
||||
color: input_def.color,
|
||||
style: input_def.style,
|
||||
backdrop: input_def.backdrop,
|
||||
hidden: input_def.hidden,
|
||||
fixed: input_def.fixed,
|
||||
field: field
|
||||
});
|
||||
this.app.element_list[id] = input_element;
|
||||
this._append(input_element.$el, input_def.id);
|
||||
return field;
|
||||
},
|
||||
|
||||
/** Append a new element to the form i.e. input element, repeat block, conditionals etc. */
|
||||
_append: function($el, id) {
|
||||
this.$el.append($el.addClass("section-row").attr("id", id));
|
||||
}
|
||||
});
|
||||
|
||||
export default {
|
||||
View: View
|
||||
};
|
||||
|
||||
@@ -1,148 +1,178 @@
|
||||
/**
|
||||
This is the main class of the form plugin. It is referenced as 'app' in lower level modules.
|
||||
*/
|
||||
define( [ 'utils/utils', 'mvc/ui/ui-portlet', 'mvc/ui/ui-misc', 'mvc/form/form-section', 'mvc/form/form-data' ],
|
||||
function( Utils, Portlet, Ui, FormSection, FormData ) {
|
||||
return Backbone.View.extend({
|
||||
initialize: function( options ) {
|
||||
this.model = new Backbone.Model({
|
||||
initial_errors : false,
|
||||
cls : 'ui-portlet-limited',
|
||||
icon : null,
|
||||
always_refresh : true,
|
||||
message_status : 'warning'
|
||||
}).set( options );
|
||||
this.setElement( '<div/>' );
|
||||
this.render();
|
||||
},
|
||||
import Portlet from "mvc/ui/ui-portlet";
|
||||
import Ui from "mvc/ui/ui-misc";
|
||||
import FormSection from "mvc/form/form-section";
|
||||
import FormData from "mvc/form/form-data";
|
||||
|
||||
/** Update available options */
|
||||
update: function( new_model ){
|
||||
var self = this;
|
||||
this.data.matchModel( new_model, function( node, input_id ) {
|
||||
var input = self.input_list[ input_id ];
|
||||
if ( input && input.options ) {
|
||||
if ( !_.isEqual( input.options, node.options ) ) {
|
||||
input.options = node.options;
|
||||
var field = self.field_list[ input_id ];
|
||||
if ( field.update ) {
|
||||
var new_options = [];
|
||||
if ( ( [ 'data', 'data_collection', 'drill_down' ] ).indexOf( input.type ) != -1 ) {
|
||||
new_options = input.options;
|
||||
} else {
|
||||
for ( var i in node.options ) {
|
||||
var opt = node.options[ i ];
|
||||
if ( opt.length > 2 ) {
|
||||
new_options.push( { label: opt[ 0 ], value: opt[ 1 ] } );
|
||||
}
|
||||
export var View = Backbone.View.extend({
|
||||
initialize: function(options) {
|
||||
this.model = new Backbone.Model({
|
||||
initial_errors: false,
|
||||
cls: "ui-portlet-limited",
|
||||
icon: null,
|
||||
always_refresh: true,
|
||||
status: "warning",
|
||||
hide_operations: false,
|
||||
onchange: function() {}
|
||||
}).set(options);
|
||||
this.setElement("<div/>");
|
||||
this.render();
|
||||
},
|
||||
|
||||
/** Update available options */
|
||||
update: function(new_model) {
|
||||
var self = this;
|
||||
this.data.matchModel(new_model, (node, input_id) => {
|
||||
var input = self.input_list[input_id];
|
||||
if (input && input.options) {
|
||||
if (!_.isEqual(input.options, node.options)) {
|
||||
input.options = node.options;
|
||||
var field = self.field_list[input_id];
|
||||
if (field.update) {
|
||||
var new_options = [];
|
||||
if (["data", "data_collection", "drill_down"].indexOf(input.type) != -1) {
|
||||
new_options = input.options;
|
||||
} else {
|
||||
for (var i in node.options) {
|
||||
var opt = node.options[i];
|
||||
if (opt.length > 2) {
|
||||
new_options.push({
|
||||
label: opt[0],
|
||||
value: opt[1]
|
||||
});
|
||||
}
|
||||
}
|
||||
field.update( new_options );
|
||||
field.trigger( 'change' );
|
||||
Galaxy.emit.debug( 'form-view::update()', 'Updating options for ' + input_id );
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/** Set form into wait mode */
|
||||
wait: function( active ) {
|
||||
for ( var i in this.input_list ) {
|
||||
var field = this.field_list[ i ];
|
||||
var input = this.input_list[ i ];
|
||||
if ( input.is_dynamic && field.wait && field.unwait ) {
|
||||
field[ active ? 'wait' : 'unwait' ]();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/** Highlight and scroll to input element (currently only used for error notifications) */
|
||||
highlight: function ( input_id, message, silent ) {
|
||||
var input_element = this.element_list[ input_id ];
|
||||
if ( input_element ) {
|
||||
input_element.error( message || 'Please verify this parameter.' );
|
||||
this.portlet.expand();
|
||||
this.trigger( 'expand', input_id );
|
||||
if ( !silent ) {
|
||||
var $panel = this.$el.parents().filter(function() {
|
||||
return [ 'auto', 'scroll' ].indexOf( $( this ).css( 'overflow' ) ) != -1;
|
||||
}).first();
|
||||
$panel.animate( { scrollTop : $panel.scrollTop() + input_element.$el.offset().top - 120 }, 500 );
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/** Highlights errors */
|
||||
errors: function( options ) {
|
||||
this.trigger( 'reset' );
|
||||
if ( options && options.errors ) {
|
||||
var error_messages = this.data.matchResponse( options.errors );
|
||||
for ( var input_id in this.element_list ) {
|
||||
var input = this.element_list[ input_id ];
|
||||
if ( error_messages[ input_id ] ) {
|
||||
this.highlight( input_id, error_messages[ input_id ], true );
|
||||
field.update(new_options);
|
||||
field.trigger("change");
|
||||
Galaxy.emit.debug("form-view::update()", `Updating options for ${input_id}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
/** Render tool form */
|
||||
render: function() {
|
||||
var self = this;
|
||||
this.off('change');
|
||||
this.off('reset');
|
||||
// contains the dom field elements as created by the parameter factory i.e. form-parameters
|
||||
this.field_list = {};
|
||||
// contains input definitions/dictionaries as provided by the parameters to_dict() function through the api
|
||||
this.input_list = {};
|
||||
// contains the dom elements of each input element i.e. form-input which wraps the actual input field
|
||||
this.element_list = {};
|
||||
// converts the form into a json data structure
|
||||
this.data = new FormData.Manager( this );
|
||||
this._renderForm();
|
||||
this.data.create();
|
||||
this.model.get( 'initial_errors' ) && this.errors( this.model.attributes );
|
||||
// add listener which triggers on checksum change, and reset the form input wrappers
|
||||
var current_check = this.data.checksum();
|
||||
this.on( 'change', function( input_id ) {
|
||||
var input = self.input_list[ input_id ];
|
||||
if ( !input || input.refresh_on_change || self.model.get( 'always_refresh' ) ) {
|
||||
var new_check = self.data.checksum();
|
||||
if ( new_check != current_check ) {
|
||||
current_check = new_check;
|
||||
self.model.get( 'onchange' ) && self.model.get( 'onchange' )();
|
||||
}
|
||||
}
|
||||
});
|
||||
this.on('reset', function() {
|
||||
_.each( self.element_list, function( input_element ) { input_element.reset() } );
|
||||
});
|
||||
return this;
|
||||
},
|
||||
|
||||
/** Renders/appends dom elements of the form */
|
||||
_renderForm: function() {
|
||||
$( '.tooltip' ).remove();
|
||||
var options = this.model.attributes;
|
||||
this.message = new Ui.UnescapedMessage();
|
||||
this.section = new FormSection.View( this, { inputs: options.inputs } );
|
||||
this.portlet = new Portlet.View({
|
||||
icon : options.icon,
|
||||
title : options.title,
|
||||
cls : options.cls,
|
||||
operations : options.operations,
|
||||
buttons : options.buttons,
|
||||
collapsible : options.collapsible,
|
||||
collapsed : options.collapsed,
|
||||
onchange_title : options.onchange_title
|
||||
});
|
||||
this.portlet.append( this.message.$el );
|
||||
this.portlet.append( this.section.$el );
|
||||
this.$el.empty();
|
||||
options.inputs && this.$el.append( this.portlet.$el );
|
||||
options.message && this.message.update( { persistent: true, status: options.message_status, message: options.message } );
|
||||
Galaxy.emit.debug( 'form-view::initialize()', 'Completed' );
|
||||
/** Set form into wait mode */
|
||||
wait: function(active) {
|
||||
for (var i in this.input_list) {
|
||||
var field = this.field_list[i];
|
||||
var input = this.input_list[i];
|
||||
if (input.is_dynamic && field.wait && field.unwait) {
|
||||
field[active ? "wait" : "unwait"]();
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/** Highlight and scroll to input element (currently only used for error notifications) */
|
||||
highlight: function(input_id, message, silent) {
|
||||
var input_element = this.element_list[input_id];
|
||||
if (input_element) {
|
||||
input_element.error(message || "Please verify this parameter.");
|
||||
this.portlet.expand();
|
||||
this.trigger("expand", input_id);
|
||||
if (!silent) {
|
||||
var $panel = this.$el
|
||||
.parents()
|
||||
.filter(function() {
|
||||
return ["auto", "scroll"].indexOf($(this).css("overflow")) != -1;
|
||||
})
|
||||
.first();
|
||||
$panel.animate(
|
||||
{
|
||||
scrollTop: $panel.scrollTop() + input_element.$el.offset().top - 120
|
||||
},
|
||||
500
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/** Highlights errors */
|
||||
errors: function(options) {
|
||||
this.trigger("reset");
|
||||
if (options && options.errors) {
|
||||
var error_messages = this.data.matchResponse(options.errors);
|
||||
for (var input_id in this.element_list) {
|
||||
if (error_messages[input_id]) {
|
||||
this.highlight(input_id, error_messages[input_id], true);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/** Render tool form */
|
||||
render: function() {
|
||||
var self = this;
|
||||
this.off("change");
|
||||
this.off("reset");
|
||||
// contains the dom field elements as created by the parameter factory i.e. form-parameters
|
||||
this.field_list = {};
|
||||
// contains input definitions/dictionaries as provided by the parameters to_dict() function through the api
|
||||
this.input_list = {};
|
||||
// contains the dom elements of each input element i.e. form-input which wraps the actual input field
|
||||
this.element_list = {};
|
||||
// converts the form into a json data structure
|
||||
this.data = new FormData.Manager(this);
|
||||
this._renderForm();
|
||||
this.data.create();
|
||||
if (this.model.get("initial_errors")) {
|
||||
this.errors(this.model.attributes);
|
||||
}
|
||||
// add listener which triggers on checksum change, and reset the form input wrappers
|
||||
var current_check = this.data.checksum();
|
||||
this.on("change", input_id => {
|
||||
var input = self.input_list[input_id];
|
||||
if (!input || input.refresh_on_change || self.model.get("always_refresh")) {
|
||||
var new_check = self.data.checksum();
|
||||
if (new_check != current_check) {
|
||||
current_check = new_check;
|
||||
self.model.get("onchange")();
|
||||
}
|
||||
}
|
||||
});
|
||||
this.on("reset", () => {
|
||||
_.each(self.element_list, input_element => {
|
||||
input_element.reset();
|
||||
});
|
||||
});
|
||||
return this;
|
||||
},
|
||||
|
||||
/** Renders/appends dom elements of the form */
|
||||
_renderForm: function() {
|
||||
$(".tooltip").remove();
|
||||
var options = this.model.attributes;
|
||||
this.message = new Ui.UnescapedMessage();
|
||||
this.section = new FormSection.View(this, {
|
||||
inputs: options.inputs
|
||||
});
|
||||
this.portlet = new Portlet.View({
|
||||
icon: options.icon,
|
||||
title: options.title,
|
||||
cls: options.cls,
|
||||
operations: !options.hide_operations && options.operations,
|
||||
buttons: options.buttons,
|
||||
collapsible: options.collapsible,
|
||||
collapsed: options.collapsed,
|
||||
onchange_title: options.onchange_title
|
||||
});
|
||||
this.portlet.append(this.message.$el);
|
||||
this.portlet.append(this.section.$el);
|
||||
this.$el.empty();
|
||||
if (options.inputs) {
|
||||
this.$el.append(this.portlet.$el);
|
||||
}
|
||||
if (options.message) {
|
||||
this.message.update({
|
||||
persistent: true,
|
||||
status: options.status,
|
||||
message: options.message
|
||||
});
|
||||
}
|
||||
Galaxy.emit.debug("form-view::initialize()", "Completed");
|
||||
}
|
||||
});
|
||||
|
||||
export default View;
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
/** Generic form view */
|
||||
import Form from "mvc/form/form-view";
|
||||
import Ui from "mvc/ui/ui-misc";
|
||||
var View = Backbone.View.extend({
|
||||
initialize: function(options) {
|
||||
this.model = new Backbone.Model(options);
|
||||
this.url = this.model.get("url");
|
||||
this.redirect = this.model.get("redirect");
|
||||
this.setElement("<div/>");
|
||||
this.render();
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var self = this;
|
||||
$.ajax({
|
||||
url: Galaxy.root + this.url,
|
||||
type: "GET"
|
||||
})
|
||||
.done(response => {
|
||||
var options = $.extend({}, self.model.attributes, response);
|
||||
var form = new Form({
|
||||
title: options.title,
|
||||
message: options.message,
|
||||
status: options.status || "warning",
|
||||
icon: options.icon,
|
||||
initial_errors: true,
|
||||
errors: options.errors,
|
||||
inputs: options.inputs,
|
||||
buttons: {
|
||||
submit: new Ui.Button({
|
||||
tooltip: options.submit_tooltip,
|
||||
title: options.submit_title || "Save",
|
||||
icon: options.submit_icon || "fa-save",
|
||||
cls: "btn btn-primary ui-clear-float",
|
||||
onclick: function() {
|
||||
self._submit(form);
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
self.$el.empty().append(form.$el);
|
||||
})
|
||||
.fail(response => {
|
||||
self.$el.empty().append(
|
||||
new Ui.Message({
|
||||
message: `Failed to load resource ${self.url}.`,
|
||||
status: "danger",
|
||||
persistent: true
|
||||
}).$el
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
_submit: function(form) {
|
||||
var self = this;
|
||||
$.ajax({
|
||||
url: Galaxy.root + self.url,
|
||||
data: JSON.stringify(form.data.create()),
|
||||
type: "PUT",
|
||||
contentType: "application/json"
|
||||
})
|
||||
.done(response => {
|
||||
var params = {};
|
||||
if (response.id) {
|
||||
params.id = response.id;
|
||||
} else {
|
||||
params = {
|
||||
message: response.message,
|
||||
status: "success",
|
||||
persistent: false
|
||||
};
|
||||
}
|
||||
if (self.redirect) {
|
||||
window.location = `${Galaxy.root + self.redirect}?${$.param(params)}`;
|
||||
} else {
|
||||
form.data.matchModel(response, (input, input_id) => {
|
||||
form.field_list[input_id].value(input.value);
|
||||
});
|
||||
self._showMessage(form, success_message);
|
||||
}
|
||||
})
|
||||
.fail(response => {
|
||||
self._showMessage(form, {
|
||||
message: response.responseJSON.err_msg,
|
||||
status: "danger",
|
||||
persistent: false
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
_showMessage: function(form, options) {
|
||||
var $panel = form.$el
|
||||
.parents()
|
||||
.filter(function() {
|
||||
return ["auto", "scroll"].indexOf($(this).css("overflow")) != -1;
|
||||
})
|
||||
.first();
|
||||
$panel.animate({ scrollTop: 0 }, 500);
|
||||
form.message.update(options);
|
||||
}
|
||||
});
|
||||
|
||||
export default {
|
||||
View: View
|
||||
};
|
||||
@@ -1,10 +1,9 @@
|
||||
// dependencies
|
||||
define([], function() {
|
||||
|
||||
// grid model
|
||||
return Backbone.Model.extend({
|
||||
export default Backbone.Model.extend({
|
||||
defaults: {
|
||||
url_base: '',
|
||||
url_base: "",
|
||||
async: false,
|
||||
async_ops: [],
|
||||
categorical_filters: [],
|
||||
@@ -29,33 +28,33 @@ return Backbone.Model.extend({
|
||||
* Add filtering criterion.
|
||||
*/
|
||||
add_filter: function(key, value, append) {
|
||||
// Update URL arg with new condition.
|
||||
// Update URL arg with new condition.
|
||||
if (append) {
|
||||
// Update or append value.
|
||||
var cur_val = this.attributes.filters[key],
|
||||
new_val;
|
||||
var cur_val = this.attributes.filters[key];
|
||||
|
||||
var new_val;
|
||||
if (cur_val === null || cur_val === undefined) {
|
||||
new_val = value;
|
||||
}
|
||||
else if (typeof(cur_val) == 'string') {
|
||||
if (cur_val == 'All') {
|
||||
} else if (typeof cur_val == "string") {
|
||||
if (cur_val == "All" || cur_val == value) {
|
||||
new_val = value;
|
||||
} else {
|
||||
// Replace string with array.
|
||||
var values = [];
|
||||
values[0] = cur_val;
|
||||
values[1] = value;
|
||||
new_val = values;
|
||||
new_val = values;
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// Current value is an array.
|
||||
new_val = cur_val;
|
||||
new_val.push(value);
|
||||
if (new_val.indexOf(value) === -1) {
|
||||
new_val.push(value);
|
||||
}
|
||||
}
|
||||
this.attributes.filters[key] = new_val;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// Replace value.
|
||||
this.attributes.filters[key] = value;
|
||||
}
|
||||
@@ -67,17 +66,17 @@ return Backbone.Model.extend({
|
||||
remove_filter: function(key, condition) {
|
||||
var cur_val = this.attributes.filters[key];
|
||||
if (cur_val === null || cur_val === undefined) {
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof(cur_val) === 'string') {
|
||||
if (typeof cur_val === "string") {
|
||||
// overwrite/remove condition.
|
||||
this.attributes.filters[key] = '';
|
||||
this.attributes.filters[key] = "";
|
||||
} else {
|
||||
// filter contains an array of conditions.
|
||||
var condition_index = _.indexOf(cur_val, condition);
|
||||
if (condition_index !== -1) {
|
||||
cur_val[condition_index] = '';
|
||||
cur_val[condition_index] = "";
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -104,18 +103,14 @@ return Backbone.Model.extend({
|
||||
|
||||
// Add filter arguments to data, placing "f-" in front of all arguments.
|
||||
var self = this;
|
||||
_.each(_.pairs(self.attributes.filters), function(k) {
|
||||
url_data['f-' + k[0]] = k[1];
|
||||
_.each(_.pairs(self.attributes.filters), k => {
|
||||
url_data[`f-${k[0]}`] = k[1];
|
||||
});
|
||||
|
||||
return url_data;
|
||||
},
|
||||
|
||||
// Return URL for obtaining a new grid
|
||||
get_url: function (args) {
|
||||
return this.get('url_base') + "?" + $.param(this.get_url_data()) + '&' + $.param(args);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// Return URL for obtaining a new grid
|
||||
get_url: function(args) {
|
||||
return `${this.get("url_base")}?${$.param(this.get_url_data())}&${$.param(args)}`;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/** This class renders the grid list with shared section. */
|
||||
import GridView from "mvc/grid/grid-view";
|
||||
var View = Backbone.View.extend({
|
||||
initialize: function(options) {
|
||||
var self = this;
|
||||
this.setElement($("<div/>"));
|
||||
this.model = new Backbone.Model(options);
|
||||
this.item = this.model.get("item");
|
||||
this.title = this.model.get("plural");
|
||||
$.ajax({
|
||||
url: `${Galaxy.root + this.item}/${this.model.get("action_id")}?${$.param(Galaxy.params)}`,
|
||||
success: function(response) {
|
||||
response["dict_format"] = true;
|
||||
self.model.set(response);
|
||||
self.render();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var grid = new GridView(this.model.attributes);
|
||||
this.$el.empty().append(grid.$el);
|
||||
this.$el.append(this._templateShared());
|
||||
},
|
||||
|
||||
_templateShared: function() {
|
||||
var self = this;
|
||||
var $tmpl = $(`<div><h2>${this.model.get("plural")} shared with you by others</h2></div>`);
|
||||
var options = this.model.attributes;
|
||||
if (options.shared_by_others && options.shared_by_others.length > 0) {
|
||||
var $table = $(
|
||||
'<table class="colored" border="0" cellspacing="0" cellpadding="0" width="100%">' +
|
||||
'<tr class="header">' +
|
||||
"<th>Title</th>" +
|
||||
"<th>Owner</th>" +
|
||||
"</tr>" +
|
||||
"</table>"
|
||||
);
|
||||
_.each(options.shared_by_others, (it, index) => {
|
||||
var display_url = `${Galaxy.root + self.item}/display_by_username_and_slug?username=${
|
||||
it.username
|
||||
}&slug=${it.slug}`;
|
||||
$table.append(
|
||||
`<tr><td><a href="${display_url}">${_.escape(it.title)}</a></td><td>${_.escape(
|
||||
it.username
|
||||
)}</td></tr>`
|
||||
);
|
||||
});
|
||||
$tmpl.append($table);
|
||||
} else {
|
||||
$tmpl.append(`No ${this.model.get("plural").toLowerCase()} have been shared with you.`);
|
||||
}
|
||||
return $tmpl;
|
||||
}
|
||||
});
|
||||
|
||||
export default {
|
||||
View: View
|
||||
};
|
||||
@@ -1,216 +1,200 @@
|
||||
// dependencies
|
||||
define(['utils/utils'], function(Utils) {
|
||||
|
||||
import Utils from "utils/utils";
|
||||
// grid view templates
|
||||
return {
|
||||
export default {
|
||||
// template
|
||||
grid: function(options) {
|
||||
var tmpl = '';
|
||||
var tmpl = "";
|
||||
if (options.embedded) {
|
||||
tmpl = this.grid_header(options) + this.grid_table(options);
|
||||
} else {
|
||||
tmpl = '<div class="loading-elt-overlay"></div>' +
|
||||
'<table>' +
|
||||
'<tr>' +
|
||||
'<td width="75%">' +
|
||||
this.grid_header(options) +
|
||||
'</td>' +
|
||||
'<td></td>' +
|
||||
'<td></td>' +
|
||||
'</tr>' +
|
||||
'<tr>' +
|
||||
'<td width="100%" id="grid-message" valign="top"></td>' +
|
||||
'<td></td>' +
|
||||
'<td></td>' +
|
||||
'</tr>' +
|
||||
'</table>' +
|
||||
this.grid_table(options);
|
||||
tmpl = `<div class="loading-elt-overlay"></div><table><tr><td width="75%">${this.grid_header(
|
||||
options
|
||||
)}</td><td></td><td></td></tr><tr><td width="100%" id="grid-message" valign="top"></td><td></td><td></td></tr></table>${this.grid_table(
|
||||
options
|
||||
)}`;
|
||||
}
|
||||
|
||||
|
||||
// add info text
|
||||
if (options.info_text) {
|
||||
tmpl += '<br><div class="toolParamHelp" style="clear: both;">' + options.info_text + '</div>';
|
||||
tmpl += `<br><div class="toolParamHelp" style="clear: both;">${options.info_text}</div>`;
|
||||
}
|
||||
|
||||
|
||||
// return
|
||||
return tmpl;
|
||||
},
|
||||
|
||||
|
||||
// template
|
||||
grid_table: function(options) {
|
||||
return '<form method="post" onsubmit="return false;">' +
|
||||
'<table id="grid-table" class="grid">' +
|
||||
'<thead id="grid-table-header"></thead>' +
|
||||
'<tbody id="grid-table-body"></tbody>' +
|
||||
'<tfoot id="grid-table-footer"></tfoot>' +
|
||||
'</table>' +
|
||||
'</form>';
|
||||
grid_table: function() {
|
||||
return `
|
||||
<form method="post" onsubmit="return false;">
|
||||
<table id="grid-table" class="grid">
|
||||
<thead id="grid-table-header"></thead>
|
||||
<tbody id="grid-table-body"></tbody>
|
||||
<tfoot id="grid-table-footer"></tfoot>
|
||||
</table>
|
||||
</form>`;
|
||||
},
|
||||
|
||||
|
||||
// template
|
||||
grid_header: function(options) {
|
||||
var tmpl = '<div class="grid-header">';
|
||||
var tmpl = '<div class="grid-header">';
|
||||
if (!options.embedded) {
|
||||
tmpl += '<h2>' + options.title + '</h2>';
|
||||
tmpl += `<h2>${options.title}</h2>`;
|
||||
}
|
||||
if (options.global_actions) {
|
||||
tmpl += '<ul class="manage-table-actions">';
|
||||
var show_popup = (options.global_actions.length >= 3);
|
||||
tmpl += '<ul class="manage-table-actions">';
|
||||
var show_popup = options.global_actions.length >= 3;
|
||||
if (show_popup) {
|
||||
tmpl += '<li><a class="action-button" id="popup-global-actions" class="menubutton">Actions</a></li>' +
|
||||
'<div popupmenu="popup-global-actions">';
|
||||
tmpl +=
|
||||
'<li><a class="action-button" id="popup-global-actions" class="menubutton">Actions</a></li>' +
|
||||
'<div popupmenu="popup-global-actions">';
|
||||
}
|
||||
for (i in options.global_actions) {
|
||||
var action = options.global_actions[i];
|
||||
tmpl += '<li>' +
|
||||
'<a class="action-button use-target" target="' + action.target + '" href="' + action.url_args + '" onclick="return false;" >' + action.label + '</a>' +
|
||||
'</li>';
|
||||
for (let action of options.global_actions) {
|
||||
tmpl += `<li><a class="action-button use-target" target="${action.target}" href="${
|
||||
action.url_args
|
||||
}" onclick="return false;" >${action.label}</a></li>`;
|
||||
}
|
||||
if (show_popup) {
|
||||
tmpl += '</div>';
|
||||
tmpl += "</div>";
|
||||
}
|
||||
tmpl += '</ul>';
|
||||
tmpl += "</ul>";
|
||||
}
|
||||
if (options.insert) {
|
||||
tmpl += options.insert;
|
||||
tmpl += options.insert;
|
||||
}
|
||||
|
||||
|
||||
// add grid filters
|
||||
tmpl += this.grid_filters(options);
|
||||
tmpl += '</div>'
|
||||
|
||||
tmpl += this.grid_filters(options);
|
||||
tmpl += "</div>";
|
||||
|
||||
// return template
|
||||
return tmpl;
|
||||
},
|
||||
|
||||
// template
|
||||
header: function(options) {
|
||||
|
||||
// start
|
||||
var tmpl = '<tr>';
|
||||
|
||||
var tmpl = "<tr>";
|
||||
|
||||
// add checkbox
|
||||
if (options.show_item_checkboxes) {
|
||||
tmpl += '<th>';
|
||||
tmpl += "<th>";
|
||||
if (options.items.length > 0) {
|
||||
tmpl += '<input type="checkbox" id="check_all" name=select_all_checkbox value="true">' +
|
||||
'<input type="hidden" name=select_all_checkbox value="true">';
|
||||
tmpl +=
|
||||
'<input type="checkbox" id="check_all" name=select_all_checkbox value="true">' +
|
||||
'<input type="hidden" name=select_all_checkbox value="true">';
|
||||
}
|
||||
tmpl += '</th>';
|
||||
tmpl += "</th>";
|
||||
}
|
||||
|
||||
|
||||
// create header elements
|
||||
for (var i in options.columns) {
|
||||
var column = options.columns[i];
|
||||
for (let column of options.columns) {
|
||||
if (column.visible) {
|
||||
tmpl += '<th id="' + column.key + '-header">';
|
||||
tmpl += `<th id="${column.key}-header">`;
|
||||
if (column.href) {
|
||||
tmpl += '<a href="' + column.href + '" class="sort-link" sort_key="' + column.key + '">' + column.label + '</a>';
|
||||
tmpl += `<a href="${column.href}" class="sort-link" sort_key="${column.key}">${column.label}</a>`;
|
||||
} else {
|
||||
tmpl += column.label;
|
||||
tmpl += column.label;
|
||||
}
|
||||
tmpl += '<span class="sort-arrow">' + column.extra + '</span>' +
|
||||
'</th>';
|
||||
tmpl += `<span class="sort-arrow">${column.extra}</span></th>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// finalize
|
||||
tmpl += '</tr>';
|
||||
|
||||
tmpl += "</tr>";
|
||||
|
||||
// return template
|
||||
return tmpl;
|
||||
},
|
||||
|
||||
|
||||
// template
|
||||
body: function(options) {
|
||||
// initialize
|
||||
var tmpl = '';
|
||||
var tmpl = "";
|
||||
var num_rows_rendered = 0;
|
||||
var items_length = options.items.length;
|
||||
|
||||
|
||||
// empty grid?
|
||||
if (items_length == 0) {
|
||||
if (items_length === 0) {
|
||||
// No results.
|
||||
tmpl += '<tr><td colspan="100"><em>No Items</em></td></tr>';
|
||||
num_rows_rendered = 1;
|
||||
}
|
||||
|
||||
|
||||
// create rows
|
||||
for (var i in options.items) {
|
||||
|
||||
// encode ids
|
||||
var item = options.items[i];
|
||||
var encoded_id = item.encode_id;
|
||||
var popupmenu_id = 'grid-' + i + '-popup';
|
||||
|
||||
for (let item of options.items) {
|
||||
// Tag current
|
||||
tmpl += '<tr ';
|
||||
tmpl += "<tr ";
|
||||
if (options.current_item_id == item.id) {
|
||||
tmpl += 'class="current"';
|
||||
}
|
||||
tmpl += '>';
|
||||
|
||||
tmpl += ">";
|
||||
|
||||
// Item selection column
|
||||
if (options.show_item_checkboxes) {
|
||||
tmpl += '<td style="width: 1.5em;">' +
|
||||
'<input type="checkbox" name="id" value="' + encoded_id + '" id="' + encoded_id + '" class="grid-row-select-checkbox" />' +
|
||||
'</td>';
|
||||
tmpl += `<td style="width: 1.5em;"><input type="checkbox" name="id" value="${item.encode_id}" id="${
|
||||
item.encode_id
|
||||
}" class="grid-row-select-checkbox" /></td>`;
|
||||
}
|
||||
|
||||
|
||||
// Data columns
|
||||
for (j in options.columns) {
|
||||
var column = options.columns[j];
|
||||
for (let column of options.columns) {
|
||||
if (column.visible) {
|
||||
// Nowrap
|
||||
var nowrap = '';
|
||||
var nowrap = "";
|
||||
if (column.nowrap) {
|
||||
nowrap = 'style="white-space:nowrap;"';
|
||||
}
|
||||
|
||||
|
||||
// get column settings
|
||||
var column_settings = item.column_config[column.label];
|
||||
|
||||
|
||||
// load attributes
|
||||
var link = column_settings.link;
|
||||
var value = column_settings.value;
|
||||
var target = column_settings.target;
|
||||
|
||||
|
||||
// unescape value
|
||||
if (jQuery.type( value ) === 'string') {
|
||||
value = value.replace(/\/\//g, '/');
|
||||
if (jQuery.type(value) === "string") {
|
||||
value = value.replace(/\/\//g, "/");
|
||||
}
|
||||
|
||||
|
||||
// Attach popup menu?
|
||||
var id = '';
|
||||
var cls = '';
|
||||
var id = "";
|
||||
var cls = "";
|
||||
if (column.attach_popup) {
|
||||
id = 'grid-' + i + '-popup';
|
||||
cls = 'menubutton';
|
||||
if (link != '') {
|
||||
cls += ' split';
|
||||
id = `grid-${item.encode_id}-popup`;
|
||||
cls = "menubutton";
|
||||
if (link !== "") {
|
||||
cls += " split";
|
||||
}
|
||||
cls += ' popup';
|
||||
cls += " popup";
|
||||
}
|
||||
|
||||
|
||||
// Check for row wrapping
|
||||
tmpl += '<td ' + nowrap + '>';
|
||||
|
||||
tmpl += `<td ${nowrap}>`;
|
||||
|
||||
// Link
|
||||
if (link) {
|
||||
if (options.operations.length != 0) {
|
||||
tmpl += '<div id="' + id + '" class="' + cls + '" style="float: left;">';
|
||||
if (options.operations.length !== 0) {
|
||||
tmpl += `<div id="${id}" class="${cls}" style="float: left;">`;
|
||||
}
|
||||
tmpl += '<a class="menubutton-label use-target" target="' + target + '" href="' + link + '" onclick="return false;">' + value + '</a>';
|
||||
if (options.operations.length != 0) {
|
||||
tmpl += '</div>';
|
||||
tmpl += `<a class="menubutton-label use-target" target="${target}" href="${
|
||||
link
|
||||
}" onclick="return false;">${value}</a>`;
|
||||
if (options.operations.length !== 0) {
|
||||
tmpl += "</div>";
|
||||
}
|
||||
} else {
|
||||
tmpl += '<div id="' + id + '" class="' + cls + '"><label id="' + column.label_id_prefix + encoded_id + '" for="' + encoded_id + '">' + (value || '') + '</label></div>';
|
||||
tmpl += `<div id="${id}" class="${cls}"><label id="${column.label_id_prefix}${
|
||||
item.encode_id
|
||||
}" for="${item.encode_id}">${value || ""}</label></div>`;
|
||||
}
|
||||
tmpl += '</td>';
|
||||
tmpl += "</td>";
|
||||
}
|
||||
}
|
||||
tmpl += '</tr>';
|
||||
tmpl += "</tr>";
|
||||
num_rows_rendered++;
|
||||
}
|
||||
return tmpl;
|
||||
@@ -218,30 +202,30 @@ return {
|
||||
|
||||
// template
|
||||
footer: function(options) {
|
||||
|
||||
// create template string
|
||||
var tmpl = '';
|
||||
|
||||
var tmpl = "";
|
||||
|
||||
// paging
|
||||
if (options.use_paging && options.num_pages > 1) {
|
||||
// get configuration
|
||||
var num_page_links = options.num_page_links;
|
||||
var cur_page_num = options.cur_page_num;
|
||||
var num_pages = options.num_pages;
|
||||
|
||||
var num_page_links = options.num_page_links;
|
||||
var cur_page_num = options.cur_page_num;
|
||||
var num_pages = options.num_pages;
|
||||
|
||||
// First pass on min page.
|
||||
var page_link_range = num_page_links / 2;
|
||||
var min_page = cur_page_num - page_link_range
|
||||
var min_offset = 0;
|
||||
var page_link_range = num_page_links / 2;
|
||||
var min_page = cur_page_num - page_link_range;
|
||||
var min_offset = 0;
|
||||
if (min_page <= 0) {
|
||||
// Min page is too low.
|
||||
min_page = 1;
|
||||
min_offset = page_link_range - ( cur_page_num - min_page );
|
||||
min_offset = page_link_range - (cur_page_num - min_page);
|
||||
}
|
||||
|
||||
|
||||
// Set max page.
|
||||
var max_range = page_link_range + min_offset;
|
||||
var max_page = cur_page_num + max_range;
|
||||
var max_offset;
|
||||
if (max_page <= num_pages) {
|
||||
// Max page is fine.
|
||||
max_offset = 0;
|
||||
@@ -249,357 +233,342 @@ return {
|
||||
// Max page is too high.
|
||||
max_page = num_pages;
|
||||
// +1 to account for the +1 in the loop below.
|
||||
max_offset = max_range - ( max_page + 1 - cur_page_num );
|
||||
max_offset = max_range - (max_page + 1 - cur_page_num);
|
||||
}
|
||||
|
||||
|
||||
// Second and final pass on min page to add any unused
|
||||
// offset from max to min.
|
||||
if (max_offset != 0) {
|
||||
min_page -= max_offset
|
||||
if (max_offset !== 0) {
|
||||
min_page -= max_offset;
|
||||
if (min_page < 1) {
|
||||
min_page = 1
|
||||
min_page = 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// template header
|
||||
tmpl += '<tr id="page-links-row">';
|
||||
if (options.show_item_checkboxes) {
|
||||
tmpl += '<td></td>';
|
||||
tmpl += "<td></td>";
|
||||
}
|
||||
tmpl += '<td colspan="100">' +
|
||||
'<span id="page-link-container">' +
|
||||
'Page:';
|
||||
|
||||
tmpl += '<td colspan="100">' + '<span id="page-link-container">' + "Page:";
|
||||
|
||||
if (min_page > 1) {
|
||||
tmpl += '<span class="page-link" id="page-link-1"><a href="javascript:void(0);" page_num="1" onclick="return false;">1</a></span> ...';
|
||||
tmpl +=
|
||||
'<span class="page-link" id="page-link-1"><a href="javascript:void(0);" page_num="1" onclick="return false;">1</a></span> ...';
|
||||
}
|
||||
|
||||
|
||||
// create page urls
|
||||
for (var page_index = min_page; page_index < max_page + 1; page_index++) {
|
||||
|
||||
if (page_index == options.cur_page_num) {
|
||||
tmpl += '<span class="page-link inactive-link" id="page-link-' + page_index + '">' + page_index + '</span>';
|
||||
tmpl += `<span class="page-link inactive-link" id="page-link-${page_index}">${page_index}</span>`;
|
||||
} else {
|
||||
tmpl += '<span class="page-link" id="page-link-' + page_index + '"><a href="javascript:void(0);" onclick="return false;" page_num="' + page_index + '">' + page_index + '</a></span>';
|
||||
tmpl += `<span class="page-link" id="page-link-${
|
||||
page_index
|
||||
}"><a href="javascript:void(0);" onclick="return false;" page_num="${page_index}">${
|
||||
page_index
|
||||
}</a></span>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// show last page
|
||||
if (max_page < num_pages) {
|
||||
tmpl += '...' +
|
||||
'<span class="page-link" id="page-link-' + num_pages + '"><a href="javascript:void(0);" onclick="return false;" page_num="' + num_pages + '">' + num_pages + '</a></span>';
|
||||
tmpl += `...<span class="page-link" id="page-link-${
|
||||
num_pages
|
||||
}"><a href="javascript:void(0);" onclick="return false;" page_num="${num_pages}">${
|
||||
num_pages
|
||||
}</a></span>`;
|
||||
}
|
||||
tmpl += '</span>';
|
||||
|
||||
tmpl += "</span>";
|
||||
|
||||
// Show all link
|
||||
tmpl += '<span class="page-link" id="show-all-link-span"> | <a href="javascript:void(0);" onclick="return false;" page_num="all">Show All</a></span>' +
|
||||
'</td>' +
|
||||
'</tr>';
|
||||
tmpl += `
|
||||
<span class="page-link" id="show-all-link-span"> | <a href="javascript:void(0);" onclick="return false;" page_num="all">Show All</a></span>
|
||||
</td>
|
||||
</tr>`;
|
||||
}
|
||||
|
||||
|
||||
// Grid operations for multiple items.
|
||||
if (options.show_item_checkboxes) {
|
||||
// start template
|
||||
tmpl += '<tr>' +
|
||||
'<input type="hidden" id="operation" name="operation" value="">' +
|
||||
'<td></td>' +
|
||||
'<td colspan="100">' +
|
||||
'For <span class="grid-selected-count"></span> selected items: ';
|
||||
|
||||
tmpl += `
|
||||
<tr>
|
||||
<input type="hidden" id="operation" name="operation" value="">
|
||||
<td></td>
|
||||
<td colspan="100">
|
||||
For <span class="grid-selected-count"></span> selected items:
|
||||
`;
|
||||
|
||||
// configure buttons for operations
|
||||
for (i in options.operations) {
|
||||
var operation = options.operations[i];
|
||||
for (let operation of options.operations) {
|
||||
if (operation.allow_multiple) {
|
||||
tmpl += '<input type="button" value="' + operation.label + '" class="operation-button action-button"> ';
|
||||
tmpl += `<input type="button" value="${
|
||||
operation.label
|
||||
}" class="operation-button action-button"> `;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// finalize template
|
||||
tmpl += '</td>' +
|
||||
'</tr>';
|
||||
tmpl += "</td>" + "</tr>";
|
||||
}
|
||||
|
||||
|
||||
// count global operations
|
||||
var found_global = false;
|
||||
for (i in options.operations) {
|
||||
if (options.operations[i].global_operation) {
|
||||
for (let operation of options.operations) {
|
||||
if (operation.global_operation) {
|
||||
found_global = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// add global operations
|
||||
if (found_global) {
|
||||
tmpl += '<tr>' +
|
||||
'<td colspan="100">';
|
||||
for (i in options.operations) {
|
||||
var operation = options.operations[i];
|
||||
tmpl += "<tr>" + '<td colspan="100">';
|
||||
for (let operation of options.operations) {
|
||||
if (operation.global_operation) {
|
||||
tmpl += '<a class="action-button" href="' + operation.global_operation + '">' + operation.label + '</a>';
|
||||
tmpl += `<a class="action-button" href="${operation.global_operation}">${operation.label}</a>`;
|
||||
}
|
||||
}
|
||||
tmpl += '</td>' +
|
||||
'</tr>';
|
||||
tmpl += "</td>" + "</tr>";
|
||||
}
|
||||
|
||||
|
||||
// add legend
|
||||
if (options.legend) {
|
||||
tmpl += '<tr>' +
|
||||
'<td colspan="100">' + options.legend + '</td>' +
|
||||
'</tr>';
|
||||
tmpl += `<tr><td colspan="100">${options.legend}</td></tr>`;
|
||||
}
|
||||
|
||||
|
||||
// return
|
||||
return tmpl;
|
||||
},
|
||||
|
||||
|
||||
// template
|
||||
message: function(options) {
|
||||
return '<p>' +
|
||||
'<div class="' + options.status + 'message transient-message">' + options.message + '</div>' +
|
||||
'<div style="clear: both"></div>' +
|
||||
'</p>';
|
||||
var status = options.status;
|
||||
if (["success", "ok"].indexOf(status) != -1) {
|
||||
status = "done";
|
||||
}
|
||||
return `<p><div class="${status}message transient-message">${_.escape(
|
||||
options.message
|
||||
)}</div><div style="clear: both"></div></p>`;
|
||||
},
|
||||
|
||||
|
||||
// template
|
||||
grid_filters: function (options) {
|
||||
|
||||
grid_filters: function(options) {
|
||||
// get filters
|
||||
var default_filter_dict = options.default_filter_dict;
|
||||
var filters = options.filters;
|
||||
|
||||
// show advanced search if flag set or if there are filters for advanced search fields
|
||||
var advanced_search_display = 'none';
|
||||
var advanced_search_display = "none";
|
||||
if (options.advanced_search) {
|
||||
advanced_search_display = 'block';
|
||||
advanced_search_display = "block";
|
||||
}
|
||||
|
||||
|
||||
// identify columns with advanced filtering
|
||||
var show_advanced_search_link = false;
|
||||
for (var i in options.columns) {
|
||||
var column = options.columns[i];
|
||||
if (column.filterable == 'advanced') {
|
||||
for (let column of options.columns) {
|
||||
if (column.filterable == "advanced") {
|
||||
var column_key = column.key;
|
||||
var f_key = filters[column_key];
|
||||
var d_key = default_filter_dict[column_key];
|
||||
if (f_key && d_key && f_key != d_key) {
|
||||
advanced_search_display = 'block';
|
||||
advanced_search_display = "block";
|
||||
}
|
||||
show_advanced_search_link = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// hide standard search if advanced is shown
|
||||
var standard_search_display = 'block';
|
||||
if (advanced_search_display == 'block') {
|
||||
standard_search_display = 'none';
|
||||
var standard_search_display = "block";
|
||||
if (advanced_search_display == "block") {
|
||||
standard_search_display = "none";
|
||||
}
|
||||
|
||||
//
|
||||
// standard search
|
||||
//
|
||||
var tmpl = '<div id="standard-search" style="display: ' + standard_search_display + ';">' +
|
||||
'<table>' +
|
||||
'<tr>' +
|
||||
'<td style="padding: 0;">' +
|
||||
'<table>';
|
||||
|
||||
var tmpl = `<div id="standard-search" style="display: ${
|
||||
standard_search_display
|
||||
};"><table><tr><td style="padding: 0;"><table>`;
|
||||
|
||||
// add standard filters
|
||||
for (var i in options.columns) {
|
||||
var column = options.columns[i];
|
||||
if (column.filterable == 'standard') {
|
||||
tmpl += this.grid_column_filter(options, column);
|
||||
for (let column of options.columns) {
|
||||
if (column.filterable == "standard") {
|
||||
tmpl += this.grid_column_filter(options, column);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// finalize standard search
|
||||
tmpl += '</table>' +
|
||||
'</td>' +
|
||||
'</tr>' +
|
||||
'<tr>' +
|
||||
'<td>';
|
||||
|
||||
tmpl += "</table>" + "</td>" + "</tr>" + "<tr>" + "<td>";
|
||||
|
||||
// show advanced search link in standard display
|
||||
if (show_advanced_search_link) {
|
||||
tmpl += '<a href="" class="advanced-search-toggle">Advanced Search</a>';
|
||||
tmpl += '<a href="" class="advanced-search-toggle">Advanced Search</a>';
|
||||
}
|
||||
|
||||
|
||||
// finalize standard search display
|
||||
tmpl += '</td>' +
|
||||
'</tr>' +
|
||||
'</table>' +
|
||||
'</div>';
|
||||
|
||||
tmpl += "</td>" + "</tr>" + "</table>" + "</div>";
|
||||
|
||||
//
|
||||
// advanced search
|
||||
//
|
||||
tmpl += '<div id="advanced-search" style="display: ' + advanced_search_display + '; margin-top: 5px; border: 1px solid #ccc;">' +
|
||||
'<table>' +
|
||||
'<tr>' +
|
||||
'<td style="text-align: left" colspan="100">' +
|
||||
'<a href="" class="advanced-search-toggle">Close Advanced Search</a>' +
|
||||
'</td>' +
|
||||
'</tr>';
|
||||
|
||||
tmpl += `<div id="advanced-search" style="display: ${
|
||||
advanced_search_display
|
||||
}; margin-top: 5px; border: 1px solid #ccc;"><table><tr><td style="text-align: left" colspan="100"><a href="" class="advanced-search-toggle">Close Advanced Search</a></td></tr>`;
|
||||
|
||||
// add advanced filters
|
||||
for (var i in options.columns) {
|
||||
var column = options.columns[i];
|
||||
if (column.filterable == 'advanced') {
|
||||
tmpl += this.grid_column_filter(options, column);
|
||||
for (let column of options.columns) {
|
||||
if (column.filterable == "advanced") {
|
||||
tmpl += this.grid_column_filter(options, column);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// finalize advanced search template
|
||||
tmpl += '</table>' +
|
||||
'</div>';
|
||||
tmpl += "</table>" + "</div>";
|
||||
|
||||
// return template
|
||||
return tmpl;
|
||||
},
|
||||
|
||||
|
||||
// template
|
||||
grid_column_filter: function(options, column) {
|
||||
|
||||
// collect parameters
|
||||
var default_filter_dict = options.default_filter_dict;
|
||||
var filters = options.filters;
|
||||
var column_label = column.label;
|
||||
var column_key = column.key;
|
||||
if (column.filterable == 'advanced') {
|
||||
if (column.filterable == "advanced") {
|
||||
column_label = column_label.toLowerCase();
|
||||
}
|
||||
|
||||
|
||||
// start
|
||||
var tmpl = '<tr>';
|
||||
|
||||
if (column.filterable == 'advanced') {
|
||||
tmpl += '<td align="left" style="padding-left: 10px">' + column_label + ':</td>';
|
||||
var tmpl = "<tr>";
|
||||
|
||||
if (column.filterable == "advanced") {
|
||||
tmpl += `<td align="left" style="padding-left: 10px">${column_label}:</td>`;
|
||||
}
|
||||
tmpl += '<td style="padding-bottom: 1px;">';
|
||||
tmpl += '<td style="padding-bottom: 1px;">';
|
||||
if (column.is_text) {
|
||||
tmpl += '<form class="text-filter-form" column_key="' + column_key + '" action="' + options.url + '" method="get" >';
|
||||
tmpl += `<form class="text-filter-form" column_key="${column_key}" action="${options.url}" method="get" >`;
|
||||
// Carry forward filtering criteria with hidden inputs.
|
||||
for (i in options.columns) {
|
||||
var temp_column = options.columns[i];
|
||||
var filter_value = filters[temp_column.key];
|
||||
for (let column of options.columns) {
|
||||
var filter_value = filters[column.key];
|
||||
if (filter_value) {
|
||||
if (filter_value != 'All') {
|
||||
if (temp_column.is_text) {
|
||||
filter_value = JSON.stringify( filter_value )
|
||||
if (filter_value != "All") {
|
||||
if (column.is_text) {
|
||||
filter_value = JSON.stringify(filter_value);
|
||||
}
|
||||
tmpl += '<input type="hidden" id="' + temp_column.key + '" name="f-' + temp_column.key + '" value="' + filter_value + '"/>';
|
||||
tmpl += `<input type="hidden" id="${column.key}" name="f-${column.key}" value="${
|
||||
filter_value
|
||||
}"/>`;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Print current filtering criteria and links to delete.
|
||||
tmpl += '<span id="' + column_key + '-filtering-criteria">';
|
||||
|
||||
tmpl += `<span id="${column_key}-filtering-criteria">`;
|
||||
|
||||
// add filters
|
||||
var column_filter = filters[column_key];
|
||||
if (column_filter) {
|
||||
// identify type
|
||||
var type = jQuery.type(column_filter);
|
||||
|
||||
|
||||
// single filter value
|
||||
if (type == 'string') {
|
||||
if (column_filter != 'All') {
|
||||
if (type == "string") {
|
||||
if (column_filter != "All") {
|
||||
// append template
|
||||
tmpl += this.filter_element(column_key, column_filter);
|
||||
tmpl += this.filter_element(column_key, column_filter);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// multiple filter values
|
||||
if (type == 'array') {
|
||||
for (var i in column_filter ) {
|
||||
// get filter
|
||||
var filter = column_filter[i];
|
||||
|
||||
if (type == "array") {
|
||||
for (let i in column_filter) {
|
||||
// copy filters and remove entry
|
||||
var params = column_filter;
|
||||
params = params.slice(i);
|
||||
|
||||
// append template
|
||||
tmpl += this.filter_element(column_key, filter);
|
||||
tmpl += this.filter_element(column_key, column_filter[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// close span
|
||||
tmpl += '</span>';
|
||||
|
||||
tmpl += "</span>";
|
||||
|
||||
// Set value, size of search input field. Minimum size is 20 characters.
|
||||
var value = '';
|
||||
if (column.filterable == 'standard') {
|
||||
var value = "";
|
||||
var size = 20;
|
||||
if (column.filterable == "standard") {
|
||||
value = column.label.toLowerCase();
|
||||
var size = value.length;
|
||||
if (size < 20) {
|
||||
size = 20;
|
||||
if (value.length < 20) {
|
||||
size = value.length;
|
||||
}
|
||||
// +4 to account for space after placeholder
|
||||
size = size + 4;
|
||||
}
|
||||
|
||||
|
||||
// print input field for column
|
||||
tmpl += '<span class="search-box">' +
|
||||
'<input class="search-box-input" id="input-' + column_key + '-filter" name="f-' + column_key + '" type="text" placeholder="' + value + '" size="' + size + '"/>' +
|
||||
'<button type="submit" style="background: transparent; border: none; padding: 4px; margin: 0px;">' +
|
||||
'<i class="fa fa-search"></i>' +
|
||||
'</button>' +
|
||||
'</span>' +
|
||||
'</form>';
|
||||
tmpl += `
|
||||
<span class="search-box">
|
||||
<input class="search-box-input" id="input-${column_key}-filter" name="f-${
|
||||
column_key
|
||||
}" type="text" placeholder="${value}" size="${size}"/>
|
||||
<button type="submit" style="background: transparent; border: none; padding: 4px; margin: 0px;">
|
||||
<i class="fa fa-search"></i>
|
||||
</button>
|
||||
</span>
|
||||
</form>`;
|
||||
} else {
|
||||
// filter criteria
|
||||
tmpl += '<span id="' + column_key + '-filtering-criteria">';
|
||||
|
||||
tmpl += `<span id="${column_key}-filtering-criteria">`;
|
||||
|
||||
// add category filters
|
||||
var seperator = false;
|
||||
for (cf_label in options.categorical_filters[column_key]) {
|
||||
for (var cf_label in options.categorical_filters[column_key]) {
|
||||
// get category filter
|
||||
var cf = options.categorical_filters[column_key][cf_label];
|
||||
|
||||
|
||||
// each filter will have only a single argument, so get that single argument
|
||||
var cf_key = '';
|
||||
var cf_arg = '';
|
||||
for (key in cf) {
|
||||
var cf_key = "";
|
||||
var cf_arg = "";
|
||||
for (var key in cf) {
|
||||
cf_key = key;
|
||||
cf_arg = cf[key];
|
||||
}
|
||||
|
||||
|
||||
// add seperator
|
||||
if (seperator) {
|
||||
tmpl += ' | ';
|
||||
tmpl += " | ";
|
||||
}
|
||||
seperator = true;
|
||||
|
||||
|
||||
// add category
|
||||
var filter = filters[column_key];
|
||||
if (filter && cf[column_key] && filter == cf_arg) {
|
||||
tmpl += '<span class="categorical-filter ' + column_key + '-filter current-filter">' + cf_label + '</span>';
|
||||
tmpl += `<span class="categorical-filter ${column_key}-filter current-filter">${cf_label}</span>`;
|
||||
} else {
|
||||
tmpl += '<span class="categorical-filter ' + column_key + '-filter">' +
|
||||
'<a href="javascript:void(0);" filter_key="' + cf_key + '" filter_val="' + cf_arg + '">' + cf_label + '</a>' +
|
||||
'</span>';
|
||||
tmpl += `<span class="categorical-filter ${
|
||||
column_key
|
||||
}-filter"><a href="javascript:void(0);" filter_key="${cf_key}" filter_val="${cf_arg}">${
|
||||
cf_label
|
||||
}</a></span>`;
|
||||
}
|
||||
}
|
||||
tmpl += '</span>';
|
||||
tmpl += "</span>";
|
||||
}
|
||||
tmpl += '</td>' +
|
||||
'</tr>';
|
||||
|
||||
tmpl += "</td>" + "</tr>";
|
||||
|
||||
// return template
|
||||
return tmpl;
|
||||
},
|
||||
|
||||
|
||||
// template for filter items
|
||||
filter_element: function(filter_key, filter_value) {
|
||||
filter_value = Utils.sanitize(filter_value);
|
||||
return '<span class="text-filter-val">' + filter_value +
|
||||
'<a href="javascript:void(0);" filter_key="' + filter_key + '" filter_val="' + filter_value + '">' +
|
||||
'<i class="fa fa-times" style="padding-left: 5px; padding-bottom: 6px;"/>' +
|
||||
'</a>' +
|
||||
'</span>';
|
||||
|
||||
return `<span class="text-filter-val">${filter_value}<a href="javascript:void(0);" filter_key="${
|
||||
filter_key
|
||||
}" filter_val="${
|
||||
filter_value
|
||||
}"><i class="fa fa-times" style="padding-left: 5px; padding-bottom: 6px;"/></a></span>`;
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
@@ -3,104 +3,110 @@
|
||||
jQuery.ajaxSettings.traditional = true;
|
||||
|
||||
// dependencies
|
||||
define([
|
||||
'utils/utils',
|
||||
'mvc/grid/grid-model',
|
||||
'mvc/grid/grid-template',
|
||||
"mvc/ui/popup-menu"
|
||||
], function(Utils, GridModel, Templates, PopupMenu) {
|
||||
|
||||
import Utils from "utils/utils";
|
||||
import GridModel from "mvc/grid/grid-model";
|
||||
import Templates from "mvc/grid/grid-template";
|
||||
import PopupMenu from "mvc/ui/popup-menu";
|
||||
// grid view
|
||||
return Backbone.View.extend({
|
||||
|
||||
export default Backbone.View.extend({
|
||||
// model
|
||||
grid: null,
|
||||
|
||||
// Initialize
|
||||
initialize: function(grid_config)
|
||||
{
|
||||
initialize: function(grid_config) {
|
||||
this.grid = new GridModel();
|
||||
this.dict_format = grid_config.dict_format;
|
||||
this.title = grid_config.title;
|
||||
var self = this;
|
||||
window.add_tag_to_grid_filter = function( tag_name, tag_value ){
|
||||
window.add_tag_to_grid_filter = (tag_name, tag_value) => {
|
||||
// Put tag name and value together.
|
||||
var tag = tag_name + ( tag_value !== undefined && tag_value !== "" ? ":" + tag_value : "" );
|
||||
var advanced_search = $( '#advanced-search').is(":visible" );
|
||||
if( !advanced_search ){
|
||||
$('#standard-search').slideToggle('fast');
|
||||
$('#advanced-search').slideToggle('fast');
|
||||
var tag = tag_name + (tag_value !== undefined && tag_value !== "" ? `:${tag_value}` : "");
|
||||
var advanced_search = $("#advanced-search").is(":visible");
|
||||
if (!advanced_search) {
|
||||
$("#standard-search").slideToggle("fast");
|
||||
$("#advanced-search").slideToggle("fast");
|
||||
}
|
||||
self.add_filter_condition( "tags", tag );
|
||||
self.add_filter_condition("tags", tag);
|
||||
};
|
||||
|
||||
// set element
|
||||
if ( this.dict_format ) {
|
||||
this.setElement('<div/>');
|
||||
if ( grid_config.url_base && !grid_config.items ) {
|
||||
Utils.get({
|
||||
url: grid_config.url_base,
|
||||
success: function( response ) {
|
||||
self.init_grid( response );
|
||||
if (this.dict_format) {
|
||||
this.setElement("<div/>");
|
||||
if (grid_config.url_base && !grid_config.items) {
|
||||
var url_data = grid_config.url_data || {};
|
||||
_.each(grid_config.filters, (v, k) => {
|
||||
url_data[`f-${k}`] = v;
|
||||
});
|
||||
$.ajax({
|
||||
url: `${grid_config.url_base}?${$.param(url_data)}`,
|
||||
success: function(response) {
|
||||
response.embedded = grid_config.embedded;
|
||||
response.filters = grid_config.filters || {};
|
||||
self.init_grid(response);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.init_grid(grid_config);
|
||||
}
|
||||
} else {
|
||||
this.setElement('#grid-container');
|
||||
this.setElement("#grid-container");
|
||||
this.init_grid(grid_config);
|
||||
}
|
||||
|
||||
// fix padding
|
||||
if (grid_config.use_panels) {
|
||||
$('#center').css ({
|
||||
padding : '10px',
|
||||
overflow : 'auto'
|
||||
$("#center").css({
|
||||
padding: "10px",
|
||||
overflow: "auto"
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// refresh frames
|
||||
handle_refresh: function (refresh_frames) {
|
||||
handle_refresh: function(refresh_frames) {
|
||||
if (refresh_frames) {
|
||||
if ($.inArray('history', refresh_frames) > -1) {
|
||||
if( top.Galaxy && top.Galaxy.currHistoryPanel ){
|
||||
top.Galaxy.currHistoryPanel.loadCurrentHistory();
|
||||
if ($.inArray("history", refresh_frames) > -1) {
|
||||
if (window.top.Galaxy && window.top.Galaxy.currHistoryPanel) {
|
||||
window.top.Galaxy.currHistoryPanel.loadCurrentHistory();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Initialize
|
||||
init_grid: function(grid_config)
|
||||
{
|
||||
// link grid model
|
||||
this.grid = new GridModel(grid_config);
|
||||
init_grid: function(grid_config) {
|
||||
this.grid.set(grid_config);
|
||||
|
||||
// get options
|
||||
var options = this.grid.attributes;
|
||||
|
||||
if (this.allow_title_display && options.title) {
|
||||
Utils.setWindowTitle(options.title);
|
||||
}
|
||||
// handle refresh requests
|
||||
this.handle_refresh(options.refresh_frames);
|
||||
|
||||
// strip protocol and domain
|
||||
var url = this.grid.get('url_base');
|
||||
url = url.replace(/^.*\/\/[^\/]+/, '');
|
||||
this.grid.set('url_base', url);
|
||||
var url = this.grid.get("url_base");
|
||||
url = url.replace(/^.*\/\/[^\/]+/, "");
|
||||
this.grid.set("url_base", url);
|
||||
|
||||
// append main template
|
||||
this.$el.html(Templates.grid(options));
|
||||
|
||||
// update div contents
|
||||
this.$el.find('#grid-table-header').html(Templates.header(options));
|
||||
this.$el.find('#grid-table-body').html(Templates.body(options));
|
||||
this.$el.find('#grid-table-footer').html(Templates.footer(options));
|
||||
this.$el.find("#grid-table-header").html(Templates.header(options));
|
||||
this.$el.find("#grid-table-body").html(Templates.body(options));
|
||||
this.$el.find("#grid-table-footer").html(Templates.footer(options));
|
||||
|
||||
// update message
|
||||
if (options.message) {
|
||||
this.$el.find('#grid-message').html(Templates.message(options));
|
||||
this.$el.find("#grid-message").html(Templates.message(options));
|
||||
var self = this;
|
||||
if (options.use_hide_message) {
|
||||
setTimeout( function() { self.$el.find('#grid-message').html(''); }, 5000);
|
||||
setTimeout(() => {
|
||||
self.$el.find("#grid-message").html("");
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,17 +116,18 @@ return Backbone.View.extend({
|
||||
|
||||
// attach global event handler
|
||||
// TODO: redundant (the onload/standard page handlers do this) - but needed because these are constructed after page ready
|
||||
init_refresh_on_change();
|
||||
if (window.init_refresh_on_change) {
|
||||
window.init_refresh_on_change();
|
||||
}
|
||||
},
|
||||
|
||||
// Initialize grid controls
|
||||
init_grid_controls: function() {
|
||||
|
||||
// link
|
||||
var self = this;
|
||||
|
||||
// Initialize grid operation button.
|
||||
this.$el.find('.operation-button').each(function() {
|
||||
this.$el.find(".operation-button").each(function() {
|
||||
$(this).off();
|
||||
$(this).click(function() {
|
||||
self.submit_operation(this);
|
||||
@@ -129,43 +136,50 @@ return Backbone.View.extend({
|
||||
});
|
||||
|
||||
// Initialize text filters to select text on click and use normal font when user is typing.
|
||||
this.$el.find('input[type=text]').each(function() {
|
||||
this.$el.find("input[type=text]").each(function() {
|
||||
$(this).off();
|
||||
$(this).click(function() { $(this).select(); } )
|
||||
.keyup(function () { $(this).css('font-style', 'normal'); });
|
||||
$(this)
|
||||
.click(function() {
|
||||
$(this).select();
|
||||
})
|
||||
.keyup(function() {
|
||||
$(this).css("font-style", "normal");
|
||||
});
|
||||
});
|
||||
|
||||
// Initialize sort links.
|
||||
this.$el.find('.sort-link').each( function() {
|
||||
this.$el.find(".sort-link").each(function() {
|
||||
$(this).off();
|
||||
$(this).click( function() {
|
||||
self.set_sort_condition( $(this).attr('sort_key') );
|
||||
return false;
|
||||
$(this).click(function() {
|
||||
self.set_sort_condition($(this).attr("sort_key"));
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
||||
// Initialize text filters.
|
||||
this.$el.find('.text-filter-form').each( function() {
|
||||
this.$el.find(".text-filter-form").each(function() {
|
||||
$(this).off();
|
||||
$(this).submit( function() {
|
||||
var column_key = $(this).attr('column_key');
|
||||
var text_input_obj = $('#input-' + column_key + '-filter');
|
||||
$(this).submit(function() {
|
||||
var column_key = $(this).attr("column_key");
|
||||
var text_input_obj = $(`#input-${column_key}-filter`);
|
||||
var text_input = text_input_obj.val();
|
||||
text_input_obj.val('');
|
||||
text_input_obj.val("");
|
||||
self.add_filter_condition(column_key, text_input);
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
||||
// Initialize categorical filters.
|
||||
this.$el.find('.text-filter-val > a').each( function() {
|
||||
this.$el.find(".text-filter-val > a").each(function() {
|
||||
$(this).off();
|
||||
$(this).click( function() {
|
||||
$(this).click(function() {
|
||||
// Remove visible element.
|
||||
$(this).parent().remove();
|
||||
$(this)
|
||||
.parent()
|
||||
.remove();
|
||||
|
||||
// Remove filter condition.
|
||||
self.remove_filter_condition ($(this).attr('filter_key'), $(this).attr('filter_val'));
|
||||
self.remove_filter_condition($(this).attr("filter_key"), $(this).attr("filter_val"));
|
||||
|
||||
// Return
|
||||
return false;
|
||||
@@ -173,50 +187,49 @@ return Backbone.View.extend({
|
||||
});
|
||||
|
||||
// Initialize categorical filters.
|
||||
this.$el.find('.categorical-filter > a').each( function() {
|
||||
this.$el.find(".categorical-filter > a").each(function() {
|
||||
$(this).off();
|
||||
$(this).click( function() {
|
||||
self.set_categorical_filter( $(this).attr('filter_key'), $(this).attr('filter_val') );
|
||||
$(this).click(function() {
|
||||
self.set_categorical_filter($(this).attr("filter_key"), $(this).attr("filter_val"));
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
||||
// Initialize standard, advanced search toggles.
|
||||
this.$el.find('.advanced-search-toggle').each( function() {
|
||||
this.$el.find(".advanced-search-toggle").each(function() {
|
||||
$(this).off();
|
||||
$(this).click( function() {
|
||||
self.$el.find('#standard-search').slideToggle('fast');
|
||||
self.$el.find('#advanced-search').slideToggle('fast');
|
||||
$(this).click(() => {
|
||||
self.$el.find("#standard-search").slideToggle("fast");
|
||||
self.$el.find("#advanced-search").slideToggle("fast");
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
||||
// Add event to check all box
|
||||
this.$el.find('#check_all').off();
|
||||
this.$el.find('#check_all').on('click', function() {
|
||||
this.$el.find("#check_all").off();
|
||||
this.$el.find("#check_all").on("click", () => {
|
||||
self.check_all_items();
|
||||
});
|
||||
},
|
||||
|
||||
// Initialize grid elements.
|
||||
init_grid_elements : function() {
|
||||
init_grid_elements: function() {
|
||||
// Initialize grid selection checkboxes.
|
||||
this.$el.find('.grid').each( function() {
|
||||
this.$el.find(".grid").each(function() {
|
||||
var checkboxes = $(this).find("input.grid-row-select-checkbox");
|
||||
var check_count = $(this).find("span.grid-selected-count");
|
||||
var update_checked = function() {
|
||||
check_count.text( $(checkboxes).filter(":checked").length );
|
||||
var update_checked = () => {
|
||||
check_count.text($(checkboxes).filter(":checked").length);
|
||||
};
|
||||
|
||||
$(checkboxes).each( function() {
|
||||
$(checkboxes).each(function() {
|
||||
$(this).change(update_checked);
|
||||
});
|
||||
update_checked();
|
||||
});
|
||||
|
||||
// Initialize ratings.
|
||||
if (this.$el.find('.community_rating_star').length !== 0)
|
||||
this.$el.find('.community_rating_star').rating({});
|
||||
if (this.$el.find(".community_rating_star").length !== 0) this.$el.find(".community_rating_star").rating({});
|
||||
|
||||
// get options
|
||||
var options = this.grid.attributes;
|
||||
@@ -225,85 +238,67 @@ return Backbone.View.extend({
|
||||
//
|
||||
// add page click events
|
||||
//
|
||||
this.$el.find('.page-link > a').each( function() {
|
||||
$(this).click( function() {
|
||||
self.set_page( $(this).attr('page_num') );
|
||||
return false;
|
||||
this.$el.find(".page-link > a").each(function() {
|
||||
$(this).click(function() {
|
||||
self.set_page($(this).attr("page_num"));
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
||||
//
|
||||
// add inbound/outbound events
|
||||
//
|
||||
this.$el.find('.use-target').each( function() {
|
||||
$(this).click( function(e) {
|
||||
this.$el.find(".use-target").each(function() {
|
||||
$(this).click(function() {
|
||||
self.execute({
|
||||
href : $(this).attr('href'),
|
||||
target : $(this).attr('target')
|
||||
href: $(this).attr("href"),
|
||||
target: $(this).attr("target")
|
||||
});
|
||||
return false;
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
// empty grid?
|
||||
var items_length = options.items.length;
|
||||
if (items_length == 0) {
|
||||
if (items_length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// add operation popup menus
|
||||
//
|
||||
for (var i in options.items)
|
||||
{
|
||||
// get items
|
||||
var item = options.items[i];
|
||||
|
||||
// get identifiers
|
||||
var button = this.$el.find('#grid-' + i + '-popup');
|
||||
button.off();
|
||||
_.each(options.items, (item, index) => {
|
||||
var button = self.$(`#grid-${item.encode_id}-popup`).off();
|
||||
var popup = new PopupMenu(button);
|
||||
_.each(options.operations, operation => {
|
||||
self._add_operation(popup, operation, item);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
// load details
|
||||
for (var j in options['operations'])
|
||||
{
|
||||
// get operation details
|
||||
var operation = options['operations'][j];
|
||||
var operation_id = operation['label'];
|
||||
var operation_settings = item['operation_config'][operation_id];
|
||||
var encode_id = item['encode_id'];
|
||||
|
||||
// check
|
||||
if (operation_settings['allowed'] && operation['allow_popup'])
|
||||
{
|
||||
// popup configuration
|
||||
var popupConfig =
|
||||
{
|
||||
html : operation['label'],
|
||||
href : operation_settings['url_args'],
|
||||
target : operation_settings['target'],
|
||||
confirmation_text : operation['confirm']
|
||||
};
|
||||
|
||||
// add popup function
|
||||
popupConfig.func = function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
var label = $(e.target).html();
|
||||
var options = this.findItemByHtml(label);
|
||||
self.execute(options);
|
||||
};
|
||||
|
||||
// add item
|
||||
popup.addItem(popupConfig);
|
||||
/** Add an operation to the items menu */
|
||||
_add_operation: function(popup, operation, item) {
|
||||
var self = this;
|
||||
var settings = item.operation_config[operation.label];
|
||||
if (settings.allowed && operation.allow_popup) {
|
||||
popup.addItem({
|
||||
html: operation.label,
|
||||
href: settings.url_args,
|
||||
target: settings.target,
|
||||
confirmation_text: operation.confirm,
|
||||
func: function(e) {
|
||||
e.preventDefault();
|
||||
var label = $(e.target).html();
|
||||
if (operation.onclick) {
|
||||
operation.onclick(item.encode_id);
|
||||
} else {
|
||||
self.execute(this.findItemByHtml(label));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// Add a condition to the grid filter; this adds the condition and refreshes the grid.
|
||||
add_filter_condition: function (name, value) {
|
||||
add_filter_condition: function(name, value) {
|
||||
// Do nothing is value is empty.
|
||||
if (value === "") {
|
||||
return false;
|
||||
@@ -324,7 +319,7 @@ return Backbone.View.extend({
|
||||
});
|
||||
|
||||
// append to container
|
||||
var container = this.$el.find('#' + name + '-filtering-criteria');
|
||||
var container = this.$el.find(`#${name}-filtering-criteria`);
|
||||
container.append(t);
|
||||
|
||||
// execute
|
||||
@@ -333,7 +328,7 @@ return Backbone.View.extend({
|
||||
},
|
||||
|
||||
// Remove a condition to the grid filter; this adds the condition and refreshes the grid.
|
||||
remove_filter_condition: function (name, value) {
|
||||
remove_filter_condition: function(name, value) {
|
||||
// Remove filter condition.
|
||||
this.grid.remove_filter(name, value);
|
||||
|
||||
@@ -343,59 +338,60 @@ return Backbone.View.extend({
|
||||
},
|
||||
|
||||
// Set sort condition for grid.
|
||||
set_sort_condition: function (col_key) {
|
||||
set_sort_condition: function(col_key) {
|
||||
// Set new sort condition. New sort is col_key if sorting new column; if reversing sort on
|
||||
// currently sorted column, sort is reversed.
|
||||
var cur_sort = this.grid.get('sort_key');
|
||||
var cur_sort = this.grid.get("sort_key");
|
||||
var new_sort = col_key;
|
||||
if (cur_sort.indexOf(col_key) !== -1) {
|
||||
// Reverse sort.
|
||||
if (cur_sort.substring(0, 1) !== '-') {
|
||||
new_sort = '-' + col_key;
|
||||
if (cur_sort.substring(0, 1) !== "-") {
|
||||
new_sort = `-${col_key}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove sort arrows elements.
|
||||
this.$el.find('.sort-arrow').remove();
|
||||
this.$el.find(".sort-arrow").remove();
|
||||
|
||||
// Add sort arrow element to new sort column.
|
||||
var sort_arrow = (new_sort.substring(0,1) == '-') ? '↑' : '↓';
|
||||
var t = $('<span>' + sort_arrow + '</span>').addClass('sort-arrow');
|
||||
var sort_arrow = new_sort.substring(0, 1) == "-" ? "↑" : "↓";
|
||||
var t = $(`<span>${sort_arrow}</span>`).addClass("sort-arrow");
|
||||
|
||||
// Add to header
|
||||
this.$el.find('#' + col_key + '-header').append(t);
|
||||
this.$el.find(`#${col_key}-header`).append(t);
|
||||
|
||||
// Update grid.
|
||||
this.grid.set('sort_key', new_sort);
|
||||
this.grid.set("sort_key", new_sort);
|
||||
this.go_page_one();
|
||||
this.execute();
|
||||
},
|
||||
|
||||
// Set new value for categorical filter.
|
||||
set_categorical_filter: function (name, new_value) {
|
||||
set_categorical_filter: function(name, new_value) {
|
||||
// Update filter hyperlinks to reflect new filter value.
|
||||
var category_filter = this.grid.get('categorical_filters')[name],
|
||||
cur_value = this.grid.get('filters')[name];
|
||||
var category_filter = this.grid.get("categorical_filters")[name];
|
||||
|
||||
var cur_value = this.grid.get("filters")[name];
|
||||
var self = this;
|
||||
this.$el.find('.' + name + '-filter').each( function() {
|
||||
var text = $.trim( $(this).text() );
|
||||
this.$el.find(`.${name}-filter`).each(function() {
|
||||
var text = $.trim($(this).text());
|
||||
var filter = category_filter[text];
|
||||
var filter_value = filter[name];
|
||||
if (filter_value == new_value) {
|
||||
// Remove filter link since grid will be using this filter. It is assumed that
|
||||
// this element has a single child, a hyperlink/anchor with text.
|
||||
$(this).empty();
|
||||
$(this).addClass('current-filter');
|
||||
$(this).addClass("current-filter");
|
||||
$(this).append(text);
|
||||
} else if (filter_value == cur_value) {
|
||||
// Add hyperlink for this filter since grid will no longer be using this filter. It is assumed that
|
||||
// this element has a single child, a hyperlink/anchor.
|
||||
$(this).empty();
|
||||
var t = $('<a href="#">' + text + '</a>');
|
||||
t.click(function() {
|
||||
self.set_categorical_filter( name, filter_value );
|
||||
var t = $(`<a href="#">${text}</a>`);
|
||||
t.click(() => {
|
||||
self.set_categorical_filter(name, filter_value);
|
||||
});
|
||||
$(this).removeClass('current-filter');
|
||||
$(this).removeClass("current-filter");
|
||||
$(this).append(t);
|
||||
}
|
||||
});
|
||||
@@ -407,116 +403,111 @@ return Backbone.View.extend({
|
||||
},
|
||||
|
||||
// Set page to view.
|
||||
set_page: function (new_page) {
|
||||
set_page: function(new_page) {
|
||||
// Update page hyperlink to reflect new page.
|
||||
var self = this;
|
||||
this.$el.find('.page-link').each( function() {
|
||||
var id = $(this).attr('id'),
|
||||
page_num = parseInt( id.split('-')[2], 10 ), // Id has form 'page-link-<page_num>
|
||||
cur_page = self.grid.get('cur_page'),
|
||||
text;
|
||||
this.$el.find(".page-link").each(function() {
|
||||
var id = $(this).attr("id");
|
||||
|
||||
var // Id has form 'page-link-<page_num>
|
||||
page_num = parseInt(id.split("-")[2], 10);
|
||||
|
||||
var cur_page = self.grid.get("cur_page");
|
||||
var text;
|
||||
if (page_num === new_page) {
|
||||
// Remove link to page since grid will be on this page. It is assumed that
|
||||
// this element has a single child, a hyperlink/anchor with text.
|
||||
text = $(this).children().text();
|
||||
text = $(this)
|
||||
.children()
|
||||
.text();
|
||||
$(this).empty();
|
||||
$(this).addClass('inactive-link');
|
||||
$(this).addClass("inactive-link");
|
||||
$(this).text(text);
|
||||
}
|
||||
else if (page_num === cur_page) {
|
||||
} else if (page_num === cur_page) {
|
||||
// Add hyperlink to this page since grid will no longer be on this page. It is assumed that
|
||||
// this element has a single child, a hyperlink/anchor.
|
||||
text = $(this).text();
|
||||
$(this).empty();
|
||||
$(this).removeClass('inactive-link');
|
||||
var t = $('<a href="#">' + text + '</a>');
|
||||
t.click(function() {
|
||||
$(this).removeClass("inactive-link");
|
||||
var t = $(`<a href="#">${text}</a>`);
|
||||
t.click(() => {
|
||||
self.set_page(page_num);
|
||||
});
|
||||
$(this).append(t);
|
||||
}
|
||||
});
|
||||
|
||||
if (new_page === 'all') {
|
||||
this.grid.set('cur_page', new_page);
|
||||
if (new_page === "all") {
|
||||
this.grid.set("cur_page", new_page);
|
||||
} else {
|
||||
this.grid.set('cur_page', parseInt(new_page, 10));
|
||||
this.grid.set("cur_page", parseInt(new_page, 10));
|
||||
}
|
||||
this.execute();
|
||||
},
|
||||
|
||||
// confirmation/submission of operation request
|
||||
submit_operation: function (operation_button, confirmation_text)
|
||||
{
|
||||
submit_operation: function(operation_button, confirmation_text) {
|
||||
// identify operation
|
||||
var operation_name = $(operation_button).val();
|
||||
|
||||
// verify in any item is selected
|
||||
// verify any item is selected
|
||||
var number_of_checked_ids = this.$el.find('input[name="id"]:checked').length;
|
||||
if (!number_of_checked_ids > 0) {
|
||||
if (number_of_checked_ids < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check to see if there's grid confirmation text for this operation
|
||||
var operation = _.findWhere(this.grid.attributes.operations, {label: operation_name});
|
||||
if (operation && !confirmation_text){
|
||||
confirmation_text = operation.confirm || '';
|
||||
var operation = _.findWhere(this.grid.attributes.operations, {
|
||||
label: operation_name
|
||||
});
|
||||
if (operation && !confirmation_text) {
|
||||
confirmation_text = operation.confirm || "";
|
||||
}
|
||||
|
||||
// collect ids
|
||||
var item_ids = [];
|
||||
this.$el.find('input[name=id]:checked').each(function() {
|
||||
item_ids.push( $(this).val() );
|
||||
this.$el.find("input[name=id]:checked").each(function() {
|
||||
item_ids.push($(this).val());
|
||||
});
|
||||
|
||||
// execute operation
|
||||
this.execute({
|
||||
var options = {
|
||||
operation: operation_name,
|
||||
id: item_ids,
|
||||
confirmation_text: confirmation_text
|
||||
});
|
||||
|
||||
// return
|
||||
};
|
||||
if (operation.target == "top" || operation.target == "center") {
|
||||
options = _.extend(options, {
|
||||
href: operation.href,
|
||||
target: operation.target
|
||||
});
|
||||
}
|
||||
this.execute(options);
|
||||
return true;
|
||||
},
|
||||
|
||||
check_all_items: function () {
|
||||
var chk_all = document.getElementById('check_all'),
|
||||
checks = document.getElementsByTagName('input'),
|
||||
total = 0,
|
||||
i;
|
||||
if ( chk_all.checked === true ) {
|
||||
for ( i=0; i < checks.length; i++ ) {
|
||||
if ( checks[i].name.indexOf( 'id' ) !== -1) {
|
||||
checks[i].checked = true;
|
||||
total++;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
for ( i=0; i < checks.length; i++ ) {
|
||||
if ( checks[i].name.indexOf( 'id' ) !== -1) {
|
||||
checks[i].checked = false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
check_all_items: function() {
|
||||
var check = this.$(".grid-row-select-checkbox");
|
||||
var state = this.$("#check_all").prop("checked");
|
||||
_.each(check, c => {
|
||||
$(c).prop("checked", state);
|
||||
});
|
||||
this.init_grid_elements();
|
||||
},
|
||||
|
||||
// Go back to page one; this is useful when a filter is applied.
|
||||
go_page_one: function () {
|
||||
go_page_one: function() {
|
||||
// Need to go back to page 1 if not showing all.
|
||||
var cur_page = this.grid.get('cur_page');
|
||||
if (cur_page !== null && cur_page !== undefined && cur_page !== 'all') {
|
||||
this.grid.set('cur_page', 1);
|
||||
var cur_page = this.grid.get("cur_page");
|
||||
if (cur_page !== null && cur_page !== undefined && cur_page !== "all") {
|
||||
this.grid.set("cur_page", 1);
|
||||
}
|
||||
},
|
||||
|
||||
//
|
||||
// execute operations and hyperlink requests
|
||||
//
|
||||
execute: function (options) {
|
||||
execute: function(options) {
|
||||
// get url
|
||||
var id = null;
|
||||
var href = null;
|
||||
@@ -534,20 +525,20 @@ return Backbone.View.extend({
|
||||
target = options.target;
|
||||
|
||||
// check if input contains the operation tag
|
||||
if (href !== undefined && href.indexOf('operation=') != -1) {
|
||||
if (href !== undefined && href.indexOf("operation=") != -1) {
|
||||
// Get operation, id in hyperlink's href.
|
||||
var href_parts = href.split("?");
|
||||
if (href_parts.length > 1) {
|
||||
var href_parms_str = href_parts[1];
|
||||
var href_parms = href_parms_str.split("&");
|
||||
for (var index = 0; index < href_parms.length; index++) {
|
||||
if (href_parms[index].indexOf('operation') != -1) {
|
||||
if (href_parms[index].indexOf("operation") != -1) {
|
||||
// Found operation parm; get operation value.
|
||||
operation = href_parms[index].split('=')[1];
|
||||
operation = operation.replace (/\+/g, ' ');
|
||||
} else if (href_parms[index].indexOf('id') != -1) {
|
||||
operation = href_parms[index].split("=")[1];
|
||||
operation = operation.replace(/\+/g, " ");
|
||||
} else if (href_parms[index].indexOf("id") != -1) {
|
||||
// Found id parm; get id value.
|
||||
id = href_parms[index].split('=')[1];
|
||||
id = href_parms[index].split("=")[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -557,9 +548,13 @@ return Backbone.View.extend({
|
||||
// check for operation details
|
||||
if (operation && id) {
|
||||
// show confirmation box
|
||||
if (confirmation_text && confirmation_text != '' && confirmation_text != 'None' && confirmation_text != 'null')
|
||||
if(!confirm(confirmation_text))
|
||||
return false;
|
||||
if (
|
||||
confirmation_text &&
|
||||
confirmation_text !== "" &&
|
||||
confirmation_text != "None" &&
|
||||
confirmation_text != "null"
|
||||
)
|
||||
if (!confirm(confirmation_text)) return false;
|
||||
|
||||
// use small characters for operation?!
|
||||
operation = operation.toLowerCase();
|
||||
@@ -571,7 +566,11 @@ return Backbone.View.extend({
|
||||
});
|
||||
|
||||
// Do operation. If operation cannot be performed asynchronously, redirect to location.
|
||||
if (this.grid.can_async_op(operation) || this.dict_format) {
|
||||
if (target == "top") {
|
||||
window.top.location = `${href}?${$.param(this.grid.get_url_data())}`;
|
||||
} else if (target == "center") {
|
||||
$("#galaxy_main").attr("src", `${href}?${$.param(this.grid.get_url_data())}`);
|
||||
} else if (this.grid.can_async_op(operation) || this.dict_format) {
|
||||
this.update_grid();
|
||||
} else {
|
||||
this.go_to(target, href);
|
||||
@@ -588,7 +587,7 @@ return Backbone.View.extend({
|
||||
}
|
||||
|
||||
// refresh grid
|
||||
if (this.grid.get('async') || this.dict_format) {
|
||||
if (this.grid.get("async") || this.dict_format) {
|
||||
this.update_grid();
|
||||
} else {
|
||||
this.go_to(target, href);
|
||||
@@ -599,18 +598,18 @@ return Backbone.View.extend({
|
||||
},
|
||||
|
||||
// go to url
|
||||
go_to: function (target, href) {
|
||||
go_to: function(target, href) {
|
||||
// get aysnc status
|
||||
var async = this.grid.get('async');
|
||||
this.grid.set('async', false);
|
||||
var async = this.grid.get("async");
|
||||
this.grid.set("async", false);
|
||||
|
||||
// get slide status
|
||||
advanced_search = this.$el.find('#advanced-search').is(':visible');
|
||||
this.grid.set('advanced_search', advanced_search);
|
||||
var advanced_search = this.$el.find("#advanced-search").is(":visible");
|
||||
this.grid.set("advanced_search", advanced_search);
|
||||
|
||||
// get default url
|
||||
if(!href) {
|
||||
href = this.grid.get('url_base') + '?' + $.param(this.grid.get_url_data());
|
||||
if (!href) {
|
||||
href = `${this.grid.get("url_base")}?${$.param(this.grid.get_url_data())}`;
|
||||
}
|
||||
|
||||
// clear grid of transient request attributes.
|
||||
@@ -620,15 +619,10 @@ return Backbone.View.extend({
|
||||
async: async
|
||||
});
|
||||
switch (target) {
|
||||
case 'inbound':
|
||||
// this currently assumes that there is only a single grid shown at a time
|
||||
var $div = $('.grid-header').closest('.inbound');
|
||||
if ($div.length !== 0) {
|
||||
$div.load(href);
|
||||
return;
|
||||
}
|
||||
case "center":
|
||||
$("#galaxy_main").attr("src", href);
|
||||
break;
|
||||
case 'top':
|
||||
case "top":
|
||||
window.top.location = href;
|
||||
break;
|
||||
default:
|
||||
@@ -637,23 +631,25 @@ return Backbone.View.extend({
|
||||
},
|
||||
|
||||
// Update grid.
|
||||
update_grid: function () {
|
||||
update_grid: function() {
|
||||
// If there's an operation, do POST; otherwise, do GET.
|
||||
var method = (this.grid.get('operation') ? 'POST' : 'GET' );
|
||||
var method = this.grid.get("operation") ? "POST" : "GET";
|
||||
|
||||
// Show overlay to indicate loading and prevent user actions.
|
||||
this.$el.find('.loading-elt-overlay').show();
|
||||
this.$el.find(".loading-elt-overlay").show();
|
||||
var self = this;
|
||||
$.ajax({
|
||||
type: method,
|
||||
url: self.grid.get('url_base'),
|
||||
url: self.grid.get("url_base"),
|
||||
data: self.grid.get_url_data(),
|
||||
error: function(response) { alert( 'Grid refresh failed' );},
|
||||
error: function() {
|
||||
alert("Grid refresh failed");
|
||||
},
|
||||
success: function(response_text) {
|
||||
|
||||
// backup
|
||||
var embedded = self.grid.get('embedded');
|
||||
var insert = self.grid.get('insert');
|
||||
var embedded = self.grid.get("embedded");
|
||||
var insert = self.grid.get("insert");
|
||||
var advanced_search = self.$el.find("#advanced-search").is(":visible");
|
||||
|
||||
// request new configuration
|
||||
var json = self.dict_format ? response_text : $.parseJSON(response_text);
|
||||
@@ -661,12 +657,13 @@ return Backbone.View.extend({
|
||||
// update
|
||||
json.embedded = embedded;
|
||||
json.insert = insert;
|
||||
json.advanced_search = advanced_search;
|
||||
|
||||
// Initialize new grid config
|
||||
self.init_grid(json);
|
||||
|
||||
// Hide loading overlay.
|
||||
self.$el.find('.loading-elt-overlay').hide();
|
||||
self.$el.find(".loading-elt-overlay").hide();
|
||||
},
|
||||
complete: function() {
|
||||
// Clear grid of transient request attributes.
|
||||
@@ -678,5 +675,3 @@ return Backbone.View.extend({
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
define([
|
||||
"mvc/ui/ui-modal",
|
||||
"mvc/ui/error-modal",
|
||||
"utils/localization"
|
||||
], function( MODAL, ERROR_MODAL, _l ){
|
||||
|
||||
'use strict';
|
||||
import MODAL from "mvc/ui/ui-modal";
|
||||
import ERROR_MODAL from "mvc/ui/error-modal";
|
||||
import _l from "utils/localization";
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
@@ -13,144 +9,183 @@ define([
|
||||
* @type {Object}
|
||||
*/
|
||||
var CopyDialog = {
|
||||
|
||||
// language related strings/fns
|
||||
defaultName : _.template( "Copy of '<%- name %>'" ),
|
||||
title : _.template( _l( 'Copying history' ) + ' "<%- name %>"' ),
|
||||
submitLabel : _l( 'Copy' ),
|
||||
errorMessage : _l( 'History could not be copied.' ),
|
||||
progressive : _l( 'Copying history' ),
|
||||
activeLabel : _l( 'Copy only the active, non-deleted datasets' ),
|
||||
allLabel : _l( 'Copy all datasets including deleted ones' ),
|
||||
anonWarning : _l( 'As an anonymous user, unless you login or register, you will lose your current history ' ) +
|
||||
_l( 'after copying this history. ' ),
|
||||
defaultName: _.template("Copy of '<%- name %>'"),
|
||||
title: _.template(`${_l("Copying history")} "<%- name %>"`),
|
||||
submitLabel: _l("Copy"),
|
||||
errorMessage: _l("History could not be copied."),
|
||||
progressive: _l("Copying history"),
|
||||
activeLabel: _l("Copy only the active, non-deleted datasets"),
|
||||
allLabel: _l("Copy all datasets including deleted ones"),
|
||||
anonWarning:
|
||||
_l("As an anonymous user, unless you login or register, you will lose your current history ") +
|
||||
_l("after copying this history. "),
|
||||
|
||||
// template for modal body
|
||||
_template : _.template([
|
||||
//TODO: remove inline styles
|
||||
// show a warning message for losing current to anon users
|
||||
'<% if( isAnon ){ %>',
|
||||
_template: _.template(
|
||||
[
|
||||
//TODO: remove inline styles
|
||||
// show a warning message for losing current to anon users
|
||||
"<% if( isAnon ){ %>",
|
||||
'<div class="warningmessage">',
|
||||
'<%- anonWarning %>',
|
||||
_l( 'You can' ),
|
||||
' <a href="/user/login">', _l( 'login here' ), '</a> ', _l( 'or' ), ' ',
|
||||
' <a href="/user/create">', _l( 'register here' ), '</a>.',
|
||||
'</div>',
|
||||
'<% } %>',
|
||||
'<form>',
|
||||
"<%- anonWarning %>",
|
||||
_l("You can"),
|
||||
' <a href="/user/login">',
|
||||
_l("login here"),
|
||||
"</a> ",
|
||||
_l("or"),
|
||||
" ",
|
||||
' <a href="/user/create">',
|
||||
_l("register here"),
|
||||
"</a>.",
|
||||
"</div>",
|
||||
"<% } %>",
|
||||
"<form>",
|
||||
'<label for="copy-modal-title">',
|
||||
_l( 'Enter a title for the new history' ), ':',
|
||||
'</label><br />',
|
||||
_l("Enter a title for the new history"),
|
||||
":",
|
||||
"</label><br />",
|
||||
// TODO: could use required here and the form validators
|
||||
// NOTE: use unescaped here if escaped in the modal function below
|
||||
'<input id="copy-modal-title" class="form-control" style="width: 100%" value="<%= name %>" />',
|
||||
'<p class="invalid-title bg-danger" style="color: red; margin: 8px 0px 8px 0px; display: none">',
|
||||
_l( 'Please enter a valid history title' ),
|
||||
'</p>',
|
||||
_l("Please enter a valid history title"),
|
||||
"</p>",
|
||||
// if allowAll, add the option to copy deleted datasets, too
|
||||
'<% if( allowAll ){ %>',
|
||||
'<br />',
|
||||
'<p>', _l( 'Choose which datasets from the original history to include:' ), '</p>',
|
||||
// copy non-deleted is the default
|
||||
'<input name="copy-what" type="radio" id="copy-non-deleted" value="copy-non-deleted" ',
|
||||
'<% if( copyWhat === "copy-non-deleted" ){ print( "checked" ); } %>/>',
|
||||
'<label for="copy-non-deleted"> <%- activeLabel %></label>',
|
||||
'<br />',
|
||||
'<input name="copy-what" type="radio" id="copy-all" value="copy-all" ',
|
||||
'<% if( copyWhat === "copy-all" ){ print( "checked" ); } %>/>',
|
||||
'<label for="copy-all"> <%- allLabel %></label>',
|
||||
'<% } %>',
|
||||
'</form>'
|
||||
].join( '' )),
|
||||
"<% if( allowAll ){ %>",
|
||||
"<br />",
|
||||
"<p>",
|
||||
_l("Choose which datasets from the original history to include:"),
|
||||
"</p>",
|
||||
// copy non-deleted is the default
|
||||
'<input name="copy-what" type="radio" id="copy-non-deleted" value="copy-non-deleted" ',
|
||||
'<% if( copyWhat === "copy-non-deleted" ){ print( "checked" ); } %>/>',
|
||||
'<label for="copy-non-deleted"> <%- activeLabel %></label>',
|
||||
"<br />",
|
||||
'<input name="copy-what" type="radio" id="copy-all" value="copy-all" ',
|
||||
'<% if( copyWhat === "copy-all" ){ print( "checked" ); } %>/>',
|
||||
'<label for="copy-all"> <%- allLabel %></label>',
|
||||
"<% } %>",
|
||||
"</form>"
|
||||
].join("")
|
||||
),
|
||||
|
||||
// empty modal body and let the user know the copy is happening
|
||||
_showAjaxIndicator : function _showAjaxIndicator(){
|
||||
var indicator = '<p><span class="fa fa-spinner fa-spin"></span> ' + this.progressive + '...</p>';
|
||||
this.modal.$( '.modal-body' ).empty().append( indicator ).css({ 'margin-top': '8px' });
|
||||
_showAjaxIndicator: function _showAjaxIndicator() {
|
||||
var indicator = `<p><span class="fa fa-spinner fa-spin"></span> ${this.progressive}...</p>`;
|
||||
this.modal
|
||||
.$(".modal-body")
|
||||
.empty()
|
||||
.append(indicator)
|
||||
.css({ "margin-top": "8px" });
|
||||
},
|
||||
|
||||
// (sorta) public interface - display the modal, render the form, and potentially copy the history
|
||||
// returns a jQuery.Deferred done->history copied, fail->user cancelled
|
||||
dialog : function _dialog( modal, history, options ){
|
||||
dialog: function _dialog(modal, history, options) {
|
||||
options = options || {};
|
||||
|
||||
var dialog = this,
|
||||
deferred = jQuery.Deferred(),
|
||||
// TODO: getting a little byzantine here
|
||||
defaultCopyNameFn = options.nameFn || this.defaultName,
|
||||
defaultCopyName = defaultCopyNameFn({ name: history.get( 'name' ) }),
|
||||
// TODO: these two might be simpler as one 3 state option (all,active,no-choice)
|
||||
defaultCopyWhat = options.allDatasets? 'copy-all' : 'copy-non-deleted',
|
||||
allowAll = !_.isUndefined( options.allowAll )? options.allowAll : true,
|
||||
autoClose = !_.isUndefined( options.autoClose )? options.autoClose : true;
|
||||
var dialog = this;
|
||||
var deferred = jQuery.Deferred();
|
||||
|
||||
var // TODO: getting a little byzantine here
|
||||
defaultCopyNameFn = options.nameFn || this.defaultName;
|
||||
|
||||
var defaultCopyName = defaultCopyNameFn({
|
||||
name: history.get("name")
|
||||
});
|
||||
|
||||
var // TODO: these two might be simpler as one 3 state option (all,active,no-choice)
|
||||
defaultCopyWhat = options.allDatasets ? "copy-all" : "copy-non-deleted";
|
||||
|
||||
var allowAll = !_.isUndefined(options.allowAll) ? options.allowAll : true;
|
||||
|
||||
var autoClose = !_.isUndefined(options.autoClose) ? options.autoClose : true;
|
||||
|
||||
this.modal = modal;
|
||||
|
||||
|
||||
// validate the name and copy if good
|
||||
function checkNameAndCopy(){
|
||||
var name = modal.$( '#copy-modal-title' ).val();
|
||||
if( !name ){
|
||||
modal.$( '.invalid-title' ).show();
|
||||
function checkNameAndCopy() {
|
||||
var name = modal.$("#copy-modal-title").val();
|
||||
if (!name) {
|
||||
modal.$(".invalid-title").show();
|
||||
return;
|
||||
}
|
||||
// get further settings, shut down and indicate the ajax call, then hide and resolve/reject
|
||||
var copyAllDatasets = modal.$( 'input[name="copy-what"]:checked' ).val() === 'copy-all';
|
||||
modal.$( 'button' ).prop( 'disabled', true );
|
||||
var copyAllDatasets = modal.$('input[name="copy-what"]:checked').val() === "copy-all";
|
||||
modal.$("button").prop("disabled", true);
|
||||
dialog._showAjaxIndicator();
|
||||
history.copy( true, name, copyAllDatasets )
|
||||
.done( function( response ){
|
||||
deferred.resolve( response );
|
||||
history
|
||||
.copy(true, name, copyAllDatasets)
|
||||
.done(response => {
|
||||
deferred.resolve(response);
|
||||
})
|
||||
.fail( function( xhr, status, message ){
|
||||
var options = { name: name, copyAllDatasets: copyAllDatasets };
|
||||
ERROR_MODAL.ajaxErrorModal( history, xhr, options, dialog.errorMessage );
|
||||
deferred.rejectWith( deferred, arguments );
|
||||
.fail(function(xhr, status, message) {
|
||||
var options = {
|
||||
name: name,
|
||||
copyAllDatasets: copyAllDatasets
|
||||
};
|
||||
ERROR_MODAL.ajaxErrorModal(history, xhr, options, dialog.errorMessage);
|
||||
deferred.rejectWith(deferred, arguments);
|
||||
})
|
||||
.done( function(){
|
||||
if( autoClose ){ modal.hide(); }
|
||||
.done(() => {
|
||||
if (autoClose) {
|
||||
modal.hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var originalClosingCallback = options.closing_callback;
|
||||
modal.show( _.extend( options, {
|
||||
title : this.title({ name: history.get( 'name' ) }),
|
||||
body : $( dialog._template({
|
||||
name : defaultCopyName,
|
||||
isAnon : Galaxy.user.isAnonymous(),
|
||||
allowAll : allowAll,
|
||||
copyWhat : defaultCopyWhat,
|
||||
activeLabel : this.activeLabel,
|
||||
allLabel : this.allLabel,
|
||||
anonWarning : this.anonWarning,
|
||||
})),
|
||||
buttons : _.object([
|
||||
[ _l( 'Cancel' ), function(){ modal.hide(); } ],
|
||||
[ this.submitLabel, checkNameAndCopy ]
|
||||
modal.show(
|
||||
_.extend(options, {
|
||||
title: this.title({ name: history.get("name") }),
|
||||
body: $(
|
||||
dialog._template({
|
||||
name: defaultCopyName,
|
||||
isAnon: Galaxy.user.isAnonymous(),
|
||||
allowAll: allowAll,
|
||||
copyWhat: defaultCopyWhat,
|
||||
activeLabel: this.activeLabel,
|
||||
allLabel: this.allLabel,
|
||||
anonWarning: this.anonWarning
|
||||
})
|
||||
),
|
||||
buttons: _.object([
|
||||
[
|
||||
_l("Cancel"),
|
||||
() => {
|
||||
modal.hide();
|
||||
}
|
||||
],
|
||||
[this.submitLabel, checkNameAndCopy]
|
||||
]),
|
||||
height : 'auto',
|
||||
closing_events : true,
|
||||
closing_callback: function _historyCopyClose( cancelled ){
|
||||
if( cancelled ){
|
||||
deferred.reject({ cancelled : true });
|
||||
height: "auto",
|
||||
closing_events: true,
|
||||
closing_callback: function _historyCopyClose(cancelled) {
|
||||
if (cancelled) {
|
||||
deferred.reject({ cancelled: true });
|
||||
}
|
||||
if( originalClosingCallback ){
|
||||
originalClosingCallback( cancelled );
|
||||
if (originalClosingCallback) {
|
||||
originalClosingCallback(cancelled);
|
||||
}
|
||||
}
|
||||
}));
|
||||
})
|
||||
);
|
||||
|
||||
// set the default dataset copy, autofocus the title, and set up for a simple return
|
||||
modal.$( '#copy-modal-title' ).focus().select();
|
||||
modal.$( '#copy-modal-title' ).on( 'keydown', function( ev ){
|
||||
if( ev.keyCode === 13 ){
|
||||
modal
|
||||
.$("#copy-modal-title")
|
||||
.focus()
|
||||
.select();
|
||||
modal.$("#copy-modal-title").on("keydown", ev => {
|
||||
if (ev.keyCode === 13) {
|
||||
ev.preventDefault();
|
||||
checkNameAndCopy();
|
||||
}
|
||||
});
|
||||
|
||||
return deferred;
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
@@ -158,17 +193,17 @@ var CopyDialog = {
|
||||
/**
|
||||
* Subclass CopyDialog to use the import language.
|
||||
*/
|
||||
var ImportDialog = _.extend( {}, CopyDialog, {
|
||||
defaultName : _.template( "imported: <%- name %>" ),
|
||||
title : _.template( _l( 'Importing history' ) + ' "<%- name %>"' ),
|
||||
submitLabel : _l( 'Import' ),
|
||||
errorMessage : _l( 'History could not be imported.' ),
|
||||
progressive : _l( 'Importing history' ),
|
||||
activeLabel : _l( 'Import only the active, non-deleted datasets' ),
|
||||
allLabel : _l( 'Import all datasets including deleted ones' ),
|
||||
anonWarning : _l( 'As an anonymous user, unless you login or register, you will lose your current history ' ) +
|
||||
_l( 'after importing this history. ' ),
|
||||
|
||||
var ImportDialog = _.extend({}, CopyDialog, {
|
||||
defaultName: _.template("imported: <%- name %>"),
|
||||
title: _.template(`${_l("Importing history")} "<%- name %>"`),
|
||||
submitLabel: _l("Import"),
|
||||
errorMessage: _l("History could not be imported."),
|
||||
progressive: _l("Importing history"),
|
||||
activeLabel: _l("Import only the active, non-deleted datasets"),
|
||||
allLabel: _l("Import all datasets including deleted ones"),
|
||||
anonWarning:
|
||||
_l("As an anonymous user, unless you login or register, you will lose your current history ") +
|
||||
_l("after importing this history. ")
|
||||
});
|
||||
|
||||
//==============================================================================
|
||||
@@ -187,16 +222,14 @@ var ImportDialog = _.extend( {}, CopyDialog, {
|
||||
* only non-deleted datasets
|
||||
* {String} allDatasets default initial checked radio button: 'copy-all' or 'copy-non-deleted',
|
||||
*/
|
||||
var historyCopyDialog = function( history, options ){
|
||||
var historyCopyDialog = (history, options) => {
|
||||
options = options || {};
|
||||
// create our own modal if Galaxy doesn't have one (mako tab without use_panels)
|
||||
var modal = window.parent.Galaxy.modal || new MODAL.View({});
|
||||
return options.useImport?
|
||||
ImportDialog.dialog( modal, history, options ):
|
||||
CopyDialog.dialog( modal, history, options );
|
||||
return options.useImport
|
||||
? ImportDialog.dialog(modal, history, options)
|
||||
: CopyDialog.dialog(modal, history, options);
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
return historyCopyDialog;
|
||||
});
|
||||
export default historyCopyDialog;
|
||||
|
||||
@@ -1,79 +1,82 @@
|
||||
define([
|
||||
"mvc/dataset/dataset-li-edit",
|
||||
"mvc/history/hda-li",
|
||||
"mvc/base-mvc",
|
||||
"utils/localization"
|
||||
], function( DATASET_LI_EDIT, HDA_LI, BASE_MVC, _l ){
|
||||
|
||||
'use strict';
|
||||
import DATASET_LI_EDIT from "mvc/dataset/dataset-li-edit";
|
||||
import HDA_LI from "mvc/history/hda-li";
|
||||
import BASE_MVC from "mvc/base-mvc";
|
||||
import _l from "utils/localization";
|
||||
|
||||
//==============================================================================
|
||||
var _super = DATASET_LI_EDIT.DatasetListItemEdit;
|
||||
/** @class Editing view for HistoryDatasetAssociation.
|
||||
*/
|
||||
var HDAListItemEdit = _super.extend(
|
||||
/** @lends HDAListItemEdit.prototype */{
|
||||
/** @lends HDAListItemEdit.prototype */ {
|
||||
className: `${_super.prototype.className} history-content`,
|
||||
|
||||
className : _super.prototype.className + " history-content",
|
||||
/** In this override, only get details if in the ready state, get rerunnable if in other states.
|
||||
* Note: fetch with no 'change' event triggering to prevent automatic rendering.
|
||||
*/
|
||||
_fetchModelDetails: function() {
|
||||
var view = this;
|
||||
if (view.model.inReadyState() && !view.model.hasDetails()) {
|
||||
return view.model.fetch({ silent: true });
|
||||
|
||||
/** In this override, only get details if in the ready state, get rerunnable if in other states.
|
||||
* Note: fetch with no 'change' event triggering to prevent automatic rendering.
|
||||
*/
|
||||
_fetchModelDetails : function(){
|
||||
var view = this;
|
||||
if( view.model.inReadyState() && !view.model.hasDetails() ){
|
||||
return view.model.fetch({ silent: true });
|
||||
// special case the need for the rerunnable and creating_job attributes
|
||||
// needed for rendering re-run button on queued, running datasets
|
||||
} else if (!view.model.has("rerunnable")) {
|
||||
return view.model.fetch({
|
||||
silent: true,
|
||||
data: {
|
||||
// only fetch rerunnable and creating_job to keep overhead down
|
||||
keys: ["rerunnable", "creating_job"].join(",")
|
||||
}
|
||||
});
|
||||
}
|
||||
return jQuery.when();
|
||||
},
|
||||
|
||||
// special case the need for the rerunnable and creating_job attributes
|
||||
// needed for rendering re-run button on queued, running datasets
|
||||
} else if( !view.model.has( 'rerunnable' ) ){
|
||||
return view.model.fetch({ silent: true, data: {
|
||||
// only fetch rerunnable and creating_job to keep overhead down
|
||||
keys: [ 'rerunnable', 'creating_job' ].join(',')
|
||||
}});
|
||||
/** event map */
|
||||
events: _.extend(_.clone(_super.prototype.events), {
|
||||
"click .unhide-link": function(ev) {
|
||||
this.model.unhide();
|
||||
return false;
|
||||
}
|
||||
}),
|
||||
|
||||
/** string rep */
|
||||
toString: function() {
|
||||
var modelString = this.model ? `${this.model}` : "(no model)";
|
||||
return `HDAListItemEdit(${modelString})`;
|
||||
}
|
||||
return jQuery.when();
|
||||
},
|
||||
|
||||
/** event map */
|
||||
events : _.extend( _.clone( _super.prototype.events ), {
|
||||
'click .unhide-link' : function( ev ){ this.model.unhide(); return false; }
|
||||
}),
|
||||
|
||||
/** string rep */
|
||||
toString : function(){
|
||||
var modelString = ( this.model )?( this.model + '' ):( '(no model)' );
|
||||
return 'HDAListItemEdit(' + modelString + ')';
|
||||
}
|
||||
});
|
||||
|
||||
);
|
||||
|
||||
// ............................................................................ TEMPLATES
|
||||
/** underscore templates */
|
||||
HDAListItemEdit.prototype.templates = (function(){
|
||||
|
||||
var warnings = _.extend( {}, _super.prototype.templates.warnings, {
|
||||
hidden : BASE_MVC.wrapTemplate([
|
||||
'<% if( !dataset.visible ){ %>',
|
||||
HDAListItemEdit.prototype.templates = (() => {
|
||||
var warnings = _.extend({}, _super.prototype.templates.warnings, {
|
||||
hidden: BASE_MVC.wrapTemplate(
|
||||
[
|
||||
"<% if( !dataset.visible ){ %>",
|
||||
// add a link to unhide a dataset
|
||||
'<div class="hidden-msg warningmessagesmall">',
|
||||
_l( 'This dataset has been hidden' ),
|
||||
'<br /><a class="unhide-link" a href="javascript:void(0);">', _l( 'Unhide it' ), '</a>',
|
||||
'</div>',
|
||||
'<% } %>'
|
||||
], 'dataset' )
|
||||
_l("This dataset has been hidden"),
|
||||
'<br /><a class="unhide-link" a href="javascript:void(0);">',
|
||||
_l("Unhide it"),
|
||||
"</a>",
|
||||
"</div>",
|
||||
"<% } %>"
|
||||
],
|
||||
"dataset"
|
||||
)
|
||||
});
|
||||
|
||||
return _.extend( {}, _super.prototype.templates, {
|
||||
return _.extend({}, _super.prototype.templates, {
|
||||
//NOTE: *steal* the HDAListItemView titleBar
|
||||
titleBar : HDA_LI.HDAListItemView.prototype.templates.titleBar,
|
||||
warnings : warnings
|
||||
titleBar: HDA_LI.HDAListItemView.prototype.templates.titleBar,
|
||||
warnings: warnings
|
||||
});
|
||||
}());
|
||||
|
||||
})();
|
||||
|
||||
//==============================================================================
|
||||
return {
|
||||
HDAListItemEdit : HDAListItemEdit
|
||||
};
|
||||
});
|
||||
export default {
|
||||
HDAListItemEdit: HDAListItemEdit
|
||||
};
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
define([
|
||||
"mvc/dataset/dataset-li",
|
||||
"mvc/base-mvc",
|
||||
"utils/localization"
|
||||
], function( DATASET_LI, BASE_MVC, _l ){
|
||||
|
||||
'use strict';
|
||||
import DATASET_LI from "mvc/dataset/dataset-li";
|
||||
import BASE_MVC from "mvc/base-mvc";
|
||||
import HISTORY_ITEM_LI from "mvc/history/history-item-li";
|
||||
import _l from "utils/localization";
|
||||
|
||||
//==============================================================================
|
||||
var _super = DATASET_LI.DatasetListItemView;
|
||||
@@ -14,67 +11,58 @@ var _super = DATASET_LI.DatasetListItemView;
|
||||
* to render the HID.
|
||||
*/
|
||||
var HDAListItemView = _super.extend(
|
||||
/** @lends HDAListItemView.prototype */{
|
||||
/** @lends HDAListItemView.prototype */ {
|
||||
className: `${_super.prototype.className} history-content`,
|
||||
|
||||
className : _super.prototype.className + " history-content",
|
||||
initialize: function(attributes, options) {
|
||||
_super.prototype.initialize.call(this, attributes, options);
|
||||
},
|
||||
|
||||
initialize : function( attributes, options ){
|
||||
_super.prototype.initialize.call( this, attributes, options );
|
||||
},
|
||||
|
||||
// ......................................................................... misc
|
||||
/** String representation */
|
||||
toString : function(){
|
||||
var modelString = ( this.model )?( this.model + '' ):( '(no model)' );
|
||||
return 'HDAListItemView(' + modelString + ')';
|
||||
// ......................................................................... misc
|
||||
/** String representation */
|
||||
toString: function() {
|
||||
var modelString = this.model ? `${this.model}` : "(no model)";
|
||||
return `HDAListItemView(${modelString})`;
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
// ............................................................................ TEMPLATES
|
||||
/** underscore templates */
|
||||
HDAListItemView.prototype.templates = (function(){
|
||||
HDAListItemView.prototype.templates = (() => {
|
||||
var titleBarTemplate = dataset => `
|
||||
<div class="title-bar clear" tabindex="0">
|
||||
<span class="state-icon"></span>
|
||||
<div class="title">
|
||||
<span class="hid">${dataset.hid}</span>
|
||||
<span class="name">${_.escape(dataset.name)}</span>
|
||||
</div>
|
||||
</br>
|
||||
${HISTORY_ITEM_LI.nametagTemplate(dataset)}
|
||||
</div>
|
||||
`;
|
||||
|
||||
var titleBarTemplate = BASE_MVC.wrapTemplate([
|
||||
// adding the hid display to the title
|
||||
'<div class="title-bar clear" tabindex="0">',
|
||||
'<span class="state-icon"></span>',
|
||||
'<div class="title">',
|
||||
//TODO: remove whitespace and use margin-right
|
||||
'<span class="hid"><%- dataset.hid %></span> ',
|
||||
'<span class="name"><%- dataset.name %></span>',
|
||||
'</div>',
|
||||
'</br>',
|
||||
'<span class="nametags">',
|
||||
'<% _.each(_.sortBy(_.uniq(dataset.tags), function(x) { return x }), function(tag){ %>',
|
||||
'<% if (tag.indexOf("name:") == 0){ %>',
|
||||
'<span class="label label-info"><%- tag.slice(5) %></span>',
|
||||
'<% } %>',
|
||||
'<% }); %>',
|
||||
'</span>',
|
||||
'</div>'
|
||||
], 'dataset' );
|
||||
|
||||
var warnings = _.extend( {}, _super.prototype.templates.warnings, {
|
||||
hidden : BASE_MVC.wrapTemplate([
|
||||
// add a warning when hidden
|
||||
'<% if( !dataset.visible ){ %>',
|
||||
var warnings = _.extend({}, _super.prototype.templates.warnings, {
|
||||
hidden: BASE_MVC.wrapTemplate(
|
||||
[
|
||||
// add a warning when hidden
|
||||
"<% if( !dataset.visible ){ %>",
|
||||
'<div class="hidden-msg warningmessagesmall">',
|
||||
_l( 'This dataset has been hidden' ),
|
||||
'</div>',
|
||||
'<% } %>'
|
||||
], 'dataset' )
|
||||
_l("This dataset has been hidden"),
|
||||
"</div>",
|
||||
"<% } %>"
|
||||
],
|
||||
"dataset"
|
||||
)
|
||||
});
|
||||
|
||||
return _.extend( {}, _super.prototype.templates, {
|
||||
titleBar : titleBarTemplate,
|
||||
warnings : warnings
|
||||
return _.extend({}, _super.prototype.templates, {
|
||||
titleBar: titleBarTemplate,
|
||||
warnings: warnings
|
||||
});
|
||||
}());
|
||||
|
||||
|
||||
})();
|
||||
|
||||
//==============================================================================
|
||||
return {
|
||||
HDAListItemView : HDAListItemView
|
||||
};
|
||||
});
|
||||
export default {
|
||||
HDAListItemView: HDAListItemView
|
||||
};
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
define([
|
||||
"mvc/dataset/dataset-model",
|
||||
"mvc/history/history-content-model",
|
||||
"mvc/base-mvc",
|
||||
"utils/localization"
|
||||
], function( DATASET, HISTORY_CONTENT, BASE_MVC, _l ){
|
||||
'use strict';
|
||||
import DATASET from "mvc/dataset/dataset-model";
|
||||
import HISTORY_CONTENT from "mvc/history/history-content-model";
|
||||
import BASE_MVC from "mvc/base-mvc";
|
||||
import _l from "utils/localization";
|
||||
|
||||
//==============================================================================
|
||||
var _super = DATASET.DatasetAssociation,
|
||||
hcontentMixin = HISTORY_CONTENT.HistoryContentMixin;
|
||||
var _super = DATASET.DatasetAssociation;
|
||||
|
||||
var hcontentMixin = HISTORY_CONTENT.HistoryContentMixin;
|
||||
/** @class (HDA) model for a Galaxy dataset contained in and related to a history.
|
||||
*/
|
||||
var HistoryDatasetAssociation = _super.extend( BASE_MVC.mixin( hcontentMixin,
|
||||
/** @lends HistoryDatasetAssociation.prototype */{
|
||||
|
||||
/** default attributes for a model */
|
||||
defaults : _.extend( {}, _super.prototype.defaults, hcontentMixin.defaults, {
|
||||
history_content_type: 'dataset',
|
||||
model_class : 'HistoryDatasetAssociation'
|
||||
}),
|
||||
}));
|
||||
var HistoryDatasetAssociation = _super.extend(
|
||||
BASE_MVC.mixin(
|
||||
hcontentMixin,
|
||||
/** @lends HistoryDatasetAssociation.prototype */ {
|
||||
/** default attributes for a model */
|
||||
defaults: _.extend({}, _super.prototype.defaults, hcontentMixin.defaults, {
|
||||
history_content_type: "dataset",
|
||||
model_class: "HistoryDatasetAssociation"
|
||||
})
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
//==============================================================================
|
||||
return {
|
||||
HistoryDatasetAssociation : HistoryDatasetAssociation
|
||||
};
|
||||
});
|
||||
export default {
|
||||
HistoryDatasetAssociation: HistoryDatasetAssociation
|
||||
};
|
||||
|
||||
@@ -1,75 +1,56 @@
|
||||
define([
|
||||
"mvc/history/hdca-li",
|
||||
"mvc/collection/collection-view-edit",
|
||||
"ui/fa-icon-button",
|
||||
"utils/localization"
|
||||
], function( HDCA_LI, DC_VIEW_EDIT, faIconButton, _l ){
|
||||
|
||||
'use strict';
|
||||
import HDCA_LI from "mvc/history/hdca-li";
|
||||
import DC_VIEW_EDIT from "mvc/collection/collection-view-edit";
|
||||
import faIconButton from "ui/fa-icon-button";
|
||||
import _l from "utils/localization";
|
||||
|
||||
//==============================================================================
|
||||
var _super = HDCA_LI.HDCAListItemView;
|
||||
/** @class Editing view for HistoryDatasetCollectionAssociation.
|
||||
*/
|
||||
var HDCAListItemEdit = _super.extend(
|
||||
/** @lends HDCAListItemEdit.prototype */{
|
||||
/** @lends HDCAListItemEdit.prototype */ {
|
||||
/** logger used to record this.log messages, commonly set to console */
|
||||
//logger : console,
|
||||
|
||||
/** logger used to record this.log messages, commonly set to console */
|
||||
//logger : console,
|
||||
/** Override to return editable versions of the collection panels */
|
||||
_getFoldoutPanelClass: function() {
|
||||
return DC_VIEW_EDIT.CollectionViewEdit;
|
||||
},
|
||||
|
||||
/** Override to return editable versions of the collection panels */
|
||||
_getFoldoutPanelClass : function(){
|
||||
switch( this.model.get( 'collection_type' ) ){
|
||||
case 'list':
|
||||
return DC_VIEW_EDIT.ListCollectionViewEdit;
|
||||
case 'paired':
|
||||
return DC_VIEW_EDIT.PairCollectionViewEdit;
|
||||
case 'list:paired':
|
||||
return DC_VIEW_EDIT.ListOfPairsCollectionViewEdit;
|
||||
case 'list:list':
|
||||
return DC_VIEW_EDIT.ListOfListsCollectionViewEdit;
|
||||
// ......................................................................... delete
|
||||
/** In this override, add the delete button. */
|
||||
_renderPrimaryActions: function() {
|
||||
this.log(`${this}._renderPrimaryActions`);
|
||||
// render the display, edit attr and delete icon-buttons
|
||||
return _super.prototype._renderPrimaryActions.call(this).concat([this._renderDeleteButton()]);
|
||||
},
|
||||
|
||||
/** Render icon-button to delete this collection. */
|
||||
_renderDeleteButton: function() {
|
||||
var deleted = this.model.get("deleted");
|
||||
return faIconButton({
|
||||
title: deleted ? _l("Dataset collection is already deleted") : _l("Delete"),
|
||||
classes: "delete-btn",
|
||||
faIcon: "fa-times",
|
||||
disabled: deleted,
|
||||
onclick: () => {
|
||||
// ...bler... tooltips being left behind in DOM (hover out never called on deletion)
|
||||
this.$el.find(".icon-btn.delete-btn").trigger("mouseout");
|
||||
this.model["delete"]();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// ......................................................................... misc
|
||||
/** string rep */
|
||||
toString: function() {
|
||||
var modelString = this.model ? `${this.model}` : "(no model)";
|
||||
return `HDCAListItemEdit(${modelString})`;
|
||||
}
|
||||
throw new TypeError( 'Uknown collection_type: ' + this.model.get( 'collection_type' ) );
|
||||
},
|
||||
|
||||
// ......................................................................... delete
|
||||
/** In this override, add the delete button. */
|
||||
_renderPrimaryActions : function(){
|
||||
this.log( this + '._renderPrimaryActions' );
|
||||
// render the display, edit attr and delete icon-buttons
|
||||
return _super.prototype._renderPrimaryActions.call( this )
|
||||
.concat([
|
||||
this._renderDeleteButton()
|
||||
]);
|
||||
},
|
||||
|
||||
/** Render icon-button to delete this collection. */
|
||||
_renderDeleteButton : function(){
|
||||
var self = this,
|
||||
deleted = this.model.get( 'deleted' );
|
||||
return faIconButton({
|
||||
title : deleted? _l( 'Dataset collection is already deleted' ): _l( 'Delete' ),
|
||||
classes : 'delete-btn',
|
||||
faIcon : 'fa-times',
|
||||
disabled : deleted,
|
||||
onclick : function() {
|
||||
// ...bler... tooltips being left behind in DOM (hover out never called on deletion)
|
||||
self.$el.find( '.icon-btn.delete-btn' ).trigger( 'mouseout' );
|
||||
self.model[ 'delete' ]();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// ......................................................................... misc
|
||||
/** string rep */
|
||||
toString : function(){
|
||||
var modelString = ( this.model )?( this.model + '' ):( '(no model)' );
|
||||
return 'HDCAListItemEdit(' + modelString + ')';
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
//==============================================================================
|
||||
return {
|
||||
HDCAListItemEdit : HDCAListItemEdit
|
||||
};
|
||||
});
|
||||
export default {
|
||||
HDCAListItemEdit: HDCAListItemEdit
|
||||
};
|
||||
|
||||
@@ -1,108 +1,156 @@
|
||||
define([
|
||||
"mvc/dataset/states",
|
||||
"mvc/collection/collection-li",
|
||||
"mvc/collection/collection-view",
|
||||
"mvc/base-mvc",
|
||||
"utils/localization"
|
||||
], function( STATES, DC_LI, DC_VIEW, BASE_MVC, _l ){
|
||||
|
||||
'use strict';
|
||||
import STATES from "mvc/dataset/states";
|
||||
import DC_LI from "mvc/collection/collection-li";
|
||||
import DC_VIEW from "mvc/collection/collection-view";
|
||||
import BASE_MVC from "mvc/base-mvc";
|
||||
import HISTORY_ITEM_LI from "mvc/history/history-item-li";
|
||||
import _l from "utils/localization";
|
||||
|
||||
//==============================================================================
|
||||
var _super = DC_LI.DCListItemView;
|
||||
/** @class Read only view for HistoryDatasetCollectionAssociation (a dataset collection inside a history).
|
||||
*/
|
||||
var HDCAListItemView = _super.extend(
|
||||
/** @lends HDCAListItemView.prototype */{
|
||||
/** @lends HDCAListItemView.prototype */ {
|
||||
className: `${_super.prototype.className} history-content`,
|
||||
|
||||
className : _super.prototype.className + " history-content",
|
||||
/** event listeners */
|
||||
_setUpListeners: function() {
|
||||
_super.prototype._setUpListeners.call(this);
|
||||
var renderListen = (model, options) => {
|
||||
this.render();
|
||||
};
|
||||
if (this.model.jobStatesSummary) {
|
||||
this.listenTo(this.model.jobStatesSummary, "change", renderListen);
|
||||
}
|
||||
this.listenTo(this.model, {
|
||||
"change:tags change:visible change:state": renderListen
|
||||
});
|
||||
},
|
||||
|
||||
/** event listeners */
|
||||
_setUpListeners : function(){
|
||||
_super.prototype._setUpListeners.call( this );
|
||||
this.listenTo( this.model, {
|
||||
'change:tags change:populated change:visible' : function( model, options ){ this.render(); },
|
||||
});
|
||||
},
|
||||
/** Override to provide the proper collections panels as the foldout */
|
||||
_getFoldoutPanelClass: function() {
|
||||
return DC_VIEW.CollectionView;
|
||||
},
|
||||
|
||||
/** Override to provide the proper collections panels as the foldout */
|
||||
_getFoldoutPanelClass : function(){
|
||||
switch( this.model.get( 'collection_type' ) ){
|
||||
case 'list':
|
||||
return DC_VIEW.ListCollectionView;
|
||||
case 'paired':
|
||||
return DC_VIEW.PairCollectionView;
|
||||
case 'list:paired':
|
||||
return DC_VIEW.ListOfPairsCollectionView;
|
||||
case 'list:list':
|
||||
return DC_VIEW.ListOfListsCollectionView;
|
||||
/** In this override, add the state as a class for use with state-based CSS */
|
||||
_swapNewRender: function($newRender) {
|
||||
_super.prototype._swapNewRender.call(this, $newRender);
|
||||
//TODO: model currently has no state
|
||||
var state;
|
||||
var jobStatesSummary = this.model.jobStatesSummary;
|
||||
if (jobStatesSummary) {
|
||||
if (jobStatesSummary.new()) {
|
||||
state = "loading";
|
||||
} else if (jobStatesSummary.errored()) {
|
||||
state = "error";
|
||||
} else if (jobStatesSummary.terminal()) {
|
||||
state = "ok";
|
||||
} else if (jobStatesSummary.running()) {
|
||||
state = "running";
|
||||
} else {
|
||||
state = "queued";
|
||||
}
|
||||
} else if (this.model.get("job_source_id")) {
|
||||
// Initial rendering - polling will fill in more details in a bit.
|
||||
state = "loading";
|
||||
} else {
|
||||
state = this.model.get("populated_state") ? STATES.OK : STATES.RUNNING;
|
||||
}
|
||||
this.$el.addClass(`state-${state}`);
|
||||
var stateDescription = this.stateDescription();
|
||||
this.$(".state-description").html(stateDescription);
|
||||
return this.$el;
|
||||
},
|
||||
|
||||
stateDescription: function() {
|
||||
var collection = this.model;
|
||||
var elementCount = collection.get("element_count");
|
||||
var jobStateSource = collection.get("job_source_type");
|
||||
var collectionType = this.model.get("collection_type");
|
||||
var collectionTypeDescription = DC_VIEW.collectionTypeDescription(collection);
|
||||
var simpleDescription = DC_VIEW.collectionDescription(collection);
|
||||
var jobStatesSummary = collection.jobStatesSummary;
|
||||
if (!jobStateSource || jobStateSource == "Job") {
|
||||
return simpleDescription;
|
||||
} else if (!jobStatesSummary || !jobStatesSummary.hasDetails()) {
|
||||
return `
|
||||
<div class="progress state-progress">
|
||||
<span class="note">Loading job data for ${
|
||||
collectionTypeDescription
|
||||
}.<span class="blinking">..</span></span>
|
||||
<div class="progress-bar info" style="width:100%">
|
||||
</div>`;
|
||||
} else {
|
||||
var isNew = jobStatesSummary.new();
|
||||
var jobCount = isNew ? null : jobStatesSummary.jobCount();
|
||||
if (isNew) {
|
||||
return `
|
||||
<div class="progress state-progress">
|
||||
<span class="note">Creating jobs.<span class="blinking">..</span></span>
|
||||
<div class="progress-bar info" style="width:100%">
|
||||
</div>`;
|
||||
} else if (jobStatesSummary.errored()) {
|
||||
var errorCount = jobStatesSummary.numInError();
|
||||
return `a ${collectionTypeDescription} with ${errorCount} / ${jobCount} jobs in error`;
|
||||
} else if (jobStatesSummary.terminal()) {
|
||||
return simpleDescription;
|
||||
} else {
|
||||
var running = jobStatesSummary.states()["running"] || 0;
|
||||
var ok = jobStatesSummary.states()["ok"] || 0;
|
||||
var okPercent = ok / (jobCount * 1.0);
|
||||
var runningPercent = running / (jobCount * 1.0);
|
||||
var otherPercent = 1.0 - okPercent - runningPercent;
|
||||
var jobsStr = jobCount && jobCount > 1 ? `${jobCount} jobs` : `a job`;
|
||||
return `
|
||||
<div class="progress state-progress">
|
||||
<span class="note">${jobsStr} generating a ${collectionTypeDescription}</span>
|
||||
<div class="progress-bar ok" style="width:${okPercent * 100.0}%"></div>
|
||||
<div class="progress-bar running" style="width:${runningPercent * 100.0}%"></div>
|
||||
<div class="progress-bar new" style="width:${otherPercent * 100.0}%">
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// ......................................................................... misc
|
||||
/** String representation */
|
||||
toString: function() {
|
||||
var modelString = this.model ? `${this.model}` : "(no model)";
|
||||
return `HDCAListItemView(${modelString})`;
|
||||
}
|
||||
throw new TypeError( 'Uknown collection_type: ' + this.model.get( 'collection_type' ) );
|
||||
},
|
||||
|
||||
/** In this override, add the state as a class for use with state-based CSS */
|
||||
_swapNewRender : function( $newRender ){
|
||||
_super.prototype._swapNewRender.call( this, $newRender );
|
||||
//TODO: model currently has no state
|
||||
var state = !this.model.get( 'populated' ) ? STATES.RUNNING : STATES.OK;
|
||||
//if( this.model.has( 'state' ) ){
|
||||
this.$el.addClass( 'state-' + state );
|
||||
//}
|
||||
return this.$el;
|
||||
},
|
||||
|
||||
// ......................................................................... misc
|
||||
/** String representation */
|
||||
toString : function(){
|
||||
var modelString = ( this.model )?( this.model + '' ):( '(no model)' );
|
||||
return 'HDCAListItemView(' + modelString + ')';
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
/** underscore templates */
|
||||
HDCAListItemView.prototype.templates = (function(){
|
||||
|
||||
var warnings = _.extend( {}, _super.prototype.templates.warnings, {
|
||||
hidden : BASE_MVC.wrapTemplate([
|
||||
// add a warning when hidden
|
||||
'<% if( !collection.visible ){ %>',
|
||||
'<div class="hidden-msg warningmessagesmall">',
|
||||
_l( 'This collection has been hidden' ),
|
||||
'</div>',
|
||||
'<% } %>'
|
||||
], 'collection' )
|
||||
HDCAListItemView.prototype.templates = (() => {
|
||||
var warnings = _.extend({}, _super.prototype.templates.warnings, {
|
||||
hidden: collection => {
|
||||
collection.visible
|
||||
? ""
|
||||
: `<div class="hidden-msg warningmessagesmall">${_l("This collection has been hidden")}</div>`;
|
||||
}
|
||||
});
|
||||
|
||||
// could steal this from hda-base (or use mixed content)
|
||||
var titleBarTemplate = BASE_MVC.wrapTemplate([
|
||||
// adding the hid display to the title
|
||||
'<div class="title-bar clear" tabindex="0">',
|
||||
'<span class="state-icon"></span>',
|
||||
'<div class="title">',
|
||||
//TODO: remove whitespace and use margin-right
|
||||
'<span class="hid"><%- collection.hid %></span> ',
|
||||
'<span class="name"><%- collection.name %></span>',
|
||||
'</div>',
|
||||
'<div class="subtitle"></div>',
|
||||
'<span class="nametags">',
|
||||
'<% _.each(_.sortBy(_.uniq(collection.tags), function(x) { return x }), function(tag){ %>',
|
||||
'<% if (tag.indexOf("name:") == 0){ %>',
|
||||
'<span class="label label-info"><%- tag.slice(5) %></span>',
|
||||
'<% } %>',
|
||||
'<% }); %>',
|
||||
'</span>',
|
||||
'</div>'
|
||||
], 'collection' );
|
||||
var titleBarTemplate = collection => `
|
||||
<div class="title-bar clear" tabindex="0">
|
||||
<span class="state-icon"></span>
|
||||
<div class="title">
|
||||
<span class="hid">${collection.hid}</span>
|
||||
<span class="name">${_.escape(collection.name)}</span>
|
||||
</div>
|
||||
<div class="state-description">
|
||||
</div>
|
||||
${HISTORY_ITEM_LI.nametagTemplate(collection)}
|
||||
</div>
|
||||
`;
|
||||
|
||||
return _.extend( {}, _super.prototype.templates, {
|
||||
warnings : warnings,
|
||||
titleBar : titleBarTemplate
|
||||
return _.extend({}, _super.prototype.templates, {
|
||||
warnings: warnings,
|
||||
titleBar: titleBarTemplate
|
||||
});
|
||||
}());
|
||||
|
||||
})();
|
||||
|
||||
//==============================================================================
|
||||
return {
|
||||
HDCAListItemView : HDCAListItemView
|
||||
};
|
||||
});
|
||||
export default {
|
||||
HDCAListItemView: HDCAListItemView
|
||||
};
|
||||
|
||||
@@ -1,131 +1,46 @@
|
||||
define([
|
||||
"mvc/collection/collection-model",
|
||||
"mvc/history/history-content-model",
|
||||
"utils/localization"
|
||||
], function( DC_MODEL, HISTORY_CONTENT, _l ){
|
||||
|
||||
'use strict';
|
||||
import DC_MODEL from "mvc/collection/collection-model";
|
||||
import HISTORY_CONTENT from "mvc/history/history-content-model";
|
||||
import _l from "utils/localization";
|
||||
|
||||
/*==============================================================================
|
||||
|
||||
Models for DatasetCollections contained within a history.
|
||||
|
||||
TODO:
|
||||
these might be compactable to one class if some duplication with
|
||||
collection-model is used.
|
||||
|
||||
==============================================================================*/
|
||||
var hcontentMixin = HISTORY_CONTENT.HistoryContentMixin,
|
||||
ListDC = DC_MODEL.ListDatasetCollection,
|
||||
PairDC = DC_MODEL.PairDatasetCollection,
|
||||
ListPairedDC = DC_MODEL.ListPairedDatasetCollection,
|
||||
ListOfListsDC = DC_MODEL.ListOfListsDatasetCollection;
|
||||
|
||||
//==============================================================================
|
||||
/** Override to post to contents route w/o id. */
|
||||
function buildHDCASave( _super ){
|
||||
return function _save( attributes, options ){
|
||||
if( this.isNew() ){
|
||||
options = options || {};
|
||||
options.url = this.urlRoot + this.get( 'history_id' ) + '/contents';
|
||||
attributes = attributes || {};
|
||||
attributes.type = 'dataset_collection';
|
||||
}
|
||||
return _super.call( this, attributes, options );
|
||||
};
|
||||
}
|
||||
var hcontentMixin = HISTORY_CONTENT.HistoryContentMixin;
|
||||
|
||||
var DC = DC_MODEL.DatasetCollection;
|
||||
|
||||
//==============================================================================
|
||||
/** @class Backbone model for List Dataset Collection within a History.
|
||||
*/
|
||||
var HistoryListDatasetCollection = ListDC.extend( hcontentMixin ).extend(
|
||||
/** @lends HistoryListDatasetCollection.prototype */{
|
||||
var HistoryDatasetCollection = DC.extend(hcontentMixin).extend(
|
||||
/** @lends HistoryDatasetCollection.prototype */ {
|
||||
defaults: _.extend(_.clone(DC.prototype.defaults), {
|
||||
history_content_type: "dataset_collection",
|
||||
model_class: "HistoryDatasetCollectionAssociation"
|
||||
}),
|
||||
|
||||
defaults : _.extend( _.clone( ListDC.prototype.defaults ), {
|
||||
history_content_type: 'dataset_collection',
|
||||
collection_type : 'list',
|
||||
model_class : 'HistoryDatasetCollectionAssociation'
|
||||
}),
|
||||
//==============================================================================
|
||||
/** Override to post to contents route w/o id. */
|
||||
save: function(attributes, options) {
|
||||
if (this.isNew()) {
|
||||
options = options || {};
|
||||
options.url = `${this.urlRoot + this.get("history_id")}/contents`;
|
||||
attributes = attributes || {};
|
||||
attributes.type = "dataset_collection";
|
||||
}
|
||||
return DC.prototype.save.call(this, attributes, options);
|
||||
},
|
||||
|
||||
/** Override to post to contents route w/o id. */
|
||||
save : buildHDCASave( ListDC.prototype.save ),
|
||||
|
||||
/** String representation. */
|
||||
toString : function(){
|
||||
return 'History' + ListDC.prototype.toString.call( this );
|
||||
/** String representation. */
|
||||
toString: function() {
|
||||
return `History${DC.prototype.toString.call(this)}`;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
);
|
||||
|
||||
//==============================================================================
|
||||
/** @class Backbone model for Pair Dataset Collection within a History.
|
||||
* @constructs
|
||||
*/
|
||||
var HistoryPairDatasetCollection = PairDC.extend( hcontentMixin ).extend(
|
||||
/** @lends HistoryPairDatasetCollection.prototype */{
|
||||
|
||||
defaults : _.extend( _.clone( PairDC.prototype.defaults ), {
|
||||
history_content_type: 'dataset_collection',
|
||||
collection_type : 'paired',
|
||||
model_class : 'HistoryDatasetCollectionAssociation'
|
||||
}),
|
||||
|
||||
/** Override to post to contents route w/o id. */
|
||||
save : buildHDCASave( PairDC.prototype.save ),
|
||||
|
||||
/** String representation. */
|
||||
toString : function(){
|
||||
return 'History' + PairDC.prototype.toString.call( this );
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** @class Backbone model for List of Pairs Dataset Collection within a History. */
|
||||
var HistoryListPairedDatasetCollection = ListPairedDC.extend( hcontentMixin ).extend({
|
||||
|
||||
defaults : _.extend( _.clone( ListPairedDC.prototype.defaults ), {
|
||||
history_content_type: 'dataset_collection',
|
||||
collection_type : 'list:paired',
|
||||
model_class : 'HistoryDatasetCollectionAssociation'
|
||||
}),
|
||||
|
||||
/** Override to post to contents route w/o id. */
|
||||
save : buildHDCASave( ListPairedDC.prototype.save ),
|
||||
|
||||
/** String representation. */
|
||||
toString : function(){
|
||||
return 'History' + ListPairedDC.prototype.toString.call( this );
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** @class Backbone model for List of Lists Dataset Collection within a History. */
|
||||
var HistoryListOfListsDatasetCollection = ListOfListsDC.extend( hcontentMixin ).extend({
|
||||
|
||||
defaults : _.extend( _.clone( ListOfListsDC.prototype.defaults ), {
|
||||
history_content_type: 'dataset_collection',
|
||||
collection_type : 'list:list',
|
||||
model_class : 'HistoryDatasetCollectionAssociation'
|
||||
}),
|
||||
|
||||
/** Override to post to contents route w/o id. */
|
||||
save : buildHDCASave( ListOfListsDC.prototype.save ),
|
||||
|
||||
/** String representation. */
|
||||
toString : function(){
|
||||
return ([ 'HistoryListOfListsDatasetCollection(', this.get( 'name' ), ')' ].join( '' ));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
//==============================================================================
|
||||
return {
|
||||
HistoryListDatasetCollection : HistoryListDatasetCollection,
|
||||
HistoryPairDatasetCollection : HistoryPairDatasetCollection,
|
||||
HistoryListPairedDatasetCollection : HistoryListPairedDatasetCollection,
|
||||
HistoryListOfListsDatasetCollection : HistoryListOfListsDatasetCollection
|
||||
};
|
||||
});
|
||||
export default {
|
||||
HistoryDatasetCollection: HistoryDatasetCollection
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user