diff --git a/.gitignore b/.gitignore index e9876d10..200420c9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.DS_Store .ropeproject node_modules bower_components @@ -39,4 +40,13 @@ nosetests.xml coverage.xml # Sphinx documentation -docs/_build/ \ No newline at end of file +docs/_build/ + +# Ignore xml files from harvest and any logs +ckanext/qdes_schema/qspatial_harvest/xml_files/*.xml +ckanext/qdes_schema/qspatial_harvest/json_files/ +ckanext/qdes_schema/qspatial_harvest/logs/ +ckanext/qdes_schema/qld_harvest/json_files/ +ckanext/qdes_schema/qld_harvest/logs/ +ckanext/qdes_schema/qld_harvest/json_files_data_qld + diff --git a/ckanext/qdes_schema/auth.py b/ckanext/qdes_schema/auth.py new file mode 100644 index 00000000..67cc8edc --- /dev/null +++ b/ckanext/qdes_schema/auth.py @@ -0,0 +1,75 @@ +import ckan.authz as authz +import ckan.plugins.toolkit as toolkit +import logging + +from ckan.logic.auth import get_package_object + +_ = toolkit._ +log = logging.getLogger(__name__) + + +def user_can_manage_dataservices(next_auth, context, data_dict=None): + # Use the context['user'] as per CKAN core logic/auth/create.py -> package_create + user = context['user'] + + if not data_dict or not isinstance(data_dict, dict): + data_dict = {} + + # We only want our auth function to take effect if we are dealing with a data service + # and the user is not an admin of some group - otherwise fall back to default CKAN + # auth function + package_type = data_dict.get('type') + + if 'dataservice' in [toolkit.get_endpoint()[0], package_type] \ + and not authz.has_user_permission_for_some_org(user, 'admin'): + return {'success': False, 'msg': _('User not authorized to perform action')} + + return next_auth(context, data_dict) + + +@toolkit.chained_auth_function +def package_show(next_auth, context, data_dict): + result = next_auth(context, data_dict) + + if not result.get('success', False): + # Check to see if the dataset is private + package = get_package_object(context, data_dict) + if package and package.private: + # Allow access for the related datasets tab to view the private dataset + if toolkit.get_endpoint() == ('qdes_schema', 'related_datasets'): + return {'success': True} + # Make sure the call is coming from dataset view instead of api view + elif toolkit.get_endpoint()[0] == 'dataset': + toolkit.abort(403, 'Record not accessible') + + # return default ckan package_show auth result + return result + + +@toolkit.chained_auth_function +def package_create(next_auth, context, data_dict): + return user_can_manage_dataservices(next_auth, context, data_dict) + + +@toolkit.chained_auth_function +def package_update(next_auth, context, data_dict): + return user_can_manage_dataservices(next_auth, context, data_dict) + + +@toolkit.chained_auth_function +def package_patch(next_auth, context, data_dict): + return user_can_manage_dataservices(next_auth, context, data_dict) + + +@toolkit.chained_auth_function +def package_delete(next_auth, context, data_dict): + return user_can_manage_dataservices(next_auth, context, data_dict) + + +def dataservice_index(context, data_dict): + # Use the context['user'] as per CKAN core logic/auth/create.py -> package_create + user = context['user'] + + if not authz.has_user_permission_for_some_org(user, 'admin'): + return {'success': False, 'msg': _('User not authorized to perform action')} + return {'success': True} diff --git a/ckanext/qdes_schema/blueprint.py b/ckanext/qdes_schema/blueprint.py new file mode 100644 index 00000000..3d922701 --- /dev/null +++ b/ckanext/qdes_schema/blueprint.py @@ -0,0 +1,416 @@ +import ckan.lib.base as base +import ckan.lib.navl.dictization_functions as dict_fns +import ckan.logic as logic +import ckan.plugins.toolkit as toolkit +import ckan.model as model +import ckanext.qdes_schema.constants as constants +import ckanext.qdes_schema.jobs as jobs +import logging +import six +import json +import xml.dom.minidom +import os + +from ckan.common import _, c, request +from ckanext.qdes_schema import helpers +from flask import Blueprint +from pprint import pformat +from ckanext.qdes_schema.logic.helpers import dataservice_helpers as dataservice_helpers +from flask import send_file + +abort = toolkit.abort +get_action = toolkit.get_action +log = logging.getLogger(__name__) +NotFound = toolkit.ObjectNotFound +NotAuthorized = toolkit.NotAuthorized +ValidationError = toolkit.ValidationError +render = toolkit.render +h = toolkit.h +clean_dict = logic.clean_dict +tuplize_dict = logic.tuplize_dict +parse_params = logic.parse_params + +qdes_schema = Blueprint('qdes_schema', __name__) + + +def related_datasets(id_or_name): + try: + context = { + u'model': model, + u'user': toolkit.g.user, + u'auth_user_obj': toolkit.g.userobj + } + + related = [] + + pkg_dict = get_action('package_show')({}, {'id': id_or_name}) + + all_relationships = helpers.get_all_relationships(pkg_dict['id']) + + for relationship in all_relationships: + # Check for access, don't show to user if user has no permission + # Example, non logged-in user should not see delete package. + try: + # Only do check_access on internal datasets not external + # Internal datasets will have a pkg_id where external datasets do not + if relationship.get('pkg_id'): + toolkit.check_access('package_show', context, {'id': relationship.get('pkg_id')}) + related.append(relationship) + except (NotFound, NotAuthorized, ValidationError) as e: + # Let's continue to the next list. + log.warning(f"related_datasets: Relationship dataset {relationship.get('pkg_id')} access check error = {e}") + pass + + extra_vars = { + 'pkg_dict': pkg_dict, + 'related': related + } + + return render('package/related_datasets.html', extra_vars=extra_vars) + except (NotFound, NotAuthorized): + abort(404, _('Related dataset not found')) + + +def dataset_metadata(id): + try: + extra_vars = {} + extra_vars['pkg_dict'] = get_action('package_show')({}, {'id': id}) + + # Load existing relationship. + relationships = h.get_subject_package_relationship_objects(extra_vars['pkg_dict'].get('id')) + if relationships: + extra_vars['pkg_dict']['related_resources'] = h.convert_relationships_to_related_resources(relationships) + + return render('package/metadata.html', extra_vars=extra_vars) + except (NotFound, NotAuthorized): + abort(404, _('Dataset metadata not found')) + + +def resource_metadata(id, resource_id): + try: + extra_vars = {} + extra_vars['pkg_dict'] = get_action('package_show')({}, {'id': id}) + extra_vars['package'] = extra_vars['pkg_dict'] + extra_vars['resource'] = [] + extra_vars['current_resource_view'] = None + + for resource in extra_vars['package'].get('resources', []): + if resource['id'] == resource_id: + extra_vars['resource'] = resource + break + if not extra_vars['resource']: + abort(404, _('Resource not found')) + + return render('scheming/package/resource_metadata.html', extra_vars=extra_vars) + except (NotFound, NotAuthorized): + abort(404, _('Resource metadata not found')) + + +def datasets_available(id): + try: + dataservice = get_action('package_show')({}, {'id': id}) + extra_vars = {} + extra_vars['pkg_dict'] = dataservice + datasets_available = [] + for dataset_id in dataservice_helpers.datasets_available_as_list(dataservice): + try: + dataset = get_action('package_show')({}, {'id': dataset_id}) + dataset_url = h.url_for('dataset.read', id=dataset_id) + dataset_title = h.get_pkg_title(dataset_id, dataset) + datasets_available.append({'title': dataset_title, 'url': dataset_url}) + except (NotFound, NotAuthorized): + # Let's continue to the next list. + pass + except Exception as e: + log.error('datasets_available - Exception loading package ID {}'.format(dataset_id)) + log.error(str(e)) + extra_vars['datasets_available'] = datasets_available + return render('package/available_datasets.html', extra_vars=extra_vars) + except (NotFound, NotAuthorized): + abort(404, _('Available datasets not found')) + + +def datasets_schema_validation(id): + # Check the user has permission to clone the dataset + context = { + 'model': model, + 'user': c.user, + 'auth_user_obj': c.userobj + } + toolkit.check_access('package_update', context, {'id': id}) + + extra_vars = {} + pkg = get_action('package_show')({}, {'id': id}) + pkg_validated = pkg.copy() + extra_vars['pkg_errors'] = [] + extra_vars['res_errors'] = [] + extra_vars['pkg_dict'] = pkg + extra_vars['data'] = [] + extra_vars['options'] = [ + {'text': 'Select publishing portal', 'value': 'none'}, + {'text': 'Opendata Portal', 'value': constants.PUBLISH_EXTERNAL_IDENTIFIER_DATA_QLD_SCHEMA}, + # {'text': 'QSpatial', 'value': constants.PUBLISH_EXTERNAL_IDENTIFIER_QSPATIAL_SCHEMA}, + # {'text': 'SIR', 'value': constants.PUBLISH_EXTERNAL_IDENTIFIER_SIR_SCHEMA} + ] + extra_vars['selected_opt'] = {} + extra_vars['valid'] = 0 + extra_vars['publication_message'] = {} + + if request.method == 'POST': + data = clean_dict(dict_fns.unflatten(tuplize_dict(parse_params( + request.form)))) + + extra_vars['data'] = data + + if not data.get('schema') == 'none': + if data.get('action') == 'validate': + extra_vars = helpers.schema_validate(extra_vars, pkg_validated, data) + elif data.get('action') == 'publish': + publish_log = helpers.schema_publish(pkg, data) + extra_vars['publication_message'] = { + 'text': 'The distribution has been queued for publishing.', + 'cls': 'alert-success' + } + if not publish_log: + extra_vars['publication_message'] = { + 'text': 'The distribution could not be queued for publishing.', + 'cls': 'alert-error' + } + + if not extra_vars['pkg_errors'] and not extra_vars['res_errors'] and not extra_vars['publication_message']: + extra_vars['valid'] = 1 + + # Load publish_log data. + extra_vars['publish_activities'] = helpers.get_publish_activities(pkg) + + # Process unpublish status. + unpublish_log_id = request.params.get('unpublish', None) + if unpublish_log_id == "0": + extra_vars['unpublish'] = 0 + elif unpublish_log_id: + extra_vars['unpublish'] = 1 if helpers.is_unpublish_pending(unpublish_log_id) else '' + + return render('package/publish_metadata.html', extra_vars=extra_vars) + + +def unpublish_external_dataset_resource(id): + # Check the user has permission to clone the dataset + context = { + 'model': model, + 'user': c.user, + 'auth_user_obj': c.userobj + } + toolkit.check_access('package_update', context, {'id': id}) + + if not request.method == 'POST': + return + + data = clean_dict(dict_fns.unflatten(tuplize_dict(parse_params( + request.form)))) + + pkg = get_action('package_show')({}, {'id': id}) + + # Create job. + resource_to_unpublish = {} + for resource in pkg.get('resources', []): + if resource.get('id') == data.get('unpublish_resource'): + resource_to_unpublish = resource + + # Add to publish log. + unpublish = 0 + try: + publish_log = get_action('create_publish_log')({}, { + 'dataset_id': pkg.get('id'), + 'resource_id': resource_to_unpublish.get('id'), + 'trigger': constants.PUBLISH_TRIGGER_MANUAL, + 'destination': data.get('schema'), + 'status': constants.PUBLISH_STATUS_PENDING, + 'action': constants.PUBLISH_ACTION_DELETE + }) + + # Add to job worker queue. + if publish_log: + # Improvements for job worker visibility when troubleshooting via logs + job_title = f'Unpublish external dataset resource: dataset_id={publish_log.dataset_id}, resource_id={publish_log.resource_id}, destination={publish_log.destination}' + toolkit.enqueue_job(jobs.unpublish_external_distribution, [publish_log.id, c.user], title=job_title) + unpublish = publish_log.id + + except Exception as e: + log.error(str(e)) + + return h.redirect_to('/dataset/{}/publish?unpublish={}'.format(id, unpublish)) + + +def _get_term_obj(field_value, vocab_service_name): + if isinstance(field_value, list): + terms = [] + for uri in field_value: + term = get_action('get_vocabulary_service_term')({}, {'vocabulary_service_name': vocab_service_name, 'term_uri': uri}) + if term: + terms.append(dict(term)) + + if terms: + field_value = terms + else: + term = get_action('get_vocabulary_service_term')({}, { + 'vocabulary_service_name': vocab_service_name, + 'term_uri': field_value + }) + + if term: + field_value = dict(term) + + return field_value + +def dataset_export(id, format): + try: + context = { + u'model': model, + u'user': toolkit.g.user, + u'auth_user_obj': toolkit.g.userobj + } + if context.get('__auth_audit', None): + context['__auth_audit'].pop() + dataset = get_action('package_show')(context, {'id': id}) + # TODO: We might need to load some vocab serivce as objects to get label etc + # if dataset['contact_publisher']: + # term = get_action('get_vocabulary_service_term')(context, {'term_uri': dataset['contact_publisher']}) + # if term: + # dataset['contact_publisher'] = term + all_relationships = helpers.get_all_relationships(dataset['id']) + relationships = [] + + for relationship in all_relationships: + if relationship.get('type') in ['Is Part Of']: + relationships.append(relationship) + + # TODO: Need to load all secure vocabs as dict objects + # Load vocabualry service contact_point + vocab_value = {} + if len(dataset.get('contact_point', '')) > 0: + secure_vocabulary_record = get_action('get_secure_vocabulary_record')( + context, {'vocabulary_name': 'point-of-contact', 'query': dataset['contact_point']}) + if secure_vocabulary_record: + vocab_value = secure_vocabulary_record + + dataset['contact_point'] = vocab_value + + vocab_value = {} + if len(dataset.get('metadata_contact_point', '')) > 0: + secure_vocabulary_record = get_action('get_secure_vocabulary_record')( + context, {'vocabulary_name': 'point-of-contact', 'query': dataset['metadata_contact_point']}) + if secure_vocabulary_record: + vocab_value = secure_vocabulary_record + + dataset['metadata_contact_point'] = vocab_value + + # Get the identifiers + dataset['additional_info'] = h.get_multi_textarea_values(dataset.get('additional_info', [])) + dataset['identifiers'] = h.get_multi_textarea_values(dataset.get('identifiers', [])) + dataset['topic'] = h.get_multi_textarea_values(dataset.get('topic', [])) + dataset['quality_measure'] = h.get_multi_textarea_values(dataset.get('quality_measure', [])) + dataset['quality_description'] = h.get_multi_textarea_values(dataset.get('quality_description', [])) + dataset['conforms_to'] = h.get_multi_textarea_values(dataset.get('conforms_to', [])) + dataset['lineage_inputs'] = h.get_multi_textarea_values(dataset.get('lineage_inputs', [])) + dataset['lineage_sensor'] = h.get_multi_textarea_values(dataset.get('lineage_sensor', [])) + dataset['lineage_responsible_party'] = h.get_multi_textarea_values(dataset.get('lineage_responsible_party', [])) + dataset['cited_in'] = h.get_multi_textarea_values(dataset.get('cited_in', [])) + dataset['classification_and_access_restrictions'] = h.get_multi_textarea_values(dataset.get('classification_and_access_restrictions', [])) + dataset['series_or_relationships'] = relationships + + # Load schema. + single_multi_vocab_fields = [ + 'topic', + 'spatial_representation', + 'spatial_datum_crs', + 'spatial_resolution', + 'contact_publisher', + 'publication_status', + 'update_schedule', + 'classification_and_access_restrictions', + 'license_id', + ] + + group_vocab_fields = { + 'quality_measure': ['measurement'], + 'quality_description': ['dimension'], + } + + res_single_multi_vocab_fields = [ + 'format', + 'compression', + 'packaging' + ] + + schema = h.scheming_get_dataset_schema(dataset.get('type')) + for field in schema.get('dataset_fields', {}): + if group_vocab_fields.get(field.get('field_name'), None): + group = group_vocab_fields.get(field.get('field_name')) + values = [] + for item in dataset.get(field.get('field_name')): + for group_vocab_field in group: + if item and item.get(group_vocab_field, None): + for field_group in field.get('field_group', {}): + if field_group.get('field_name') == group_vocab_field: + group_field_value = item.get(group_vocab_field, {}) + group_field_vocab_name = field_group.get('vocabulary_service_name') + item[group_vocab_field] = _get_term_obj(group_field_value, group_field_vocab_name) + values.append(item) + + if field.get('vocabulary_service_name'): + if field.get('field_name') in single_multi_vocab_fields: + dataset[field.get('field_name')] = _get_term_obj(dataset.get(field.get('field_name')), field.get('vocabulary_service_name')) + + new_resources = [] + for res in dataset.get('resources'): + for field in schema.get('resource_fields', {}): + if field.get('field_name') in res_single_multi_vocab_fields: + res[field.get('field_name')] = _get_term_obj(res.get(field.get('field_name')), field.get('vocabulary_service_name')) + + dataservices = [] + for id in h.get_multi_textarea_values(res.get('data_services', [])): + if id: + dataservices.append(get_action('package_show')(context, {'id': id})) + + res['data_services'] = dataservices + + new_resources.append(res) + + if new_resources: + dataset['resources'] = new_resources + + + extra_vars = {} + extra_vars['dataset'] = dataset + data = None + if format == 'XML (ISO-19139)': + data = render(f'package/export/{format}.xml.j2', extra_vars=extra_vars) + else: + abort(400, _('Invalid export format')) + + if data: + # Pretty print. + dom = xml.dom.minidom.parseString(data) + pretty_xml_as_string = dom.toprettyxml() + + # Remove weird whitespace. + pretty_xml = os.linesep.join([s for s in pretty_xml_as_string.splitlines() if s.strip()]) + + return send_file(six.BytesIO(pretty_xml.encode('utf8')), + as_attachment=True, + attachment_filename=f'{dataset.get("title")}.xml') + except (NotFound, NotAuthorized): + abort(404, _('Dataset not found')) + + +qdes_schema.add_url_rule(u'/dataset//related-datasets', view_func=related_datasets) +qdes_schema.add_url_rule(u'/dataset//metadata', view_func=dataset_metadata) +qdes_schema.add_url_rule(u'/dataservice//metadata', endpoint='dataservice_metadata', view_func=dataset_metadata) +qdes_schema.add_url_rule(u'/dataset//resource//metadata', view_func=resource_metadata) +qdes_schema.add_url_rule(u'/dataservice//datasets-available', view_func=datasets_available) +qdes_schema.add_url_rule(u'/dataset//publish', methods=[u'GET', u'POST'], + view_func=datasets_schema_validation) +qdes_schema.add_url_rule(u'/dataset//unpublish-external', methods=[u'POST'], + view_func=unpublish_external_dataset_resource) +qdes_schema.add_url_rule(u'/dataset//export/', + view_func=dataset_export) diff --git a/ckanext/qdes_schema/cli.py b/ckanext/qdes_schema/cli.py new file mode 100644 index 00000000..d155ed6c --- /dev/null +++ b/ckanext/qdes_schema/cli.py @@ -0,0 +1,26 @@ +import ckan.plugins.toolkit as toolkit +import click +import logging + +from ckanext.qdes_schema import model +from pprint import pformat + +log = logging.getLogger(__name__) + + +@click.command(u"publish-log-init-db") +def init_db_cmd(): + u""" + Initialise the database tables required for publish_log + """ + click.secho(u"Initializing publish_log table", fg=u"green") + + try: + model.publish_log_table.create() + click.secho(u"Table publish_log is setup", fg=u"green") + except Exception as e: + log.error(str(e)) + + +def get_commands(): + return [init_db_cmd] diff --git a/ckanext/qdes_schema/constants.py b/ckanext/qdes_schema/constants.py new file mode 100644 index 00000000..dc3be56d --- /dev/null +++ b/ckanext/qdes_schema/constants.py @@ -0,0 +1,63 @@ +PUBLISH_EXTERNAL_IDENTIFIER_DATA_QLD_SCHEMA = 'dataqld_dataset' +PUBLISH_EXTERNAL_IDENTIFIER_QSPATIAL_SCHEMA = 'qspatial_dataset' +PUBLISH_EXTERNAL_IDENTIFIER_SIR_SCHEMA = 'sir_dataset' +PUBLISH_TRIGGER_MANUAL = 'manual' +PUBLISH_TRIGGER_AUTOMATED = 'automated' +PUBLISH_STATUS_PENDING = 'pending' +PUBLISH_STATUS_SUCCESS = 'success' +PUBLISH_STATUS_FAILED = 'failed' +PUBLISH_STATUS_VALIDATION_ERROR = 'validation_error' +PUBLISH_STATUS_VALIDATION_SUCCESS = 'validation_success' +PUBLISH_ACTION_CREATE = 'create' +PUBLISH_ACTION_UPDATE = 'update' +PUBLISH_ACTION_DELETE = 'delete' +PUBLISH_LOG_PENDING = 'Pending' +PUBLISH_LOG_PUBLISHED = 'Published' +PUBLISH_LOG_UNPUBLISHED = 'Unpublished' +PUBLISH_LOG_UNPUBLISH_ERROR = 'Unpublish error' +PUBLISH_LOG_PUBLISH_ERROR = 'Publish error' +PUBLISH_LOG_VALIDATION_ERROR = 'Validation error' +PUBLISH_LOG_NEED_REPUBLISH = 'Need republish' + +def get_key_name(schema): + if schema == PUBLISH_EXTERNAL_IDENTIFIER_DATA_QLD_SCHEMA: + return 'DATA_QLD_API_KEY' + elif schema == PUBLISH_EXTERNAL_IDENTIFIER_QSPATIAL_SCHEMA: + return 'QSPATIAL_API_KEY' + elif schema == PUBLISH_EXTERNAL_IDENTIFIER_SIR_SCHEMA: + return 'SIR_API_KEY' + + return '' + + +def get_owner_org(schema): + if schema == PUBLISH_EXTERNAL_IDENTIFIER_DATA_QLD_SCHEMA: + return 'DATA_QLD_OWNER_ORG' + elif schema == PUBLISH_EXTERNAL_IDENTIFIER_QSPATIAL_SCHEMA: + return 'QSPATIAL_OWNER_ORG' + elif schema == PUBLISH_EXTERNAL_IDENTIFIER_SIR_SCHEMA: + return 'SIR_OWNER_ORG' + + return '' + + +def get_external_schema_url(schema): + if schema == PUBLISH_EXTERNAL_IDENTIFIER_DATA_QLD_SCHEMA: + return 'DATA_QLD_URL' + elif schema == PUBLISH_EXTERNAL_IDENTIFIER_QSPATIAL_SCHEMA: + return 'QSPATIAL_URL' + elif schema == PUBLISH_EXTERNAL_IDENTIFIER_SIR_SCHEMA: + return 'SIR_URL' + + return '' + + +def get_dataservice_id(schema): + if schema == PUBLISH_EXTERNAL_IDENTIFIER_DATA_QLD_SCHEMA: + return 'ckanext.qdes_schema.publishing_portals.opendata' + elif schema == PUBLISH_EXTERNAL_IDENTIFIER_QSPATIAL_SCHEMA: + return 'ckanext.qdes_schema.publishing_portals.qspatial' + elif schema == PUBLISH_EXTERNAL_IDENTIFIER_SIR_SCHEMA: + return 'ckanext.qdes_schema.publishing_portals.sir' + + return '' diff --git a/ckanext/qdes_schema/dataqld_dataset.json b/ckanext/qdes_schema/dataqld_dataset.json new file mode 100644 index 00000000..5c80ab8f --- /dev/null +++ b/ckanext/qdes_schema/dataqld_dataset.json @@ -0,0 +1,762 @@ +{ + "scheming_version": 1, + "dataset_type": "dataqld_dataset", + "about": "A reimplementation of the default CKAN dataset schema", + "about_url": "http://github.com/ckan/ckanext-scheming", + "dataset_fields": [ + { + "field_name": "title", + "label": "Title", + "preset": "title", + "form_placeholder": "eg. A descriptive title", + "display_group": "general", + "required": true, + "validators": "scheming_required" + }, + { + "field_name": "name", + "label": "URL", + "preset": "dataset_slug", + "form_placeholder": "eg. my-dataset", + "display_group": "general", + "required": true + }, + { + "field_name": "identifiers", + "label": "Identifier", + "display_property": "dcterms:identifier", + "display_snippet": "qdes_multi_text.html", + "form_snippet": "qdes_multi_text.html", + "form_placeholder": "doi:10.25901/5e3ba30f141b7", + "display_group": "general" + }, + { + "field_name": "notes", + "label": "Description or abstract", + "form_snippet": "markdown.html", + "display_snippet": "markdown.html", + "form_placeholder": "eg. Some useful notes about the data", + "display_property": "dcterms:description", + "display_group": "description", + "required": true + }, + { + "field_name": "classification", + "label": "General classification of dataset type", + "display_property": "dcterms:type", + "form_placeholder": "eg. 'image', 'map', 'section', 'gridded data', etc.", + "display_group": "description", + "preset": "controlled_vocabulary_multi_select", + "vocabulary_service_name": "classification", + "validators": "scheming_required", + "form_attrs": { + "class": "form-control", + "data-module-title": "title" + }, + "form_include_blank_choice": true + }, + { + "field_name": "purpose", + "label": "Purpose", + "form_snippet": "markdown.html", + "display_snippet": "markdown.html", + "display_property": "qdcat:purpose", + "form_placeholder": "This map provides supporting information for assessments under the Vegetation Management Act.", + "display_group": "description" + }, + { + "field_name": "additional_info", + "label": "Additional information", + "form_snippet": "qdes_multi_markdown.html", + "display_property": "rdfs:comment", + "display_snippet": "qdes_multi_markdown.html", + "form_placeholder": "e.g. warnings", + "display_group": "description" + }, + { + "field_name": "metadata_review_date", + "label": "Metadata review date", + "preset": "qdes_metadata_review_date", + "display_group": "description" + }, + { + "field_name": "metadata_contact_point", + "label": "Metadata point of contact", + "display_property": "dcat:contactPoint", + "preset": "qdes_secure_vocab_service_autocomplete", + "vocabulary_service_name": "point-of-contact", + "display_group": "description", + "form_include_blank_choice": true + }, + { + "field_name": "url", + "label": "Source of record", + "form_placeholder": "http://example.com/dataset.json", + "display_property": "foaf:homepage", + "display_snippet": "link.html", + "display_group": "description", + "validators": "ignore_missing url_validator qdes_uri_validator" + }, + { + "field_name": "dataset_language", + "label": "Dataset language", + "display_property": "dcterms:language", + "display_group": "description", + "preset": "controlled_vocabulary_multi_select", + "vocabulary_service_name": "dataset_language", + "form_attrs": { + "data-module": "qdes_autocomplete", + "data-module-title": "title" + }, + "first_choice": "English", + "form_include_blank_choice": true + }, + { + "field_name": "topic", + "label": "Topic or theme", + "display_property": "dcat:theme", + "display_group": "description", + "preset": "controlled_vocabulary_multi_select", + "vocabulary_service_name": "topic", + "validators": "scheming_required", + "form_attrs": { + "data-module": "qdes_autocomplete", + "data-module-title": "title" + }, + "form_include_blank_choice": true + }, + { + "field_name": "tag_string", + "label": "Keyword", + "preset": "tag_string_autocomplete", + "form_placeholder": "eg. economy, mental health, government", + "display_group": "description" + }, + { + "field_name": "parameter", + "label": "Parameter", + "display_property": "dcat:property", + "display_group": "description", + "preset": "controlled_vocabulary_multi_group", + "field_group": [ + { + "field_name": "observed-property", + "label": "Observed property", + "form_snippet": "select.html", + "display_property": "qudt:hasQuantityKind", + "vocabulary_service_name": "observed-property", + "choices_helper": "scheming_vocabulary_service_choices", + "form_attrs": { + "data-module": "qdes_autocomplete", + "data-module-title": "title" + }, + "form_include_blank_choice": true + }, + { + "field_name": "unit-of-measure", + "label": "Unit of measure", + "form_snippet": "select.html", + "display_property": "qudt:unit", + "vocabulary_service_name": "unit-of-measure", + "choices_helper": "scheming_vocabulary_service_choices", + "form_attrs": { + "data-module": "qdes_autocomplete", + "data-module-title": "title" + }, + "form_include_blank_choice": true + } + ] + }, + { + "field_name": "temporal_start", + "label": "Temporal coverage start", + "display_property": "dcat:startDate", + "preset": "date", + "validators": "qdes_temporal_start_end_date", + "display_group": "temporal content", + "sub_heading": "temporal coverage" + }, + { + "field_name": "temporal_end", + "label": "Temporal coverage end", + "display_property": "dcat:endDate", + "preset": "date", + "validators": "ignore_missing qdes_temporal_start_end_date", + "display_group": "temporal content", + "sub_heading": "temporal coverage" + }, + { + "field_name": "temporal_precision_spacing", + "label": "Time precision or spacing", + "preset": "iso_8601_durations", + "display_group": "temporal content" + }, + { + "field_name": "temporal_resolution_range", + "label": "Temporal resolution range", + "display_group": "temporal content", + "display_property": "tempqdcat:temporalRange", + "preset": "controlled_vocabulary_single_select", + "vocabulary_service_name": "temporal_resolution_ranges", + "form_attrs": { + "data-module-title": "title" + } + }, + { + "field_name": "spatial_name_code", + "label": "Name or code", + "preset": "controlled_vocabulary_single_select", + "vocabulary_service_name": "spatial_name_code", + "form_attrs": { + "data-module": "qdes_autocomplete", + "data-module-title": "title" + }, + "display_property": "dcterms:spatial", + "display_group": "spatial content", + "sub_heading": "spatial coverage" + }, + { + "field_name": "spatial_lower_left", + "label": "Lower left", + "form_snippet": "textarea.html", + "validators": "ignore_missing qdes_spatial_points_pair qdes_validate_geojson_point qdes_within_au_bounding_box", + "display_property": "qdcat:lowerLeft", + "display_group": "spatial content", + "sub_heading": "spatial coverage" + }, + { + "field_name": "spatial_upper_right", + "label": "Upper right", + "form_snippet": "textarea.html", + "validators": "ignore_missing qdes_spatial_points_pair qdes_validate_geojson_point qdes_within_au_bounding_box", + "display_property": "qdcat:upperRight", + "display_group": "spatial content", + "sub_heading": "spatial coverage" + }, + { + "field_name": "spatial_centroid", + "label": "Centroid", + "form_snippet": "textarea.html", + "validators": "ignore_missing qdes_validate_geojson_point qdes_within_au_bounding_box", + "display_property": "dcat:centroid", + "display_group": "spatial content", + "sub_heading": "spatial coverage" + }, + { + "field_name": "spatial_geometry", + "label": "Geometry", + "form_snippet": "textarea.html", + "validators": "ignore_missing qdes_validate_geojson_polygon", + "display_property": "qdcat:asGeoJSON", + "display_group": "spatial content", + "sub_heading": "spatial coverage" + }, + { + "field_name": "spatial_content_resolution", + "label": "Spatial resolution in meters", + "validators": "ignore_missing qdes_validate_decimal", + "display_property": "dcat:spatialResolutionInMeters", + "display_group": "spatial content", + "sub_heading": "spatial coverage" + }, + { + "field_name": "spatial", + "label": "Spatial", + "preset": "json_object", + "form_snippet": "qdes_hidden_json.html", + "validators": "qdes_validate_geojson_spatial", + "display_group": "spatial content", + "sub_heading": "spatial coverage" + }, + { + "field_name": "spatial_representation", + "label": "Representation", + "preset": "controlled_vocabulary_single_select", + "vocabulary_service_name": "spatial_representation", + "display_property": "qdcat:hasSpatialRepresentation", + "display_group": "spatial content", + "sub_heading": "spatial details", + "form_attrs": { + "data-module-title": "title" + } + }, + { + "field_name": "spatial_datum_crs", + "label": "Datum & CRS", + "preset": "controlled_vocabulary_single_select", + "vocabulary_service_name": "spatial_datum_crs", + "display_property": "qeox:inCRS", + "display_group": "spatial content", + "sub_heading": "spatial details", + "form_attrs": { + "data-module-title": "title" + } + }, + { + "field_name": "spatial_resolution", + "label": "Resolution", + "preset": "controlled_vocabulary_single_select", + "vocabulary_service_name": "spatial_resolution", + "display_property": "qdcat:spatialResolution", + "display_group": "spatial content", + "sub_heading": "spatial details", + "form_attrs": { + "data-module-title": "title" + } + }, + { + "field_name": "conforms_to", + "label": "Schema or standard", + "display_snippet": "qdes_multi_text_url.html", + "form_snippet": "qdes_multi_text.html", + "validators": "ignore_missing qdes_uri_validator", + "display_property": "dcterms:conformsTo", + "display_group": "data quality", + "recommended": true + }, + { + "field_name": "quality_measure", + "label": "Quality measure", + "display_group": "data quality", + "form_snippet": "qdes_multi_group.html", + "display_snippet": "qdes_multi_group.html", + "validators": "ignore_empty qdes_validate_multi_groups", + "field_group": [ + { + "field_name": "measurement", + "label": "Measurement", + "preset": "controlled_vocabulary_single_select", + "form_snippet": "select.html", + "display_snippet": "controlled_vocabulary_single_select.html", + "choices_helper": "scheming_vocabulary_service_choices", + "vocabulary_service_name": "measurement", + "form_attrs": { + "class": "form-control", + "data-module-title": "title" + } + }, + { + "field_name": "value", + "label": "Value", + "form_snippet": "text.html", + "form_attrs": { + "class": "form-control" + } + } + ] + }, + { + "field_name": "quality_description", + "label": "Quality description", + "display_group": "data quality", + "form_snippet": "qdes_multi_group.html", + "display_snippet": "qdes_multi_group.html", + "validators": "ignore_empty qdes_validate_multi_groups", + "field_group": [ + { + "field_name": "dimension", + "label": "Dimension", + "display_property": "dqv:inDimension", + "preset": "controlled_vocabulary_single_select", + "form_snippet": "select.html", + "display_snippet": "controlled_vocabulary_single_select.html", + "choices_helper": "scheming_vocabulary_service_choices", + "vocabulary_service_name": "dimension", + "form_attrs": { + "class": "form-control", + "data-module-title": "title" + } + }, + { + "field_name": "value", + "label": "Value", + "form_snippet": "text.html", + "display_property": "oa:bodyValue", + "form_attrs": { + "class": "form-control" + } + } + ] + }, + { + "field_name": "series_or_collection", + "label": "Series or collection", + "preset": "dataset_related_multi_text" + }, + { + "preset": "related_resources_multi_group", + "validators": "ignore_missing" + }, + { + "field_name": "lineage_description", + "label": "Description", + "form_snippet": "markdown.html", + "display_snippet": "markdown.html", + "display_group": "related and lineage", + "display_property": "dcterms:description", + "sub_heading": "lineage" + }, + { + "field_name": "lineage_plan", + "label": "Plan", + "display_snippet": "qdes_text_url.html", + "validators": "ignore_missing url_validator qdes_uri_validator", + "display_group": "related and lineage", + "display_property": "prov:hadPlan", + "sub_heading": "lineage" + }, + { + "field_name": "lineage_inputs", + "label": "Inputs", + "display_snippet": "qdes_multi_text_url.html", + "form_snippet": "qdes_multi_text.html", + "validators": "ignore_missing qdes_uri_validator", + "display_group": "related and lineage", + "display_property": "prov:used", + "sub_heading": "lineage" + }, + { + "field_name": "lineage_sensor", + "label": "Sensor", + "display_snippet": "qdes_multi_text_url.html", + "form_snippet": "qdes_multi_text.html", + "validators": "ignore_missing qdes_uri_validator", + "display_group": "related and lineage", + "display_property": "sosa:madeBySensor", + "sub_heading": "lineage" + }, + { + "field_name": "lineage_responsible_party", + "label": "Responsible party", + "display_group": "related and lineage", + "display_property": "prov:wasAssociatedWith", + "sub_heading": "lineage", + "validators": "ignore_missing" + }, + { + "field_name": "cited_in", + "label": "Cited in", + "display_snippet": "qdes_multi_text_url.html", + "form_snippet": "qdes_multi_text.html", + "validators": "ignore_missing qdes_uri_validator", + "display_group": "related and lineage", + "sub_heading": "lineage" + }, + { + "field_name": "contact_point", + "label": "Point of contact", + "display_property": "dcat:contactPoint", + "preset": "qdes_secure_vocab_service_autocomplete", + "vocabulary_service_name": "point-of-contact", + "display_group": "contacts" + }, + { + "field_name": "contact_publisher", + "label": "Publisher", + "display_property": "dcterms:publisher", + "preset": "controlled_vocabulary_single_select", + "vocabulary_service_name": "contact_publisher", + "display_group": "contacts", + "form_include_blank_choice": true, + "form_attrs": { + "data-module-title": "title" + } + }, + { + "field_name": "contact_creator", + "label": "Creator", + "display_snippet": "text.html", + "display_property": "dcterms:creator", + "preset": "qdes_secure_vocab_service_autocomplete", + "vocabulary_service_name": "creator", + "display_group": "contacts", + "form_attrs": { + "data-module": "qdes_autocomplete", + "data-module-source": "/ckan-admin/vocabulary-services/secure-autocomplete/{vocabularyServiceName}?incomplete=?", + "data-module-key": "value", + "data-module-label": "name", + "data-module-minimum-input-length": 3, + "data-module-create-search-choice": true, + "data-module-title": "title" + } + }, + { + "field_name": "contact_other_party", + "label": "Other responsible party", + "display_property": "prov:qualifiedAttribution", + "preset": "controlled_vocabulary_multi_group", + "display_group": "contacts", + "field_group": [ + { + "field_name": "the-party", + "label": "The party", + "form_snippet": "text.html", + "display_snippet": "qdes_secure_vocabulary_text.html", + "display_property": "prov:agent", + "vocabulary_service_name": "the-party", + "form_attrs": { + "data-module": "qdes_autocomplete", + "data-module-source": "/ckan-admin/vocabulary-services/secure-autocomplete/{vocabularyServiceName}?incomplete=?", + "data-module-key": "value", + "data-module-label": "name", + "data-module-minimum-input-length": 3, + "data-module-create-search-choice": true, + "data-module-title": "title" + } + }, + { + "field_name": "nature-of-their-responsibility", + "label": "Nature of their responsibility", + "form_snippet": "select.html", + "display_property": "dcat:hadRole", + "vocabulary_service_name": "nature-of-their-responsibility", + "choices_helper": "scheming_vocabulary_service_choices", + "form_attrs": { + "class": "form-control", + "data-module-title": "title" + }, + "form_include_blank_choice": true + } + ] + }, + { + "field_name": "acknowledgements", + "label": "Acknowledgements", + "display_property": "qdcat:acknowledgements", + "display_group": "contacts", + "form_snippet": "textarea.html" + }, + { + "field_name": "publication_status", + "label": "Publication status", + "display_property": "adms:status", + "preset": "controlled_vocabulary_single_select", + "vocabulary_service_name": "publication_status", + "display_group": "status", + "form_attrs": { + "data-module-title": "title" + }, + "form_include_blank_choice": true + }, + { + "field_name": "dataset_creation_date", + "label": "Creation date", + "display_property": "dcterms:created", + "preset": "datetime_tz", + "validators": "scheming_isodatetime_tz convert_to_json_if_datetime", + "display_group": "status", + "default_value": true + }, + { + "field_name": "dataset_release_date", + "label": "Release date", + "display_property": "dcterms:issued", + "preset": "datetime_tz", + "validators": "scheming_isodatetime_tz convert_to_json_if_datetime qdes_dataset_current_date_later_than_creation", + "display_group": "status" + }, + { + "field_name": "dataset_last_modified_date", + "label": "Last modified date", + "display_property": "dcterms:modified", + "preset": "datetime_tz", + "validators": "scheming_isodatetime_tz convert_to_json_if_datetime qdes_dataset_last_modified_date_before_today", + "display_group": "status" + }, + { + "field_name": "update_schedule", + "label": "Update schedule", + "display_property": "dcterms:accrualPeriodicity", + "preset": "controlled_vocabulary_single_select", + "vocabulary_service_name": "update_schedule", + "display_group": "status", + "form_attrs": { + "data-module-title": "title" + }, + "required": true + }, + { + "field_name": "classification_and_access_restrictions", + "label": "Classification and access restrictions", + "display_property": "dcterms:accessRights", + "display_group": "rights & licensing", + "preset": "controlled_vocabulary_multi_select", + "vocabulary_service_name": "classification_and_access_restrictions", + "form_attrs": { + "data-module": "qdes_autocomplete", + "data-module-title": "title" + }, + "form_include_blank_choice": true, + "required": true + }, + { + "field_name": "rights_statement", + "label": "Rights statement", + "form_snippet": "textarea.html", + "display_property": "dcterms:rights", + "display_group": "rights & licensing", + "default_value": "© State of Queensland (Department of Environment and Science) " + }, + { + "field_name": "license_id", + "label": "License", + "display_property": "dcterms:license", + "preset": "controlled_vocabulary_single_select", + "vocabulary_service_name": "license", + "display_group": "rights & licensing", + "required": true, + "form_include_blank_choice": true, + "form_attrs": { + "data-module-title": "title" + } + }, + { + "field_name": "specialized_license", + "label": "Specialised license", + "form_snippet": "textarea.html", + "display_property": "odrl:hasPolicy", + "display_snippet": "qdes_text_url.html", + "display_group": "rights & licensing" + }, + { + "field_name": "landing_page", + "label": "Web-page (landing page) for this dataset", + "display_property": "dcat:landingPage", + "display_group": "data access", + "validators": "ignore_missing url_validator qdes_uri_validator" + }, + { + "field_name": "owner_org", + "label": "Organization", + "preset": "dataset_organization", + "required": true + }, + { + "field_name": "state", + "label": "State", + "choices_helper": "get_state_list", + "preset": "select", + "form_snippet": "state_select.html" + } + ], + "resource_fields": [ + { + "field_name": "name", + "label": "Title", + "form_placeholder": "eg. January 2011 Gold Prices", + "display_property": "dcterms:title", + "required": true + }, + { + "field_name": "schema_standards", + "label": "Schema, standard(s)", + "display_property": "dcterms:conformsTo", + "display_snippet": "qdes_multi_text_url.html", + "form_snippet": "qdes_multi_text.html", + "validators": "ignore_missing qdes_uri_validator" + }, + { + "field_name": "format", + "label": "Primary format", + "display_property": "dcterms:format", + "preset": "qdes_vocab_service_autocomplete", + "vocabulary_service_name": "format", + "form_attrs": { + "data-module": "qdes_autocomplete", + "data-module-source": "/ckan-admin/vocabulary-service/term-autocomplete/format?incomplete=?", + "data-module-key": "value", + "data-module-label": "name", + "data-module-title": "title" + }, + "required": true + }, + { + "field_name": "compression", + "label": "Compression", + "display_property": "dcat:compressFormat", + "preset": "controlled_vocabulary_single_select", + "vocabulary_service_name": "compression", + "form_attrs": { + "data-module": "qdes_autocomplete", + "data-module-title": "title" + } + }, + { + "field_name": "packaging", + "label": "Packaging", + "display_property": "dcat:packageFormat", + "preset": "controlled_vocabulary_single_select", + "vocabulary_service_name": "packaging", + "form_attrs": { + "data-module": "qdes_autocomplete", + "data-module-title": "title" + } + }, + { + "field_name": "size", + "label": "Size", + "display_property": "dcat:byteSize", + "display_snippet": "qdes_byte_size.html", + "validators": "int_validator", + "form_placeholder": "Enter exact or approximate file size in bytes (integer)", + "required": true + }, + { + "field_name": "url", + "label": "Download address", + "display_property": "dcat:downloadURL", + "display_snippet": "qdes_text_url.html", + "required": true + }, + { + "field_name": "service_api_endpoint", + "label": "Service API end-point", + "display_property": "dcterms:conformsTo", + "display_snippet": "qdes_multi_text_url.html", + "form_snippet": "qdes_multi_text.html", + "validators": "ignore_missing qdes_uri_validator" + }, + { + "field_name": "data_services", + "label": "Data service", + "display_property": "dcat:accessService", + "preset": "controlled_dataservice_multi_select", + "form_attrs": { + "data-module": "qdes_autocomplete" + } + }, + { + "field_name": "description", + "label": "Description", + "form_snippet": "markdown.html", + "display_snippet": "markdown.html", + "form_placeholder": "Some useful notes about the data", + "display_property": "dcterms:description", + "required": true + }, + { + "field_name": "additional_info", + "label": "Additional information", + "form_snippet": "qdes_multi_markdown.html", + "display_snippet": "qdes_multi_markdown.html" + }, + { + "field_name": "rights_statement", + "label": "Rights statement", + "form_snippet": "textarea.html", + "display_property": "dcterms:rights", + "default_value": "© State of Queensland (Department of Environment and Science) " + }, + { + "field_name": "license", + "label": "License", + "display_property": "dcterms:license", + "preset": "controlled_vocabulary_single_select", + "vocabulary_service_name": "license", + "form_attrs": { + "data-module": "qdes_autocomplete", + "data-module-title": "title" + } + } + ] +} \ No newline at end of file diff --git a/ckanext/qdes_schema/fanstatic/autocomplete.js b/ckanext/qdes_schema/fanstatic/autocomplete.js new file mode 100644 index 00000000..59634017 --- /dev/null +++ b/ckanext/qdes_schema/fanstatic/autocomplete.js @@ -0,0 +1,436 @@ +/* An auto-complete module for select and input elements that can pull in + * a list of terms from an API endpoint (provided using data-module-source). + * + * source - A url pointing to an API autocomplete endpoint. + * interval - The interval between requests in milliseconds (default: 300). + * items - The max number of items to display (default: 10) + * tags - Boolean attribute if true will create a tag input. + * key - A string of the key you want to be the form value to end up on + * from the ajax returned results + * label - A string of the label you want to appear within the dropdown for + * returned results + * tokensep - A string that contains characters which will be interpreted + * as separators for tags when typed or pasted (default ","). + * Examples + * + * // + * + */ +jQuery(document).ready(function () { + ckan.module('qdes_autocomplete', function (jQuery) { + return { + /* Options for the module */ + options: { + tags: false, + createtags: true, + key: false, + label: false, + title: false, + items: 10, + source: null, + tokensep: ',', + interval: 300, + dropdownClass: '', + containerClass: '', + allowClear: true, + createSearchChoice: false, + id: '', + type: '', + vocabularyServiceName: '', + minimumInputLength: 0 + }, + + /* Sets up the module, binding methods, creating elements etc. Called + * internally by ckan.module.initialize(); + * + * Returns nothing. + */ + initialize: function () { + jQuery.proxyAll(this, /_on/, /format/); + this.setupAutoComplete(); + }, + + /* Sets up the auto complete plugin. + * + * Returns nothing. + */ + setupAutoComplete: function () { + var settings = { + width: 'resolve', + formatResult: this.formatResult, + formatNoMatches: this.formatNoMatches, + formatInputTooShort: this.formatInputTooShort, + dropdownCssClass: this.options.dropdownClass, + containerCssClass: this.options.containerClass, + tokenSeparators: this.options.tokensep.split(''), + allowClear: this.options.allowClear, + minimumInputLength: this.options.minimumInputLength + }; + + // If source is set for API, replace the id placeholder with id passed in + if (this.options.source && this.options.id) { + this.options.source = this.options.source.replace('', this.options.id); + } + + // Different keys are required depending on whether the select is + // tags or generic completion. + if (!this.el.is('select')) { + if (this.options.tags) { + settings.tags = this._onQuery; + + // Disable creating new tags + if (!this.options.createtags) { + settings.createSearchChoice = function (params) { + return undefined; + } + } + } else { + settings.query = this._onQuery; + // If there is no match from search results, allow user to create new choice option + if (this.options.createSearchChoice) { + settings.createSearchChoice = this.formatTerm; + } + else { + // Search needs to find a match to be selected + settings.createSearchChoice = function (params) { + return undefined; + } + } + } + settings.initSelection = this.formatInitialValue; + } + else { + if (/MSIE (\d+\.\d+);/.test(navigator.userAgent)) { + var ieversion = new Number(RegExp.$1); + if (ieversion <= 7) { return } + } + } + + var select2 = this.el.select2(settings).data('select2'); + + if (this.options.tags && select2 && select2.search) { + // find the "fake" input created by select2 and add the keypress event. + // This is not part of the plugins API and so may break at any time. + select2.search.on('keydown', this._onKeydown); + } + + // This prevents Internet Explorer from causing a window.onbeforeunload + // even from firing unnecessarily + $('.select2-choice', select2.container).on('click', function () { + return false; + }); + + this.el.on('select2-selecting', function (e) { + $('.tooltip').remove(); + }); + + this._select2 = select2; + }, + + /* Looks up the completions for the current search term and passes them + * into the provided callback function. + * + * The results are formatted for use in the select2 autocomplete plugin. + * + * string - The term to search for. + * fn - A callback function. + * + * Examples + * + * module.getCompletions('cake', function (results) { + * results === {results: []} + * }); + * + * Returns a jqXHR promise. + */ + getCompletions: function (string, fn) { + var parts = this.options.source.split('?'); + var end = parts.pop(); + var source = parts.join('?') + encodeURIComponent(string) + end; + if (this.options.id.length > 0) { + source += "&dataset_id=" + this.options.id; + } + if (this.options.type.length > 0) { + source += "&dataset_type=" + this.options.type; + } + if (source.indexOf('{vocabularyServiceName}') >= 0 && this.options.vocabularyServiceName.length > 0) { + source = source.replace("{vocabularyServiceName}", this.options.vocabularyServiceName); + } + var client = this.sandbox.client; + var parseCompletions = this.parseCompletions; + var options = { + format: function (data) { + var completion_options = jQuery.extend(options, { objects: true }); + return { + results: parseCompletions(data, completion_options) + } + }, + key: this.options.key, + label: this.options.label, + title: this.options.title + }; + + return client.getCompletions(source, options, fn); + }, + + /* Copied from ckan/public/base/javascript/client.js and modified to add title property to object returned + * Takes a JSON response from an auto complete endpoint and normalises + * the data into an array of strings. This also will remove duplicates + * from the results (this is case insensitive). + * + * data - The parsed JSON response from the server. + * options - An object of options for the method. + * objects: If true returns an object of results. + * + * Examples + * + * jQuery.getJSON(tagCompletionUrl, function (data) { + * var parsed = client.parseCompletions(data); + * }); + * + * Returns the parsed object. + */ + parseCompletions: function (data, options) { + if (typeof data === 'string') { + // Package completions are returned as a crazy string. So we handle + // them separately. + return this.parsePackageCompletions(data, options); + } + + var map = {}; + // If given a 'result' array then convert it into a Result dict inside a Result dict. + // new syntax (not used until all browsers support arrow notation): + //data = data.result ? { 'ResultSet': { 'Result': data.result.map(x => ({'Name': x})) } } : data; + // compatible syntax: + data = data.result ? { 'ResultSet': { 'Result': data.result.map(function (val) { return { 'Name': val } }) } } : data; + // If given a Result dict inside a ResultSet dict then use the Result dict. + var raw = jQuery.isArray(data) ? data : data.ResultSet && data.ResultSet.Result || {}; + + var items = jQuery.map(raw, function (item) { + var key = typeof options.key != 'undefined' ? item[options.key] : false; + var label = typeof options.label != 'undefined' ? item[options.label] : false; + var title = typeof options.title != 'undefined' ? item[options.title] : false; + + item = typeof item === 'string' ? item : item.name || item.Name || item.Format || ''; + item = jQuery.trim(item); + + key = key ? key : item; + label = label ? label : item; + title = title ? title : ''; + + var lowercased = item.toLowerCase(); + var returnObject = options && options.objects === true; + + if (lowercased && !map[lowercased]) { + map[lowercased] = 1; + return returnObject ? { id: key, text: label, title: title } : item; + } + + return null; + }); + + // Remove duplicates. + items = jQuery.grep(items, function (item) { return item !== null; }); + + return items; + }, + + /* Looks up the completions for the provided text but also provides a few + * optimisations. If there is no search term it will automatically set + * an empty array. Ajax requests will also be debounced to ensure that + * the server is not overloaded. + * + * string - The term to search for. + * fn - A callback function. + * + * Returns nothing. + */ + lookup: function (string, fn) { + var module = this; + + // Cache the last searched term otherwise we'll end up searching for + // old data. + this._lastTerm = string; + + // Kills previous timeout + clearTimeout(this._debounced); + + // OK, wipe the dropdown before we start ajaxing the completions + fn({ results: [] }); + + if (string) { + // Set a timer to prevent the search lookup occurring too often. + this._debounced = setTimeout(function () { + var term = module._lastTerm; + + // Cancel the previous request if it hasn't yet completed. + if (module._last && typeof module._last.abort == 'function') { + module._last.abort(); + } + + module._last = module.getCompletions(term, fn); + }, this.options.interval); + + // This forces the ajax throbber to appear, because we've called the + // callback already and that hides the throbber + $('.select2-search input', this._select2.dropdown).addClass('select2-active'); + } + }, + + /* Formatter for the select2 plugin that returns a string for use in the + * results list with the current term emboldened. + * + * state - The current object that is being rendered. + * container - The element the content will be added to (added in 3.0) + * query - The query object (added in select2 3.0). + * + * + * Returns a text string. + */ + formatResult: function (state, container, query, escapeMarkup) { + var term = this._lastTerm || (query ? query.term : null) || null; // same as query.term + + if (container) { + // Append the select id to the element for styling. + container.attr('data-value', state.id); + // Add container title attribute either from select element or state object property + if (state.element && state.element[0]) { + container.prop('title', state.element[0].title); + } + else if (state.title) { + container.prop('title', state.title); + } + container.on("mouseover", this._initialiseTooltip.bind(this)); + } + + var result = []; + $(state.text.split(term)).each(function () { + result.push(escapeMarkup ? escapeMarkup(this) : this); + }); + + return result.join(term && (escapeMarkup ? escapeMarkup(term) : term).bold()); + }, + + /* Formatter for the select2 plugin that returns a string used when + * the filter has no matches. + * + * Returns a text string. + */ + formatNoMatches: function (term) { + return !term ? this._('Start typing…') : this._('No matches found'); + }, + + /* Formatter used by the select2 plugin that returns a string when the + * input is too short. + * + * Returns a string. + */ + formatInputTooShort: function (term, min) { + return this.ngettext( + 'Input is too short, must be at least one character', + 'Input is too short, must be at least %(num)d characters', + min + ); + }, + + /* Takes a string and converts it into an object used by the select2 plugin. + * + * term - The term to convert. + * + * Returns an object for use in select2. + */ + formatTerm: function (term) { + term = jQuery.trim(term || ''); + + // Need to replace comma with a unicode character to trick the plugin + // as it won't split this into multiple items. + return { id: term.replace(/,/g, '\u002C'), text: term }; + }, + + /* Callback function that parses the initial field value. + * + * element - The initialized input element wrapped in jQuery. + * callback - A callback to run once the formatting is complete. + * + * Returns a term object or an array depending on the type. + */ + formatInitialValue: function (element, callback) { + var value = jQuery.trim(element.val() || ''); + var formatted; + + if (this.options.tags) { + formatted = jQuery.map(value.split(","), this.formatTerm); + } else if (this.options.source) { + // If a API source is used, check if the value is a json object to preselect the initial value + try { + formatted = JSON.parse(value); + // Set the element value to the object id so on form submit the id value is used instead of the JSON object + element[0].value = formatted.id; + } catch (e) { + formatted = this.formatTerm(value); + } + } else { + formatted = this.formatTerm(value); + } + + // Select2 v3.0 supports a callback for async calls. + if (typeof callback === 'function') { + callback(formatted); + } + + return formatted; + }, + + /* Callback triggered when the select2 plugin needs to make a request. + * + * Returns nothing. + */ + _onQuery: function (options) { + if (options) { + this.lookup(options.term, options.callback); + } + }, + + /* Called when a key is pressed. If the key is a comma we block it and + * then simulate pressing return. + * + * Returns nothing. + */ + _onKeydown: function (event) { + if (typeof event.key !== 'undefined' ? event.key === ',' : event.which === 188) { + event.preventDefault(); + setTimeout(function () { + var e = jQuery.Event("keydown", { which: 13 }); + jQuery(event.target).trigger(e); + }, 10); + } + }, + + /* Initialise the elements bootstrap tooltip + * + * Returns nothing. + */ + _initialiseTooltip : function (event) { + // Only initialise tooltip if it has not been initialised yet + if(jQuery(event.target).data('bs.tooltip') === undefined) { + jQuery(event.target).tooltip({ + "placement": "auto", + "delay": {"show": 500, "hide": 100}, + "container": 'body' + }); + setTimeout(this._showTooltip, 500, event.target); + } + }, + + /* Show the tooltip after its been initialised only if the mouse is still hovering over the element + * + * Returns nothing. + */ + _showTooltip : function (container) { + if(jQuery(container).is(":hover")) { + jQuery(container).tooltip('show'); + } + } + }; + }); +}); diff --git a/ckanext/qdes_schema/fanstatic/dataservice.js b/ckanext/qdes_schema/fanstatic/dataservice.js new file mode 100644 index 00000000..a1fb87d3 --- /dev/null +++ b/ckanext/qdes_schema/fanstatic/dataservice.js @@ -0,0 +1,6 @@ +jQuery(document).ready(function () { + // Remove any input name="_ckan_phase" fields from the form + jQuery('input[name="_ckan_phase"]').each(function () { + jQuery(this).remove(); + }); +}); \ No newline at end of file diff --git a/ckanext/qdes_schema/fanstatic/dataset_no_resource.js b/ckanext/qdes_schema/fanstatic/dataset_no_resource.js new file mode 100644 index 00000000..4366a5eb --- /dev/null +++ b/ckanext/qdes_schema/fanstatic/dataset_no_resource.js @@ -0,0 +1,7 @@ +jQuery(document).ready(function () { + jQuery('button[name="save_record"]').on('click', function (e) { + e.preventDefault(); + jQuery('input[name="_ckan_phase"]').remove(); + jQuery(this).closest('form').submit(); + }); +}); diff --git a/ckanext/qdes_schema/fanstatic/help-text.js b/ckanext/qdes_schema/fanstatic/help-text.js new file mode 100644 index 00000000..305c84f2 --- /dev/null +++ b/ckanext/qdes_schema/fanstatic/help-text.js @@ -0,0 +1,38 @@ +(function ($) { + $(document).ready(function () { + /** + * Setup position of help texts. + */ + var $formGroup = $('.form-group'); + var $displayGroupTooltips = $('.qdes-display-group-help-text'); + var $subHeadingTooltips = $('.qdes-sub-heading-help-text'); + + $formGroup.each(function () { + var $helpTextEl = $(this).find('.qdes-help-text'); + var $labelEl = $(this).find('> label'); + + if ($helpTextEl.length > 0) { + $labelEl.after($helpTextEl); + } + }); + + $displayGroupTooltips.each(function () { + var $accordionTitleBtn = $(this).closest('.display-group-status').find('.acc-heading > label .title'); + $accordionTitleBtn.after($(this)); + }); + + $subHeadingTooltips.each(function () { + var $headingEl = $(this).closest('.form-group').prev(); + if ($headingEl.prop('tagName') === 'H3' || $headingEl.prop('tagName') === 'H2') { + $headingEl.append($(this)); + } + }); + + // Init bootstrap tooltip. + $('body').tooltip({ + placement: 'right', + selector: '.help-text', + container: 'body' + }); + }); +})(jQuery) diff --git a/ckanext/qdes_schema/fanstatic/input-date-helper.js b/ckanext/qdes_schema/fanstatic/input-date-helper.js new file mode 100644 index 00000000..2c4553e6 --- /dev/null +++ b/ckanext/qdes_schema/fanstatic/input-date-helper.js @@ -0,0 +1,45 @@ +(function ($) { + $(document).ready(function () { + /** + * Setup clear button on input[type=date] elements. + */ + var $clearEl = $('.clear-btn'); + var eventF = function ($el) { + if ($el.val().length > 0) { + $el.parent().addClass('show-clear-btn'); + } else { + $el.parent().removeClass('show-clear-btn'); + } + } + + $clearEl.each(function () { + var $inputEl = $(this).parent().find('input'); + + if ($inputEl.attr('type') === 'date') { + $inputEl.change(function () { + eventF($(this)); + }); + + $inputEl.change(); + } else { + $inputEl.keyup(function () { + eventF($(this)); + }); + $inputEl.keyup(); + } + }); + + // Listen to clear button. + $clearEl.click(function () { + var $inputEl = $(this).parent().find('input'); + $inputEl.val(""); + + if ($inputEl.attr('type') === 'date') { + $inputEl.change(); + } + else { + $inputEl.keyup(); + } + }); + }); +})(jQuery) diff --git a/ckanext/qdes_schema/fanstatic/iso-8601-duration.js b/ckanext/qdes_schema/fanstatic/iso-8601-duration.js new file mode 100644 index 00000000..1a5d48e6 --- /dev/null +++ b/ckanext/qdes_schema/fanstatic/iso-8601-duration.js @@ -0,0 +1,172 @@ +(function ($) { + $(document).ready(function () { + 'use strict'; + + /** + * Default designators. + */ + var designators = [ + {label: 'years', symbol: 'Y', type: 'period', value: ''}, + {label: 'months', symbol: 'M', type: 'period', value: ''}, + {label: 'weeks', symbol: 'W', type: 'period', value: ''}, + {label: 'days', symbol: 'D', type: 'period', value: ''}, + {label: 'hours', symbol: 'H', type: 'time', value: ''}, + {label: 'minutes', symbol: 'M', type: 'time', value: ''}, + {label: 'seconds', symbol: 'S', type: 'time', value: ''}, + ] + + /** + * Assign value to the element designators. + * + * @param el_designators + * The designator for the element. + * @param label + * The label. + * @param value + * The value. + */ + var setDesignator = function (el_designators, label, value) { + el_designators.forEach(function (item, index) { + if (item.label === label) { + designators[index].value = value; + } + }); + } + + /** + * Returns designator value with or without its symbol. + * + * @param el_designators + * The designator for the element. + * @param label + * The label. + * @param symbol + * True with symbol, else false without symbol. + */ + var getDesignatorValue = function (el_designators, label, symbol) { + var value = ''; + el_designators.forEach(function (item, index) { + if (item.label === label && item.value !== '') { + value = symbol ? item.value + item.symbol : item.value; + } + }); + + return value; + } + + /** + * Dump designators to ISO 8061 duration format. + * + * @param el_designators + * The designator for the element. + * @returns {string} + * The duration format. + */ + var dumpDesignators = function (el_designators) { + var str = ''; + var has_period_notation = false; + var has_time_notation = false; + designators.forEach(function (item, index) { + var value = getDesignatorValue(el_designators, item.label, true); + + if (value.length > 0 && item.type === 'period' && !has_period_notation) { + has_period_notation = true; + value = 'P' + value; + } + + if (value.length > 0 && item.type === 'time' && !has_time_notation) { + has_time_notation = true; + value = 'T' + value; + } + + str = str + value; + }); + + return str; + } + + /** + * Parses the iso8601 Duration string to object. + * + * @param iso8601Duration + * The duration string. + * + * @returns obj + * The designators obj. + */ + var parseISO8601Duration = function (iso8601Duration) { + var iso8601DurationRegex = /(-)?P(?:([.,\d]+)Y)?(?:([.,\d]+)M)?(?:([.,\d]+)W)?(?:([.,\d]+)D)?(?:T(?:([.,\d]+)H)?(?:([.,\d]+)M)?(?:([.,\d]+)S)?)?/; + var matches = iso8601Duration.match(iso8601DurationRegex); + var el_designators = designators; + + var result = { + sign: matches[1] === undefined ? '+' : '-', + years: matches[2] === undefined ? '' : matches[2], + months: matches[3] === undefined ? '' : matches[3], + weeks: matches[4] === undefined ? '' : matches[4], + days: matches[5] === undefined ? '' : matches[5], + hours: matches[6] === undefined ? '' : matches[6], + minutes: matches[7] === undefined ? '' : matches[7], + seconds: matches[8] === undefined ? '' : matches[8] + } + + el_designators.forEach(function (item, index) { + el_designators[index].value = result[item.label]; + }); + + return el_designators; + } + + // Listen to duration fields. + $(document).on('blur', '.iso-8601-duration-field input', function () { + var $wrapper = $(this).parents('.iso-8601-duration-field-wrapper'); + var field_name = $wrapper.data('duration-fieldname'); + var $el = $wrapper.find('#field-' + field_name + '-hidden'); + var current_designator = $(this).attr('name').replace(field_name + '_', ''); + + setDesignator(duration_field_designators[field_name], current_designator, $(this).val()); + + $el.val(dumpDesignators(duration_field_designators[field_name])); + }); + + // Setup the duration field. + var duration_field_designators = []; + $('.iso-8601-duration-field-wrapper').each(function () { + var $el_wrapper = $(this); + var field_name = $el_wrapper.data('duration-fieldname'); + var $el = $el_wrapper.find('#field-' + field_name + '-hidden'); + + if (field_name.length > 0) { + // Parse the duration and put them to respected field. + duration_field_designators[field_name] = designators; + + if ($el.val().length > 0) { + var has_existing_value = false; + duration_field_designators[field_name] = parseISO8601Duration($el.val()); + + duration_field_designators[field_name].forEach(function (item, index) { + var $el_designator = $el_wrapper.find('#field-' + field_name + '-' + item.label); + + // On setup, when the field already a value, leave it, + // it is an value from form validation error. + if ($el_designator.val().length > 0) { + has_existing_value = true; + } + else { + $el_designator.val(item.value); + } + }); + + // If has_existing_value is true, trigger the blur effect on each designator fields, + // at this stage the duration_field_designators[field_name] won't have all information. + if (has_existing_value) { + $el_wrapper.find('.iso-8601-duration-field input').each(function () { + console.log($(this).attr('id')); + $(this).blur(); + }); + } + } + } + }); + }); +}(jQuery)); diff --git a/ckanext/qdes_schema/fanstatic/jquery.fancytree-all-deps.min.js b/ckanext/qdes_schema/fanstatic/jquery.fancytree-all-deps.min.js new file mode 100644 index 00000000..ff109f1e --- /dev/null +++ b/ckanext/qdes_schema/fanstatic/jquery.fancytree-all-deps.min.js @@ -0,0 +1 @@ +!function(S){S.ui=S.ui||{};S.ui.version="1.12.1";var r,n=0,a=Array.prototype.slice;S.cleanData=(r=S.cleanData,function(e){var t,n,i;for(i=0;null!=(n=e[i]);i++)try{(t=S._data(n,"events"))&&t.remove&&S(n).triggerHandler("remove")}catch(e){}r(e)}),S.widget=function(e,n,t){var i,r,o,s={},a=e.split(".")[0],l=a+"-"+(e=e.split(".")[1]);return t||(t=n,n=S.Widget),S.isArray(t)&&(t=S.extend.apply(null,[{}].concat(t))),S.expr[":"][l.toLowerCase()]=function(e){return!!S.data(e,l)},S[a]=S[a]||{},i=S[a][e],r=S[a][e]=function(e,t){if(!this._createWidget)return new r(e,t);arguments.length&&this._createWidget(e,t)},S.extend(r,i,{version:t.version,_proto:S.extend({},t),_childConstructors:[]}),(o=new n).options=S.widget.extend({},o.options),S.each(t,function(t,o){S.isFunction(o)?s[t]=function(){function i(){return n.prototype[t].apply(this,arguments)}function r(e){return n.prototype[t].apply(this,e)}return function(){var e,t=this._super,n=this._superApply;return this._super=i,this._superApply=r,e=o.apply(this,arguments),this._super=t,this._superApply=n,e}}():s[t]=o}),r.prototype=S.widget.extend(o,{widgetEventPrefix:i&&o.widgetEventPrefix||e},s,{constructor:r,namespace:a,widgetName:e,widgetFullName:l}),i?(S.each(i._childConstructors,function(e,t){var n=t.prototype;S.widget(n.namespace+"."+n.widgetName,r,t._proto)}),delete i._childConstructors):n._childConstructors.push(r),S.widget.bridge(e,r),r},S.widget.extend=function(e){for(var t,n,i=a.call(arguments,1),r=0,o=i.length;r",options:{classes:{},disabled:!1,create:null},_createWidget:function(e,t){t=S(t||this.defaultElement||this)[0],this.element=S(t),this.uuid=n++,this.eventNamespace="."+this.widgetName+this.uuid,this.bindings=S(),this.hoverable=S(),this.focusable=S(),this.classesElementLookup={},t!==this&&(S.data(t,this.widgetFullName,this),this._on(!0,this.element,{remove:function(e){e.target===t&&this.destroy()}}),this.document=S(t.style?t.ownerDocument:t.document||t),this.window=S(this.document[0].defaultView||this.document[0].parentWindow)),this.options=S.widget.extend({},this.options,this._getCreateOptions(),e),this._create(),this.options.disabled&&this._setOptionDisabled(this.options.disabled),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:function(){return{}},_getCreateEventData:S.noop,_create:S.noop,_init:S.noop,destroy:function(){var n=this;this._destroy(),S.each(this.classesElementLookup,function(e,t){n._removeClass(t,e)}),this.element.off(this.eventNamespace).removeData(this.widgetFullName),this.widget().off(this.eventNamespace).removeAttr("aria-disabled"),this.bindings.off(this.eventNamespace)},_destroy:S.noop,widget:function(){return this.element},option:function(e,t){var n,i,r,o=e;if(0===arguments.length)return S.widget.extend({},this.options);if("string"==typeof e)if(o={},e=(n=e.split(".")).shift(),n.length){for(i=o[e]=S.widget.extend({},this.options[e]),r=0;r
"),i=n.children()[0];return S("body").append(n),e=i.offsetWidth,n.css("overflow","scroll"),e===(t=i.offsetWidth)&&(t=n[0].clientWidth),n.remove(),r=e-t},getScrollInfo:function(e){var t=e.isWindow||e.isDocument?"":e.element.css("overflow-x"),n=e.isWindow||e.isDocument?"":e.element.css("overflow-y"),i="scroll"===t||"auto"===t&&e.width_(k(i),k(r))?o.important="horizontal":o.important="vertical",u.using.call(this,e,o)}),s.offset(S.extend(d,{using:e}))})},S.ui.position={fit:{left:function(e,t){var n,i=t.within,r=i.isWindow?i.scrollLeft:i.offset.left,o=i.width,s=e.left-t.collisionPosition.marginLeft,a=r-s,l=s+t.collisionWidth-o-r;t.collisionWidth>o?0o?0"'/]/g,n=/[<>"'/]/g,h="$recursive_request",f="$request_target_invalid",i={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/"},r={16:!0,17:!0,18:!0},v={8:"backspace",9:"tab",10:"return",13:"return",19:"pause",20:"capslock",27:"esc",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down",45:"insert",46:"del",59:";",61:"=",96:"0",97:"1",98:"2",99:"3",100:"4",101:"5",102:"6",103:"7",104:"8",105:"9",106:"*",107:"+",109:"-",110:".",111:"/",112:"f1",113:"f2",114:"f3",115:"f4",116:"f5",117:"f6",118:"f7",119:"f8",120:"f9",121:"f10",122:"f11",123:"f12",144:"numlock",145:"scroll",173:"-",186:";",187:"=",188:",",189:"-",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'"},m={16:"shift",17:"ctrl",18:"alt",91:"meta",93:"meta"},s={0:"",1:"left",2:"middle",3:"right"},b="active expanded focus folder lazy radiogroup selected unselectable unselectableIgnore".split(" "),x={},p="columns types".split(" "),_="checkbox expanded extraClasses folder icon iconTooltip key lazy partsel radiogroup refKey selected statusNodeType title tooltip type unselectable unselectableIgnore unselectableStatus".split(" "),a={},k={},l={active:!0,children:!0,data:!0,focus:!0};for(o=0;oe.getIndexHier(".",5)},isChildOf:function(e){return this.parent&&this.parent===e},isDescendantOf:function(e){if(!e||e.tree!==this.tree)return!1;for(var t=this.parent;t;){if(t===e)return!0;t===t.parent&&C.error("Recursive parent link: "+t),t=t.parent}return!1},isExpanded:function(){return!!this.expanded},isFirstSibling:function(){var e=this.parent;return!e||e.children[0]===this},isFolder:function(){return!!this.folder},isLastSibling:function(){var e=this.parent;return!e||e.children[e.children.length-1]===this},isLazy:function(){return!!this.lazy},isLoaded:function(){return!this.lazy||void 0!==this.hasChildren()},isLoading:function(){return!!this._isLoading},isRoot:function(){return this.isRootNode()},isPartsel:function(){return!this.selected&&!!this.partsel},isPartload:function(){return!!this.partload},isRootNode:function(){return this.tree.rootNode===this},isSelected:function(){return!!this.selected},isStatusNode:function(){return!!this.statusNodeType},isPagingNode:function(){return"paging"===this.statusNodeType},isTopLevel:function(){return this.tree.rootNode===this.parent},isUndefined:function(){return void 0===this.hasChildren()},isVisible:function(){var e,t,n=this.tree.enableFilter,i=this.getParentList(!1,!1);if(n&&!this.match&&!this.subMatchCount)return!1;for(e=0,t=i.length;eDate.now()?t.value:(delete this._tempCache[e],null)},_usesExtension:function(e){return 0<=C.inArray(e,this.options.extensions)},_requireExtension:function(e,t,n,i){null!=n&&(n=!!n);var r=this._local.name,o=this.options.extensions,s=C.inArray(e,o)",{type:"checkbox",name:r,value:e.key,checked:!0}))}a.length?a.empty():a=C("
",{id:s}).hide().insertAfter(this.$container),!1!==t&&this.activeNode&&a.append(C("",{type:"radio",name:o,value:this.activeNode.key,checked:!0})),n.filter?this.visit(function(e){var t=n.filter(e);if("skip"===t)return t;!1!==t&&d(e)}):!1!==e&&(i=this.getSelectedNodes(l),C.each(i,function(e,t){d(t)}))},getActiveNode:function(){return this.activeNode},getFirstChild:function(){return this.rootNode.getFirstChild()},getFocusNode:function(){return this.focusNode},getOption:function(e){return this.widget.option(e)},getNodeByKey:function(t,e){var n,i;return!e&&(n=document.getElementById(this.options.idPrefix+t))?n.ftnode?n.ftnode:null:(e=e||this.rootNode,i=null,t=""+t,e.visit(function(e){if(e.key===t)return i=e,!1},!0),i)},getRootNode:function(){return this.rootNode},getSelectedNodes:function(e){return this.rootNode.getSelectedNodes(e)},hasFocus:function(){return!!this._hasFocus},info:function(e){3<=this.options.debugLevel&&(Array.prototype.unshift.call(arguments,this.toString()),d("info",arguments))},isLoading:function(){var t=!1;return this.rootNode.visit(function(e){if(e._isLoading||e._requestId)return!(t=!0)},!0),t},loadKeyPath:function(e,t){var i,n,r,o=this,s=new C.Deferred,a=this.getRootNode(),l=this.options.keyPathSeparator,d=[],c=C.extend({},t);for("function"==typeof t?i=t:t&&t.callback&&(i=t.callback),c.callback=function(e,t,n){i&&i.call(e,t,n),s.notifyWith(e,[{node:t,status:n}])},null==c.matchKey&&(c.matchKey=function(e,t){return e.key===t}),C.isArray(e)||(e=[e]),n=0;nu)a.rejectWith(this,[h]);else if(null!==d.parent||null===c){if(o.options.postProcess){try{(r=l._triggerNodeEvent("postProcess",o,o.originalEvent,{response:e,error:null,dataType:s.dataType})).error&&l.warn("postProcess returned error:",r)}catch(e){r={error:e,message:""+e,details:"postProcess failed"}}if(r.error)return i=C.isPlainObject(r.error)?r.error:{message:r.error},i=l._makeHookContext(d,null,i),void a.rejectWith(this,[i]);(C.isArray(r)||C.isPlainObject(r)&&C.isArray(r.children))&&(e=r)}else e&&e.hasOwnProperty("d")&&o.options.enableAspx&&(42===o.options.enableAspx&&l.warn("The default for enableAspx will change to `false` in the fututure. Pass `enableAspx: true` or implement postProcess to silence this warning."),e="string"==typeof e.d?C.parseJSON(e.d):e.d);a.resolveWith(this,[e])}else a.rejectWith(this,[f])},function(e,t,n){var i=l._makeHookContext(d,null,{error:e,args:Array.prototype.slice.call(arguments),message:n,details:e.status+": "+n});a.rejectWith(this,[i])}),a.done(function(e){var t,n,i;l.nodeSetStatus(o,"ok"),C.isPlainObject(e)?(w(d.isRootNode(),"source may only be an object for root nodes (expecting an array of child objects otherwise)"),w(C.isArray(e.children),"if an object is passed as source, it must contain a 'children' array (all other properties are added to 'tree.data')"),t=(n=e).children,delete n.children,C.each(p,function(e,t){void 0!==n[t]&&(l[t]=n[t],delete n[t])}),C.extend(l.data,n)):t=e,w(C.isArray(t),"expected array of children"),d._setChildren(t),l.options.nodata&&0===t.length&&(C.isFunction(l.options.nodata)?i=l.options.nodata.call(l,{type:"nodata"},o):!0===l.options.nodata&&d.isRootNode()?i=l.options.strings.noData:"string"==typeof l.options.nodata&&d.isRootNode()&&(i=l.options.nodata),i&&d.setStatus("nodata",i)),l._triggerNodeEvent("loadChildren",d)}).fail(function(e){var t;e!==h?e!==f?(e.node&&e.error&&e.message?t=e:"[object Object]"===(t=l._makeHookContext(d,null,{error:e,args:Array.prototype.slice.call(arguments),message:e?e.message||e.toString():""})).message&&(t.message=""),d.warn("Load children failed ("+t.message+")",t),!1!==l._triggerNodeEvent("loadError",t,null)&&l.nodeSetStatus(o,"error",t.message,t.details)):d.warn("Lazy parent node was removed while loading: discarding response."):d.warn("Ignored response for obsolete load request #"+u+" (expected #"+d._requestId+")")}).always(function(){d._requestId=null,i&&l.debugTimeEnd(r)}),a.promise()},nodeLoadKeyPath:function(e,t){},nodeRemoveChild:function(e,t){var n,i=e.node,r=C.extend({},e,{node:t}),o=i.children;if(1===o.length)return w(t===o[0],"invalid single child"),this.nodeRemoveChildren(e);this.activeNode&&(t===this.activeNode||this.activeNode.isDescendantOf(t))&&this.activeNode.setActive(!1),this.focusNode&&(t===this.focusNode||this.focusNode.isDescendantOf(t))&&(this.focusNode=null),this.nodeRemoveMarkup(r),this.nodeRemoveChildren(r),w(0<=(n=C.inArray(t,o)),"invalid child"),i.triggerModifyChild("remove",t),t.visit(function(e){e.parent=null},!0),this._callHook("treeRegisterNode",this,!1,t),o.splice(n,1)},nodeRemoveChildMarkup:function(e){var t=e.node;t.ul&&(t.isRootNode()?C(t.ul).empty():(C(t.ul).remove(),t.ul=null),t.visit(function(e){e.li=e.ul=null}))},nodeRemoveChildren:function(e){var t=e.tree,n=e.node;n.children&&(this.activeNode&&this.activeNode.isDescendantOf(n)&&this.activeNode.setActive(!1),this.focusNode&&this.focusNode.isDescendantOf(n)&&(this.focusNode=null),this.nodeRemoveChildMarkup(e),n.triggerModifyChild("remove",null),n.visit(function(e){e.parent=null,t._callHook("treeRegisterNode",t,!1,e)}),n.lazy?n.children=[]:n.children=null,n.isRootNode()||(n.expanded=!1),this.nodeRenderStatus(e))},nodeRemoveMarkup:function(e){var t=e.node;t.li&&(C(t.li).remove(),t.li=null),this.nodeRemoveChildMarkup(e)},nodeRender:function(e,t,n,i,r){var o,s,a,l,d,c,u,h=e.node,f=e.tree,p=e.options,g=p.aria,y=!1,v=h.parent,m=!v,b=h.children,x=null;if(!1!==f._enableUpdate&&(m||v.ul)){if(w(m||v.ul,"parent UL must exist"),m||(h.li&&(t||h.li.parentNode!==h.parent.ul)&&(h.li.parentNode===h.parent.ul?x=h.li.nextSibling:this.debug("Unlinking "+h+" (must be child of "+h.parent+")"),this.nodeRemoveMarkup(e)),h.li?this.nodeRenderStatus(e):(y=!0,h.li=document.createElement("li"),(h.li.ftnode=h).key&&p.generateIds&&(h.li.id=p.idPrefix+h.key),h.span=document.createElement("span"),h.span.className="fancytree-node",g&&!h.tr&&C(h.li).attr("role","treeitem"),h.li.appendChild(h.span),this.nodeRenderTitle(e),p.createNode&&p.createNode.call(f,{type:"createNode"},e)),p.renderNode&&p.renderNode.call(f,{type:"renderNode"},e)),b){if(m||h.expanded||!0===n){for(h.ul||(h.ul=document.createElement("ul"),(!0!==i||r)&&h.expanded||(h.ul.style.display="none"),g&&C(h.ul).attr("role","group"),h.li?h.li.appendChild(h.ul):h.tree.$div.append(h.ul)),l=0,d=b.length;l")):p.push(""),(n=g.evalOption("checkbox",d,d,u,!1))&&!d.isStatusNode()&&(s=h?" role='checkbox'":"",i="fancytree-checkbox",("radio"===n||d.parent&&d.parent.radiogroup)&&(i+=" fancytree-radio"),p.push("")),void 0!==d.data.iconClass&&(d.icon?C.error("'iconClass' node option is deprecated since v2.14.0: use 'icon' only instead"):(d.warn("'iconClass' node option is deprecated since v2.14.0: use 'icon' instead"),d.icon=d.data.iconClass)),!1!==(r=g.evalOption("icon",d,d,u,!0))&&(s=h?" role='presentation'":"",l=(l=g.evalOption("iconTooltip",d,d,u,null))?" title='"+O(l)+"'":"","string"==typeof r?y.test(r)?(r="/"===r.charAt(0)?r:(u.imagePath||"")+r,p.push("")):p.push(""):r.text?p.push(""+g.escapeHtml(r.text)+""):r.html?p.push(""+r.html+""):p.push("")),o="",u.renderTitle&&(o=u.renderTitle.call(c,{type:"renderTitle"},e)||""),o||(!0===(a=g.evalOption("tooltip",d,d,u,null))&&(a=d.title),o=""+(u.escapeTitles?g.escapeHtml(d.title):d.title)+""),p.push(o),d.span.innerHTML=p.join(""),this.nodeRenderStatus(e),u.enhanceTitle&&(e.$title=C(">span.fancytree-title",d.span),o=u.enhanceTitle.call(c,{type:"enhanceTitle"},e)||""))},nodeRenderStatus:function(e){var t,n=e.node,i=e.tree,r=e.options,o=n.hasChildren(),s=n.isLastSibling(),a=r.aria,l=r._classNames,d=[],c=n[i.statusClassPropName];c&&!1!==i._enableUpdate&&(a&&(t=C(n.tr||n.li)),d.push(l.node),i.activeNode===n&&d.push(l.active),i.focusNode===n&&d.push(l.focused),n.expanded&&d.push(l.expanded),a&&(!1===o?t.removeAttr("aria-expanded"):t.attr("aria-expanded",Boolean(n.expanded))),n.folder&&d.push(l.folder),!1!==o&&d.push(l.hasChildren),s&&d.push(l.lastsib),n.lazy&&null==n.children&&d.push(l.lazy),n.partload&&d.push(l.partload),n.partsel&&d.push(l.partsel),g.evalOption("unselectable",n,n,r,!1)&&d.push(l.unselectable),n._isLoading&&d.push(l.loading),n._error&&d.push(l.error),n.statusNodeType&&d.push(l.statusNodePrefix+n.statusNodeType),n.selected?(d.push(l.selected),a&&t.attr("aria-selected",!0)):a&&t.attr("aria-selected",!1),n.extraClasses&&d.push(n.extraClasses),!1===o?d.push(l.combinedExpanderPrefix+"n"+(s?"l":"")):d.push(l.combinedExpanderPrefix+(n.expanded?"e":"c")+(n.lazy&&null==n.children?"d":"")+(s?"l":"")),d.push(l.combinedIconPrefix+(n.expanded?"e":"c")+(n.folder?"f":"")),c.className=d.join(" "),n.li&&C(n.li).toggleClass(l.lastsib,s))},nodeSetActive:function(e,t,n){n=n||{};var i,r=e.node,o=e.tree,s=e.options,a=!0===n.noEvents,l=!0===n.noFocus,d=!1!==n.scrollIntoView;return r===o.activeNode===(t=!1!==t)?S(r):t&&!a&&!1===this._triggerNodeEvent("beforeActivate",r,e.originalEvent)?E(r,["rejected"]):(t?(o.activeNode&&(w(o.activeNode!==r,"node was active (inconsistency)"),i=C.extend({},e,{node:o.activeNode}),o.nodeSetActive(i,!1),w(null===o.activeNode,"deactivate was out of sync?")),s.activeVisible&&r.makeVisible({scrollIntoView:d}),o.activeNode=r,o.nodeRenderStatus(e),l||o.nodeSetFocus(e),a||o._triggerNodeEvent("activate",r,e.originalEvent)):(w(o.activeNode===r,"node was not active (inconsistency)"),o.activeNode=null,this.nodeRenderStatus(e),a||e.tree._triggerNodeEvent("deactivate",r,e.originalEvent)),S(r))},nodeSetExpanded:function(i,r,e){e=e||{};var t,n,o,s,a,l,d=i.node,c=i.tree,u=i.options,h=!0===e.noAnimation,f=!0===e.noEvents;if(r=!1!==r,C(d.li).hasClass(u._classNames.animating))return d.warn("setExpanded("+r+") while animating: ignored."),E(d,["recursion"]);if(d.expanded&&r||!d.expanded&&!r)return S(d);if(r&&!d.lazy&&!d.hasChildren())return S(d);if(!r&&d.getLevel()ul.fancytree-container").empty(),t.rootNode.children=null,t._callHook("treeStructureChanged",e,"clear")},treeCreate:function(e){},treeDestroy:function(e){this.$div.find(">ul.fancytree-container").remove(),this.$source&&this.$source.removeClass("fancytree-helper-hidden")},treeInit:function(e){var n=e.tree,i=n.options;n.$container.attr("tabindex",i.tabindex),C.each(p,function(e,t){void 0!==i[t]&&(n.info("Move option "+t+" to tree"),n[t]=i[t],delete i[t])}),i.checkboxAutoHide&&n.$container.addClass("fancytree-checkbox-auto-hide"),i.rtl?n.$container.attr("DIR","RTL").addClass("fancytree-rtl"):n.$container.removeAttr("DIR").removeClass("fancytree-rtl"),i.aria&&(n.$container.attr("role","tree"),1!==i.selectMode&&n.$container.attr("aria-multiselectable",!0)),this.treeLoad(e)},treeLoad:function(e,t){var n,i,r,o=e.tree,s=e.widget.element,a=C.extend({},e,{node:this.rootNode});if(o.rootNode.children&&this.treeClear(e),t=t||this.options.source)"string"==typeof t&&C.error("Not implemented");else switch(i=s.data("type")||"html"){case"html":(r=s.find(">ul").not(".fancytree-container").first()).length?(r.addClass("ui-fancytree-source fancytree-helper-hidden"),t=C.ui.fancytree.parseHtml(r),this.data=C.extend(this.data,A(r))):(g.warn("No `source` option was passed and container does not contain `
    `: assuming `source: []`."),t=[]);break;case"json":t=C.parseJSON(s.text()),s.contents().filter(function(){return 3===this.nodeType}).remove(),C.isPlainObject(t)&&(w(C.isArray(t.children),"if an object is passed as source, it must contain a 'children' array (all other properties are added to 'tree.data')"),t=(n=t).children,delete n.children,C.each(p,function(e,t){void 0!==n[t]&&(o[t]=n[t],delete n[t])}),C.extend(o.data,n));break;default:C.error("Invalid data-type: "+i)}return o._triggerTreeEvent("preInit",null),this.nodeLoadChildren(a,t).done(function(){o._callHook("treeStructureChanged",e,"loadChildren"),o.render(),3===e.options.selectMode&&o.rootNode.fixSelection3FromEndNodes(),o.activeNode&&o.options.activeVisible&&o.activeNode.makeVisible(),o._triggerTreeEvent("init",null,{status:!0})}).fail(function(){o.render(),o._triggerTreeEvent("init",null,{status:!1})})},treeRegisterNode:function(e,t,n){e.tree._callHook("treeStructureChanged",e,t?"addNode":"removeNode")},treeSetFocus:function(e,t,n){var i;(t=!1!==t)!==this.hasFocus()&&(!(this._hasFocus=t)&&this.focusNode?this.focusNode.setFocus(!1):!t||n&&n.calledByNode||C(this.$container).focus(),this.$container.toggleClass("fancytree-treefocus",t),this._triggerTreeEvent(t?"focusTree":"blurTree"),t&&!this.activeNode&&(i=this._lastMousedownNode||this.getFirstChild())&&i.setFocus())},treeSetOption:function(e,t,n){var i=e.tree,r=!0,o=!1,s=!1;switch(t){case"aria":case"checkbox":case"icon":case"minExpandLevel":case"tabindex":s=o=!0;break;case"checkboxAutoHide":i.$container.toggleClass("fancytree-checkbox-auto-hide",!!n);break;case"escapeTitles":case"tooltip":s=!0;break;case"rtl":!1===n?i.$container.removeAttr("DIR").removeClass("fancytree-rtl"):i.$container.attr("DIR","RTL").addClass("fancytree-rtl"),s=!0;break;case"source":r=!1,i._callHook("treeLoad",i,n),s=!0}i.debug("set option "+t+"="+n+" <"+typeof n+">"),r&&(this.widget._super?this.widget._super.call(this.widget,t,n):C.Widget.prototype._setOption.call(this.widget,t,n)),o&&i._callHook("treeCreate",i),s&&i.render(!0,!1)},treeStructureChanged:function(e,t){}}),C.widget("ui.fancytree",{options:{activeVisible:!0,ajax:{type:"GET",cache:!1,dataType:"json"},aria:!0,autoActivate:!0,autoCollapse:!1,autoScroll:!1,checkbox:!1,clickFolderMode:4,copyFunctionsToData:!1,debugLevel:null,disabled:!1,enableAspx:42,escapeTitles:!1,extensions:[],focusOnSelect:!1,generateIds:!1,icon:!0,idPrefix:"ft_",keyboard:!0,keyPathSeparator:"/",minExpandLevel:1,nodata:!0,quicksearch:!1,rtl:!1,scrollOfs:{top:0,bottom:0},scrollParent:null,selectMode:2,strings:{loading:"Loading...",loadError:"Load error!",moreData:"More...",noData:"No data."},tabindex:"0",titlesTabbable:!1,toggleEffect:{effect:"slideToggle",duration:200},tooltip:!1,treeId:null,_classNames:{active:"fancytree-active",animating:"fancytree-animating",combinedExpanderPrefix:"fancytree-exp-",combinedIconPrefix:"fancytree-ico-",error:"fancytree-error",expanded:"fancytree-expanded",focused:"fancytree-focused",folder:"fancytree-folder",hasChildren:"fancytree-has-children",lastsib:"fancytree-lastsib",lazy:"fancytree-lazy",loading:"fancytree-loading",node:"fancytree-node",partload:"fancytree-partload",partsel:"fancytree-partsel",radio:"fancytree-radio",selected:"fancytree-selected",statusNodePrefix:"fancytree-statusnode-",unselectable:"fancytree-unselectable"},lazyLoad:null,postProcess:null},_deprecationWarning:function(e){var t=this.tree;t&&3<=t.options.debugLevel&&t.warn("$().fancytree('"+e+"') is deprecated (see https://wwwendt.de/tech/fancytree/doc/jsdoc/Fancytree_Widget.html")},_create:function(){this.tree=new D(this),this.$source=this.source||"json"===this.element.data("type")?this.element:this.element.find(">ul").first();var e,t,n,i=this.options,r=i.extensions;this.tree;for(n=0;n element.");else{if(n){if(o._getExpiringValue("focusin"))return void o.debug("Ignored double focusin.");o._setExpiringValue("focusin",!0,50),t||(t=o._getExpiringValue("mouseDownNode"))&&o.debug("Reconstruct mouse target for focusin from recent event.")}t?o._callHook("nodeSetFocus",o._makeHookContext(t,e),n):o.tbody&&C(e.target).parents("table.fancytree-container > thead").length?o.debug("Ignore focus event outside table body.",e):o._callHook("treeSetFocus",o,n)}}).on("selectstart"+e,"span.fancytree-title",function(e){e.preventDefault()}).on("keydown"+e,function(e){if(a.disabled||!1===a.keyboard)return!0;var t,n=o.focusNode,i=o._makeHookContext(n||o,e),r=o.phase;try{return o.phase="userEvent","preventNav"===(t=n?o._triggerNodeEvent("keydown",n,e):o._triggerTreeEvent("keydown",e))?t=!0:!1!==t&&(t=o._callHook("nodeKeydown",i)),t}finally{o.phase=r}}).on("mousedown"+e,function(e){var t=g.getEventTarget(e);o._lastMousedownNode=t?t.node:null,o._setExpiringValue("mouseDownNode",o._lastMousedownNode)}).on("click"+e+" dblclick"+e,function(e){if(a.disabled)return!0;var t,n=g.getEventTarget(e),i=n.node,r=s.tree,o=r.phase;if(!i)return!0;t=r._makeHookContext(i,e);try{switch(r.phase="userEvent",e.type){case"click":return t.targetType=n.type,i.isPagingNode()?!0===r._triggerNodeEvent("clickPaging",t,e):!1!==r._triggerNodeEvent("click",t,e)&&r._callHook("nodeClick",t);case"dblclick":return t.targetType=n.type,!1!==r._triggerNodeEvent("dblclick",t,e)&&r._callHook("nodeDblclick",t)}}finally{r.phase=o}})},getActiveNode:function(){return this._deprecationWarning("getActiveNode"),this.tree.activeNode},getNodeByKey:function(e){return this._deprecationWarning("getNodeByKey"),this.tree.getNodeByKey(e)},getRootNode:function(){return this._deprecationWarning("getRootNode"),this.tree.rootNode},getTree:function(){return this._deprecationWarning("getTree"),this.tree}}),g=C.ui.fancytree,C.extend(C.ui.fancytree,{version:"2.37.0",buildType: "production",debugLevel: 3,_nextId:1,_nextNodeKey:1,_extensions:{},_FancytreeClass:D,_FancytreeNodeClass:F,jquerySupports:{positionMyOfs:function(e,t,n,i){var r,o,s,a=C.map(C.trim(e).split("."),function(e){return parseInt(e,10)}),l=C.map(Array.prototype.slice.call(arguments,1),function(e){return parseInt(e,10)});for(r=0;rli"),g=[];return e.each(function(){var e,t,n=C(this),i=n.find(">span",this).first(),r=i.length?null:n.find(">a").first(),o={tooltip:null,data:{}};for(i.length?o.title=i.html():r&&r.length?(o.title=r.html(),o.data.href=r.attr("href"),o.data.target=r.attr("target"),o.tooltip=r.attr("title")):(o.title=n.html(),0<=(u=o.title.search(/
      ul").first()).length?o.children=C.ui.fancytree.parseHtml(s):o.children=o.lazy?void 0:null,g.push(o)}),g},registerExtension:function(e){w(null!=e.name,"extensions must have a `name` property."),w(null!=e.version,"extensions must have a `version` property."),C.ui.fancytree._extensions[e.name]=e},unescapeHtml:function(e){var t=document.createElement("div");return t.innerHTML=e,0===t.childNodes.length?"":t.childNodes[0].nodeValue},warn:function(e){2<=C.ui.fancytree.debugLevel&&d("warn",arguments)}}),C.ui.fancytree}function w(e,t){e||(t=t?": "+t:"",C.error("Fancytree assertion failed"+t))}function d(e,t){var n,i,r=window.console?window.console[e]:null;if(r)try{r.apply(window.console,t)}catch(e){for(i="",n=0;nul.fancytree-container").remove();var t,n={tree:this};this.rootNode=new F(n,{title:"root",key:"root_"+this._id,children:null,expanded:!0}),this.rootNode.parent=null,t=C("
        ",{id:"ft-id-"+this._id,class:"ui-fancytree fancytree-container fancytree-plain"}).appendTo(this.$div),this.$container=t,this.rootNode.ul=t[0],null==this.options.debugLevel&&(this.options.debugLevel=g.debugLevel)}C.ui.fancytree.warn("Fancytree: ignored duplicate include")},"function"==typeof define&&define.amd?define(["jquery","./jquery.fancytree.ui-deps"],t):"object"==typeof module&&module.exports?(require("./jquery.fancytree.ui-deps"),module.exports=t(require("jquery"))):t(jQuery),n=function(o){"use strict";return o.ui.fancytree._FancytreeClass.prototype.countSelected=function(e){this.options;return this.getSelectedNodes(e).length},o.ui.fancytree._FancytreeNodeClass.prototype.updateCounters=function(){var e=this,t=o("span.fancytree-childcounter",e.span),n=e.tree.options.childcounter,i=e.countChildren(n.deep);!(e.data.childCounter=i)&&n.hideZeros||e.isExpanded()&&n.hideExpanded?t.remove():(t.length||(t=o("").appendTo(o("span.fancytree-icon,span.fancytree-custom-icon",e.span))),t.text(i)),!n.deep||e.isTopLevel()||e.isRootNode()||e.parent.updateCounters()},o.ui.fancytree.prototype.widgetMethod1=function(e){this.tree;return e},o.ui.fancytree.registerExtension({name:"childcounter",version:"2.37.0",options:{deep:!0,hideZeros:!0,hideExpanded:!1},foo:42,_appendCounter:function(e){},treeInit:function(e){e.options,e.options.childcounter;this._superApply(arguments),this.$container.addClass("fancytree-ext-childcounter")},treeDestroy:function(e){this._superApply(arguments)},nodeRenderTitle:function(e,t){var n=e.node,i=e.options.childcounter,r=null==n.data.childCounter?n.countChildren(i.deep):+n.data.childCounter;this._super(e,t),!r&&i.hideZeros||n.isExpanded()&&i.hideExpanded||o("span.fancytree-icon,span.fancytree-custom-icon",n.span).append(o("").text(r))},nodeSetExpanded:function(e,t,n){var i=e.tree;e.node;return this._superApply(arguments).always(function(){i.nodeRenderTitle(e)})}}),o.ui.fancytree},"function"==typeof define&&define.amd?define(["jquery","./jquery.fancytree"],n):"object"==typeof module&&module.exports?(require("./jquery.fancytree"),module.exports=n(require("jquery"))):n(jQuery),i=function(h){"use strict";function f(e,t){e||(t=t?": "+t:"",h.error("Assertion failed"+t))}function r(e,t,n){for(var i,r,o=3&e.length,s=e.length-o,a=n,l=3432918353,d=461845907,c=0;c>>16)*l&65535)<<16)&4294967295)<<15|r>>>17))*d+(((r>>>16)*d&65535)<<16)&4294967295)<<13|a>>>19))+((5*(a>>>16)&65535)<<16)&4294967295))+((58964+(i>>>16)&65535)<<16);switch(r=0,o){case 3:r^=(255&e.charCodeAt(c+2))<<16;case 2:r^=(255&e.charCodeAt(c+1))<<8;case 1:a^=r=(65535&(r=(r=(65535&(r^=255&e.charCodeAt(c)))*l+(((r>>>16)*l&65535)<<16)&4294967295)<<15|r>>>17))*d+(((r>>>16)*d&65535)<<16)&4294967295}return a^=e.length,a=2246822507*(65535&(a^=a>>>16))+((2246822507*(a>>>16)&65535)<<16)&4294967295,a=3266489909*(65535&(a^=a>>>13))+((3266489909*(a>>>16)&65535)<<16)&4294967295,a^=a>>>16,t?("0000000"+(a>>>0).toString(16)).substr(-8):a>>>0}return h.ui.fancytree._FancytreeNodeClass.prototype.getCloneList=function(e){var t,n=this.tree,i=n.refMap[this.refKey]||null,r=n.keyMap;return i&&(t=this.key,e?i=h.map(i,function(e){return r[e]}):(i=h.map(i,function(e){return e===t?null:r[e]})).length<1&&(i=null)),i},h.ui.fancytree._FancytreeNodeClass.prototype.isClone=function(){var e=this.refKey||null,t=e&&this.tree.refMap[e]||null;return!!(t&&1 "+c.getPath(!0);o.error(u),h.error(u)}s[l]=n,d&&((i=a[d])?(i.push(l),2===i.length&&e.options.clones.highlightClones&&s[i[0]].renderStatus()):a[d]=[l])}else null==s[l]&&h.error("clones.treeRegisterNode: node.key not registered: "+n.key),delete s[l],d&&(i=a[d])&&((r=i.length)<=1?(f(1===r),f(i[0]===l),delete a[d]):(!function(e,t){var n;for(n=e.length-1;0<=n;n--)if(e[n]===t)return e.splice(n,1)}(i,l),2===r&&e.options.clones.highlightClones&&s[i[0]].renderStatus()));return this._super(e,t,n)},nodeRenderStatus:function(e){var t,n,i=e.node;return n=this._super(e),e.options.clones.highlightClones&&(t=h(i[e.tree.statusClassPropName])).length&&i.isClone()&&t.addClass("fancytree-clone"),n},nodeSetActive:function(e,n,t){var i,r=e.tree.statusClassPropName,o=e.node;return i=this._superApply(arguments),e.options.clones.highlightActiveClones&&o.isClone()&&h.each(o.getCloneList(!0),function(e,t){h(t[r]).toggleClass("fancytree-active-clone",!1!==n)}),i}}),h.ui.fancytree},"function"==typeof define&&define.amd?define(["jquery","./jquery.fancytree"],i):"object"==typeof module&&module.exports?(require("./jquery.fancytree"),module.exports=i(require("jquery"))):i(jQuery),r=function(w){"use strict";var l,d,N=w.ui.fancytree,c=/Mac/.test(navigator.platform),u="fancytree-drag-source",h="fancytree-drag-remove",S="fancytree-drop-accept",E="fancytree-drop-after",T="fancytree-drop-before",A="fancytree-drop-over",O="fancytree-drop-reject",P="fancytree-drop-target",y="application/x-fancytree-node",F=null,v=null,m=null,f=null,D=null,p=null,g=null,b=null,L=null,x=null;function C(){m=v=p=b=g=x=D=null,f&&f.removeClass(u+" "+h),f=null,F&&F.hide(),d&&(d.remove(),d=null)}function j(e){return 0===e?"":0 "+o),p=o),t.isMove="move"===t.dropEffect,t.files=a.files||[]}function R(e,t,n){var i=t.tree,r=t.dataTransfer;return"dragstart"!==e.type&&b!==t.effectAllowed&&i.warn("effectAllowed should only be changed in dragstart event: "+e.type+": data.effectAllowed changed from "+b+" -> "+t.effectAllowed),!1===n&&(i.info("applyDropEffectCallback: allowDrop === false"),t.effectAllowed="none",t.dropEffect="none"),t.isMove="move"===t.dropEffect,"dragstart"===e.type&&(b=t.effectAllowed,g=t.dropEffect),r.effectAllowed=b,r.dropEffect=g}function _(e,t){var n,i,r,o,s,a,l,d,c;if(t.options.dnd5.scroll&&(n=t.tree,i=e,s=n.options.dnd5,a=n.$scrollParent[0],l=s.scrollSensitivity,d=s.scrollSpeed,c=0,a!==document&&"HTML"!==a.tagName?(r=n.$scrollParent.offset(),o=a.scrollTop,r.top+a.offsetHeight-i.pageYd.autoExpandMS)||u.isLoading()||d.dragExpand&&!1===d.dragExpand(u,f)||u.setExpanded():x=Date.now():x=null;break;case"dragleave":if(!u){l.debug("Ignore non-node "+e.type+": "+e.target.tagName+"."+e.target.className);break}if(!w(u.span).hasClass(A)){u.debug("Ignore dragleave (multi).");break}w(u.span).removeClass(A+" "+S+" "+O),u.scheduleAction("cancel"),d.dragLeave(u,f),F.hide();break;case"drop":if(0<=w.inArray(y,h.types)&&(n=h.getData(y),l.info(e.type+": getData('application/x-fancytree-node'): '"+n+"'")),n||(n=h.getData("text"),l.info(e.type+": getData('text'): '"+n+"'")),n)try{void 0!==(t=JSON.parse(n)).title&&(f.otherNodeData=t)}catch(e){}l.debug(e.type+": nodeData: '"+n+"', otherNodeData: ",f.otherNodeData),w(u.span).removeClass(A+" "+S+" "+O),f.hitMode=L,I(e,f),f.isCancelled=!L;var p=v&&v.span,g=v&&v.tree;d.dragDrop(u,f),e.preventDefault(),p&&!document.body.contains(p)&&(g===l?(l.debug("Drop handler removed source element: generating dragEnd."),d.dragEnd(v,f)):l.warn("Drop handler removed source element: dragend event may be lost.")),C()}if(c)return e.preventDefault(),!1}return w.ui.fancytree.getDragNodeList=function(){return m||[]},w.ui.fancytree.getDragNode=function(){return v},w.ui.fancytree.registerExtension({name:"dnd5",version:"2.37.0",options:{autoExpandMS:1500,dropMarkerInsertOffsetX:-16,dropMarkerOffsetX:-24,dropMarkerParent:"body",multiSource:!1,effectAllowed:"all",dropEffectDefault:"move",preventForeignNodes:!1,preventLazyParents:!0,preventNonNodes:!1,preventRecursion:!0,preventSameParent:!1,preventVoidMoves:!0,scroll:!0,scrollSensitivity:20,scrollSpeed:5,setTextTypeJson:!1,dragStart:null,dragDrag:w.noop,dragEnd:w.noop,dragEnter:null,dragOver:w.noop,dragExpand:w.noop,dragDrop:w.noop,dragLeave:w.noop},treeInit:function(e){var t,n=e.tree,i=e.options,r=i.glyph||null,o=i.dnd5;0<=w.inArray("dnd",i.extensions)&&w.error("Extensions 'dnd' and 'dnd5' are mutually exclusive."),o.dragStop&&w.error("dragStop is not used by ext-dnd5. Use dragEnd instead."),null!=o.preventRecursiveMoves&&w.error("preventRecursiveMoves was renamed to preventRecursion."),o.dragStart&&N.overrideMethod(e.options,"createNode",function(e,t){this._super.apply(this,arguments),t.node.span?t.node.span.draggable=!0:t.node.warn("Cannot add `draggable`: no span tag")}),this._superApply(arguments),this.$container.addClass("fancytree-ext-dnd5"),t=w("").appendTo(this.$container),this.$scrollParent=t.scrollParent(),t.remove(),(F=w("#fancytree-drop-marker")).length||(F=w("
        ").hide().css({"z-index":1e3,"pointer-events":"none"}).prependTo(o.dropMarkerParent),r&&N.setSpanIcon(F[0],r.map._addClass,r.map.dropMarker)),F.toggleClass("fancytree-rtl",!!i.rtl),o.dragStart&&n.$container.on("dragstart drag dragend",function(e){var t,n=this,i=n.options.dnd5,r=N.getNode(e),o=e.dataTransfer||e.originalEvent.dataTransfer,s={tree:n,node:r,options:n.options,originalEvent:e.originalEvent,widget:n.widget,dataTransfer:o,useDefaultImage:!0,dropEffect:void 0,dropEffectSuggested:void 0,effectAllowed:void 0,files:void 0,isCancelled:void 0,isMove:void 0};switch(e.type){case"dragstart":if(!r)return n.info("Ignored dragstart on a non-node."),!1;v=r,m=!1===i.multiSource?[r]:!0===i.multiSource?r.isSelected()?n.getSelectedNodes():[r]:i.multiSource(r,s),(f=w(w.map(m,function(e){return e.span}))).addClass(u);var a=r.toDict();a.treeId=r.tree._id,t=JSON.stringify(a);try{o.setData(y,t),o.setData("text/html",w(r.span).html()),o.setData("text/plain",r.title)}catch(e){n.warn("Could not set data (IE only accepts 'text') - "+e)}return i.setTextTypeJson?o.setData("text",t):o.setData("text",r.title),I(e,s),!1===i.dragStart(r,s)?(C(),!1):(R(e,s),d=null,s.useDefaultImage&&(l=w(r.span).find(".fancytree-title"),m&&1").text("+"+(m.length-1)).appendTo(l)),o.setDragImage&&o.setDragImage(l[0],-10,-10)),!0);case"drag":I(e,s),i.dragDrag(r,s),R(e,s),f.toggleClass(h,s.isMove);break;case"dragend":I(e,s),C(),s.isCancelled=!L,i.dragEnd(r,s,!L)}}.bind(n)),o.dragEnter&&n.$container.on("dragenter dragover dragleave drop",s.bind(n))}}),w.ui.fancytree},"function"==typeof define&&define.amd?define(["jquery","./jquery.fancytree"],r):"object"==typeof module&&module.exports?(require("./jquery.fancytree"),module.exports=r(require("jquery"))):r(jQuery),o=function(d){"use strict";var t=/Mac/.test(navigator.platform),c=d.ui.fancytree.escapeHtml,a=d.ui.fancytree.unescapeHtml;return d.ui.fancytree._FancytreeNodeClass.prototype.editStart=function(){var t,n=this,e=this.tree,i=e.ext.edit,r=e.options.edit,o=d(".fancytree-title",n.span),s={node:n,tree:e,options:e.options,isNew:d(n[e.statusClassPropName]).hasClass("fancytree-edit-new"),orgTitle:n.title,input:null,dirty:!1};if(!1===r.beforeEdit.call(n,{type:"beforeEdit"},s))return!1;d.ui.fancytree.assert(!i.currentNode,"recursive edit"),i.currentNode=this,i.eventData=s,e.widget._unbind(),i.lastDraggableAttrValue=n.span.draggable,i.lastDraggableAttrValue&&(n.span.draggable=!1),d(document).on("mousedown.fancytree-edit",function(e){d(e.target).hasClass("fancytree-edit-input")||n.editEnd(!0,e)}),t=d("",{class:"fancytree-edit-input",type:"text",value:e.options.escapeTitles?s.orgTitle:a(s.orgTitle)}),i.eventData.input=t,null!=r.adjustWidthOfs&&t.width(o.width()+r.adjustWidthOfs),null!=r.inputCss&&t.css(r.inputCss),o.html(t),t.focus().change(function(e){t.addClass("fancytree-edit-dirty")}).on("keydown",function(e){switch(e.which){case d.ui.keyCode.ESCAPE:n.editEnd(!1,e);break;case d.ui.keyCode.ENTER:return n.editEnd(!0,e),!1}e.stopPropagation()}).blur(function(e){return n.editEnd(!0,e)}),r.edit.call(n,{type:"edit"},s)},d.ui.fancytree._FancytreeNodeClass.prototype.editEnd=function(e,t){var n,i=this,r=this.tree,o=r.ext.edit,s=o.eventData,a=r.options.edit,l=d(".fancytree-title",i.span).find("input.fancytree-edit-input");return a.trim&&l.val(d.trim(l.val())),n=l.val(),s.dirty=n!==i.title,s.originalEvent=t,!1===e?s.save=!1:s.isNew?s.save=""!==n:s.save=s.dirty&&""!==n,!1!==a.beforeClose.call(i,{type:"beforeClose"},s)&&((!s.save||!1!==a.save.call(i,{type:"save"},s))&&(l.removeClass("fancytree-edit-dirty").off(),d(document).off(".fancytree-edit"),s.save?(i.setTitle(r.options.escapeTitles?n:c(n)),i.setFocus()):s.isNew?(i.remove(),i=s.node=null,o.relatedNode.setFocus()):(i.renderTitle(),i.setFocus()),o.eventData=null,o.currentNode=null,o.relatedNode=null,r.widget._bind(),i&&o.lastDraggableAttrValue&&(i.span.draggable=!0),r.$container.get(0).focus({preventScroll:!0}),s.input=null,a.close.call(i,{type:"close"},s),!0))},d.ui.fancytree._FancytreeNodeClass.prototype.editCreateNode=function(e,t){var n,i=this.tree,r=this;e=e||"child",null==t?t={title:""}:"string"==typeof t?t={title:t}:d.ui.fancytree.assert(d.isPlainObject(t)),"child"!==e||this.isExpanded()||!1===this.hasChildren()?((n=this.addNode(t,e)).match=!0,d(n[i.statusClassPropName]).removeClass("fancytree-hide").addClass("fancytree-match"),n.makeVisible().done(function(){d(n[i.statusClassPropName]).addClass("fancytree-edit-new"),r.tree.ext.edit.relatedNode=r,n.editStart()})):this.setExpanded().done(function(){r.editCreateNode(e,t)})},d.ui.fancytree._FancytreeClass.prototype.isEditing=function(){return this.ext.edit?this.ext.edit.currentNode:null},d.ui.fancytree._FancytreeNodeClass.prototype.isEditing=function(){return!!this.tree.ext.edit&&this.tree.ext.edit.currentNode===this},d.ui.fancytree.registerExtension({name:"edit",version:"2.37.0",options:{adjustWidthOfs:4,allowEmpty:!1,inputCss:{minWidth:"3em"},triggerStart:["f2","mac+enter","shift+click"],trim:!0,beforeClose:d.noop,beforeEdit:d.noop,close:d.noop,edit:d.noop,save:d.noop},currentNode:null,treeInit:function(e){var i=e.tree;this._superApply(arguments),this.$container.addClass("fancytree-ext-edit").on("fancytreebeforeupdateviewport",function(e,t){var n=i.isEditing();n&&(n.info("Cancel edit due to scroll event."),n.editEnd(!1,e))})},nodeClick:function(e){var t=d.ui.fancytree.eventToString(e.originalEvent),n=e.options.edit.triggerStart;return"shift+click"===t&&0<=d.inArray("shift+click",n)&&e.originalEvent.shiftKey?(e.node.editStart(),!1):"click"===t&&0<=d.inArray("clickActive",n)&&e.node.isActive()&&!e.node.isEditing()&&d(e.originalEvent.target).hasClass("fancytree-title")?(e.node.editStart(),!1):this._superApply(arguments)},nodeDblclick:function(e){return 0<=d.inArray("dblclick",e.options.edit.triggerStart)?(e.node.editStart(),!1):this._superApply(arguments)},nodeKeydown:function(e){switch(e.originalEvent.which){case 113:if(0<=d.inArray("f2",e.options.edit.triggerStart))return e.node.editStart(),!1;break;case d.ui.keyCode.ENTER:if(0<=d.inArray("mac+enter",e.options.edit.triggerStart)&&t)return e.node.editStart(),!1}return this._superApply(arguments)}}),d.ui.fancytree},"function"==typeof define&&define.amd?define(["jquery","./jquery.fancytree"],o):"object"==typeof module&&module.exports?(require("./jquery.fancytree"),module.exports=o(require("jquery"))):o(jQuery),s=function(y){"use strict";var v="__not_found__",m=y.ui.fancytree.escapeHtml;function b(e){return(e+"").replace(/([.?*+^$[\]\\(){}|-])/g,"\\$1")}return y.ui.fancytree._FancytreeClass.prototype._applyFilterImpl=function(i,r,e){var t,n,o,s,a,l,d=0,c=this.options,u=c.escapeTitles,h=c.autoCollapse,f=y.extend({},c.filter,e),p="hide"===f.mode,g=!!f.leavesOnly&&!r;if("string"==typeof i){if(""===i)return this.warn("Fancytree passing an empty string as a filter is handled as clearFilter()."),void this.clearFilter();t=f.fuzzy?i.split("").reduce(function(e,t){return e+"[^"+t+"]*"+t}):b(i),o=new RegExp(".*"+t+".*","i"),s=new RegExp(b(i),"gi"),i=function(e){if(!e.title)return!1;var t,n=u?e.title:0<=(t=e.title).indexOf(">")?y("
        ").html(t).text():t,i=!!o.test(n);return i&&f.highlight&&(e.titleWithHighlight=u?(a=n.replace(s,function(e){return"\ufff7"+e+"\ufff8"}),m(a).replace(/\uFFF7/g,"").replace(/\uFFF8/g,"")):n.replace(s,function(e){return""+e+""})),i}}return this.enableFilter=!0,this.lastFilterArgs=arguments,l=this.enableUpdate(!1),this.$div.addClass("fancytree-ext-filter"),p?this.$div.addClass("fancytree-ext-filter-hide"):this.$div.addClass("fancytree-ext-filter-dimm"),this.$div.toggleClass("fancytree-ext-filter-hide-expanders",!!f.hideExpanders),this.rootNode.subMatchCount=0,this.visit(function(e){delete e.match,delete e.titleWithHighlight,e.subMatchCount=0}),(n=this.getRootNode()._findDirectChild(v))&&n.remove(),c.autoCollapse=!1,this.visit(function(t){if(!g||null==t.children){var e=i(t),n=!1;if("skip"===e)return t.visit(function(e){e.match=!1},!0),"skip";e||!r&&"branch"!==e||!t.parent.match||(n=e=!0),e&&(d++,t.match=!0,t.visitParents(function(e){e!==t&&(e.subMatchCount+=1),!f.autoExpand||n||e.expanded||(e.setExpanded(!0,{noAnimation:!0,noEvents:!0,scrollIntoView:!1}),e._filterAutoExpanded=!0)},!0))}}),c.autoCollapse=h,0===d&&f.nodata&&p&&(n=f.nodata,y.isFunction(n)&&(n=n()),!0===n?n={}:"string"==typeof n&&(n={title:n}),n=y.extend({statusNodeType:"nodata",key:v,title:this.options.strings.noData},n),this.getRootNode().addNode(n).match=!0),this._callHook("treeStructureChanged",this,"applyFilter"),this.enableUpdate(l),d},y.ui.fancytree._FancytreeClass.prototype.filterNodes=function(e,t){return"boolean"==typeof t&&(t={leavesOnly:t},this.warn("Fancytree.filterNodes() leavesOnly option is deprecated since 2.9.0 / 2015-04-19. Use opts.leavesOnly instead.")),this._applyFilterImpl(e,!1,t)},y.ui.fancytree._FancytreeClass.prototype.filterBranches=function(e,t){return this._applyFilterImpl(e,!0,t)},y.ui.fancytree._FancytreeClass.prototype.clearFilter=function(){var t,e=this.getRootNode()._findDirectChild(v),n=this.options.escapeTitles,i=this.options.enhanceTitle,r=this.enableUpdate(!1);e&&e.remove(),delete this.rootNode.match,delete this.rootNode.subMatchCount,this.visit(function(e){e.match&&e.span&&(t=y(e.span).find(">span.fancytree-title"),n?t.text(e.title):t.html(e.title),i&&i({type:"enhanceTitle"},{node:e,$title:t})),delete e.match,delete e.subMatchCount,delete e.titleWithHighlight,e.$subMatchBadge&&(e.$subMatchBadge.remove(),delete e.$subMatchBadge),e._filterAutoExpanded&&e.expanded&&e.setExpanded(!1,{noAnimation:!0,noEvents:!0,scrollIntoView:!1}),delete e._filterAutoExpanded}),this.enableFilter=!1,this.lastFilterArgs=null,this.$div.removeClass("fancytree-ext-filter fancytree-ext-filter-dimm fancytree-ext-filter-hide"),this._callHook("treeStructureChanged",this,"clearFilter"),this.enableUpdate(r)},y.ui.fancytree._FancytreeClass.prototype.isFilterActive=function(){return!!this.enableFilter},y.ui.fancytree._FancytreeNodeClass.prototype.isMatched=function(){return!(this.tree.enableFilter&&!this.match)},y.ui.fancytree.registerExtension({name:"filter",version:"2.37.0",options:{autoApply:!0,autoExpand:!1,counter:!0,fuzzy:!1,hideExpandedCounter:!0,hideExpanders:!1,highlight:!0,leavesOnly:!1,nodata:!0,mode:"dimm"},nodeLoadChildren:function(e,t){var n=e.tree;return this._superApply(arguments).done(function(){n.enableFilter&&n.lastFilterArgs&&e.options.filter.autoApply&&n._applyFilterImpl.apply(n,n.lastFilterArgs)})},nodeSetExpanded:function(e,t,n){var i=e.node;return delete i._filterAutoExpanded,!t&&e.options.filter.hideExpandedCounter&&i.$subMatchBadge&&i.$subMatchBadge.show(),this._superApply(arguments)},nodeRenderStatus:function(e){var t,n=e.node,i=e.tree,r=e.options.filter,o=y(n.span).find("span.fancytree-title"),s=y(n[i.statusClassPropName]),a=e.options.enhanceTitle,l=e.options.escapeTitles;return t=this._super(e),s.length&&i.enableFilter&&(s.toggleClass("fancytree-match",!!n.match).toggleClass("fancytree-submatch",!!n.subMatchCount).toggleClass("fancytree-hide",!(n.match||n.subMatchCount)),!r.counter||!n.subMatchCount||n.isExpanded()&&r.hideExpandedCounter?n.$subMatchBadge&&n.$subMatchBadge.hide():(n.$subMatchBadge||(n.$subMatchBadge=y(""),y("span.fancytree-icon, span.fancytree-custom-icon",n.span).append(n.$subMatchBadge)),n.$subMatchBadge.show().text(n.subMatchCount)),!n.span||n.isEditing&&n.isEditing.call(n)||(n.titleWithHighlight?o.html(n.titleWithHighlight):l?o.text(n.title):o.html(n.title),a&&a({type:"enhanceTitle"},{node:n,$title:o}))),t}}),y.ui.fancytree},"function"==typeof define&&define.amd?define(["jquery","./jquery.fancytree"],s):"object"==typeof module&&module.exports?(require("./jquery.fancytree"),module.exports=s(require("jquery"))):s(jQuery),a=function(c){"use strict";var a=c.ui.fancytree,i={awesome3:{_addClass:"",checkbox:"icon-check-empty",checkboxSelected:"icon-check",checkboxUnknown:"icon-check icon-muted",dragHelper:"icon-caret-right",dropMarker:"icon-caret-right",error:"icon-exclamation-sign",expanderClosed:"icon-caret-right",expanderLazy:"icon-angle-right",expanderOpen:"icon-caret-down",loading:"icon-refresh icon-spin",nodata:"icon-meh",noExpander:"",radio:"icon-circle-blank",radioSelected:"icon-circle",doc:"icon-file-alt",docOpen:"icon-file-alt",folder:"icon-folder-close-alt",folderOpen:"icon-folder-open-alt"},awesome4:{_addClass:"fa",checkbox:"fa-square-o",checkboxSelected:"fa-check-square-o",checkboxUnknown:"fa-square fancytree-helper-indeterminate-cb",dragHelper:"fa-arrow-right",dropMarker:"fa-long-arrow-right",error:"fa-warning",expanderClosed:"fa-caret-right",expanderLazy:"fa-angle-right",expanderOpen:"fa-caret-down",loading:{html:""},nodata:"fa-meh-o",noExpander:"",radio:"fa-circle-thin",radioSelected:"fa-circle",doc:"fa-file-o",docOpen:"fa-file-o",folder:"fa-folder-o",folderOpen:"fa-folder-open-o"},awesome5:{_addClass:"",checkbox:"far fa-square",checkboxSelected:"far fa-check-square",checkboxUnknown:"fas fa-square fancytree-helper-indeterminate-cb",radio:"far fa-circle",radioSelected:"fas fa-circle",radioUnknown:"far fa-dot-circle",dragHelper:"fas fa-arrow-right",dropMarker:"fas fa-long-arrow-alt-right",error:"fas fa-exclamation-triangle",expanderClosed:"fas fa-caret-right",expanderLazy:"fas fa-angle-right",expanderOpen:"fas fa-caret-down",loading:"fas fa-spinner fa-pulse",nodata:"far fa-meh",noExpander:"",doc:"far fa-file",docOpen:"far fa-file",folder:"far fa-folder",folderOpen:"far fa-folder-open"},bootstrap3:{_addClass:"glyphicon",checkbox:"glyphicon-unchecked",checkboxSelected:"glyphicon-check",checkboxUnknown:"glyphicon-expand fancytree-helper-indeterminate-cb",dragHelper:"glyphicon-play",dropMarker:"glyphicon-arrow-right",error:"glyphicon-warning-sign",expanderClosed:"glyphicon-menu-right",expanderLazy:"glyphicon-menu-right",expanderOpen:"glyphicon-menu-down",loading:"glyphicon-refresh fancytree-helper-spin",nodata:"glyphicon-info-sign",noExpander:"",radio:"glyphicon-remove-circle",radioSelected:"glyphicon-ok-circle",doc:"glyphicon-file",docOpen:"glyphicon-file",folder:"glyphicon-folder-close",folderOpen:"glyphicon-folder-open"},material:{_addClass:"material-icons",checkbox:{text:"check_box_outline_blank"},checkboxSelected:{text:"check_box"},checkboxUnknown:{text:"indeterminate_check_box"},dragHelper:{text:"play_arrow"},dropMarker:{text:"arrow-forward"},error:{text:"warning"},expanderClosed:{text:"chevron_right"},expanderLazy:{text:"last_page"},expanderOpen:{text:"expand_more"},loading:{text:"autorenew",addClass:"fancytree-helper-spin"},nodata:{text:"info"},noExpander:{text:""},radio:{text:"radio_button_unchecked"},radioSelected:{text:"radio_button_checked"},doc:{text:"insert_drive_file"},docOpen:{text:"insert_drive_file"},folder:{text:"folder"},folderOpen:{text:"folder_open"}}};function l(e,t,n,i,r){var o=i.map,s=o[r],a=c(t),l=a.find(".fancytree-childcounter"),d=n+" "+(o._addClass||"");c.isFunction(s)&&(s=s.call(this,e,t,r)),"string"==typeof s?(t.innerHTML="",a.attr("class",d+" "+s).append(l)):s&&(s.text?t.textContent=""+s.text:s.html?t.innerHTML=s.html:t.innerHTML="",a.attr("class",d+" "+(s.addClass||"")).append(l))}return c.ui.fancytree.registerExtension({name:"glyph",version:"2.37.0",options:{preset:null,map:{}},treeInit:function(e){var t=e.tree,n=e.options.glyph;n.preset?(a.assert(!!i[n.preset],"Invalid value for `options.glyph.preset`: "+n.preset),n.map=c.extend({},i[n.preset],n.map)):t.warn("ext-glyph: missing `preset` option."),this._superApply(arguments),t.$container.addClass("fancytree-ext-glyph")},nodeRenderStatus:function(e){var t,n,i,r=e.node,o=c(r.span),s=e.options.glyph;return n=this._super(e),r.isRootNode()||((i=o.children(".fancytree-expander").get(0))&&l(r,i,"fancytree-expander",s,r.expanded&&r.hasChildren()?"expanderOpen":r.isUndefined()?"expanderLazy":r.hasChildren()?"expanderClosed":"noExpander"),(i=r.tr?c("td",r.tr).find(".fancytree-checkbox").get(0):o.children(".fancytree-checkbox").get(0))&&(t=a.evalOption("checkbox",r,r,s,!1),r.parent&&r.parent.radiogroup||"radio"===t?l(r,i,"fancytree-checkbox fancytree-radio",s,r.selected?"radioSelected":"radio"):l(r,i,"fancytree-checkbox",s,r.selected?"checkboxSelected":r.partsel?"checkboxUnknown":"checkbox")),(i=o.children(".fancytree-icon").get(0))&&l(r,i,"fancytree-icon",s,r.statusNodeType?r.statusNodeType:r.folder?r.expanded&&r.hasChildren()?"folderOpen":"folder":r.expanded?"docOpen":"doc")),n},nodeSetStatus:function(e,t,n,i){var r,o,s=e.options.glyph,a=e.node;return r=this._superApply(arguments),"error"!==t&&"loading"!==t&&"nodata"!==t||(a.parent?(o=c(".fancytree-expander",a.span).get(0))&&l(a,o,"fancytree-expander",s,t):(o=c(".fancytree-statusnode-"+t,a[this.nodeContainerAttrName]).find(".fancytree-icon").get(0))&&l(a,o,"fancytree-icon",s,t)),r}}),c.ui.fancytree},"function"==typeof define&&define.amd?define(["jquery","./jquery.fancytree"],a):"object"==typeof module&&module.exports?(require("./jquery.fancytree"),module.exports=a(require("jquery"))):a(jQuery),l=function(c){"use strict";var u=c.ui.keyCode,a={text:[u.UP,u.DOWN],checkbox:[u.UP,u.DOWN,u.LEFT,u.RIGHT],link:[u.UP,u.DOWN,u.LEFT,u.RIGHT],radiobutton:[u.UP,u.DOWN,u.LEFT,u.RIGHT],"select-one":[u.LEFT,u.RIGHT],"select-multiple":[u.LEFT,u.RIGHT]};function h(e,t){var n,i=null,r=0;return e.children().each(function(){if(t<=r)return i=c(this),!1;n=c(this).prop("colspan"),r+=n||1}),i}function l(e,t){var n,i,r,o,s,a,l=e.closest("td"),d=null;switch(t){case u.LEFT:d=l.prev();break;case u.RIGHT:d=l.next();break;case u.UP:case u.DOWN:for(n=l.parent(),r=n,s=l.get(0),a=0,r.children().each(function(){if(this===s)return!1;o=c(this).prop("colspan"),a+=o||1}),i=a;(n=t===u.UP?n.prev():n.next()).length&&(n.is(":hidden")||!(d=h(n,i))||!d.find(":input,a").length););}return d}return c.ui.fancytree.registerExtension({name:"gridnav",version:"2.37.0",options:{autofocusInput:!1,handleCursorKeys:!0},treeInit:function(i){this._requireExtension("table",!0,!0),this._superApply(arguments),this.$container.addClass("fancytree-ext-gridnav"),this.$container.on("focusin",function(e){var t,n=c.ui.fancytree.getNode(e.target);n&&!n.isActive()&&(t=i.tree._makeHookContext(n,e),i.tree._callHook("nodeSetActive",t,!0))})},nodeSetActive:function(e,t,n){var i=e.options.gridnav,r=e.node,o=e.originalEvent||{},s=c(o.target).is(":input");t=!1!==t,this._superApply(arguments),t&&(e.options.titlesTabbable?(s||(c(r.span).find("span.fancytree-title").focus(),r.setFocus()),e.tree.$container.attr("tabindex","-1")):i.autofocusInput&&!s&&c(r.tr||r.span).find(":input:enabled").first().focus())},nodeKeydown:function(e){var t,n,i,r=e.options.gridnav,o=e.originalEvent,s=c(o.target);return s.is(":input:enabled")?t=s.prop("type"):s.is("a")&&(t="link"),t&&r.handleCursorKeys?!((n=a[t])&&0<=c.inArray(o.which,n)&&(i=l(s,o.which))&&i.length)||(i.find(":input:enabled,a").focus(),!1):this._superApply(arguments)}}),c.ui.fancytree},"function"==typeof define&&define.amd?define(["jquery","./jquery.fancytree","./jquery.fancytree.table"],l):"object"==typeof module&&module.exports?(require("./jquery.fancytree.table"),module.exports=l(require("jquery"))):l(jQuery),d=function(s){"use strict";return s.ui.fancytree.registerExtension({name:"multi",version:"2.37.0",options:{allowNoSelect:!1,mode:"sameParent"},treeInit:function(e){this._superApply(arguments),this.$container.addClass("fancytree-ext-multi"),1===e.options.selectMode&&s.error("Fancytree ext-multi: selectMode: 1 (single) is not compatible.")},nodeClick:function(e){var t=e.tree,n=e.node,i=t.getActiveNode()||t.getFirstChild(),r="checkbox"===e.targetType,o="expander"===e.targetType;switch(s.ui.fancytree.eventToString(e.originalEvent)){case"click":if(o)break;r||(t.selectAll(!1),n.setSelected());break;case"shift+click":t.visitRows(function(e){if(e.setSelected(),e===n)return!1},{start:i,reverse:i.isBelowOf(n)});break;case"ctrl+click":case"meta+click":return void n.toggleSelected()}return this._superApply(arguments)},nodeKeydown:function(e){var t=e.tree,n=e.node,i=e.originalEvent;switch(s.ui.fancytree.eventToString(i)){case"up":case"down":t.selectAll(!1),n.navigate(i.which,!0),t.getActiveNode().setSelected();break;case"shift+up":case"shift+down":n.navigate(i.which,!0),t.getActiveNode().setSelected()}return this._superApply(arguments)}}),s.ui.fancytree},"function"==typeof define&&define.amd?define(["jquery","./jquery.fancytree"],d):"object"==typeof module&&module.exports?(require("./jquery.fancytree"),module.exports=d(require("jquery"))):d(jQuery),c=function(p){"use strict";var t=null,n=null,i=null,r=p.ui.fancytree.assert,u="active",g="expanded",h="focus",f="selected";try{r(window.localStorage&&window.localStorage.getItem),n={get:function(e){return window.localStorage.getItem(e)},set:function(e,t){window.localStorage.setItem(e,t)},remove:function(e){window.localStorage.removeItem(e)}}}catch(e){p.ui.fancytree.warn("Could not access window.localStorage",e)}try{r(window.sessionStorage&&window.sessionStorage.getItem),i={get:function(e){return window.sessionStorage.getItem(e)},set:function(e,t){window.sessionStorage.setItem(e,t)},remove:function(e){window.sessionStorage.removeItem(e)}}}catch(e){p.ui.fancytree.warn("Could not access window.sessionStorage",e)}return"function"==typeof Cookies?t={get:Cookies.get,set:function(e,t){Cookies.set(e,t,this.options.persist.cookie)},remove:Cookies.remove}:p&&"function"==typeof p.cookie&&(t={get:p.cookie,set:function(e,t){p.cookie.set(e,t,this.options.persist.cookie)},remove:p.removeCookie}),p.ui.fancytree._FancytreeClass.prototype.clearPersistData=function(e){var t=this.ext.persist,n=t.cookiePrefix;0<=(e=e||"active expanded focus selected").indexOf(u)&&t._data(n+u,null),0<=e.indexOf(g)&&t._data(n+g,null),0<=e.indexOf(h)&&t._data(n+h,null),0<=e.indexOf(f)&&t._data(n+f,null)},p.ui.fancytree._FancytreeClass.prototype.clearCookies=function(e){return this.warn("'tree.clearCookies()' is deprecated since v2.27.0: use 'clearPersistData()' instead."),this.clearPersistData(e)},p.ui.fancytree._FancytreeClass.prototype.getPersistData=function(){var e=this.ext.persist,t=e.cookiePrefix,n=e.cookieDelimiter,i={};return i[u]=e._data(t+u),i[g]=(e._data(t+g)||"").split(n),i[f]=(e._data(t+f)||"").split(n),i[h]=e._data(t+h),i},p.ui.fancytree.registerExtension({name:"persist",version:"2.37.0",options:{cookieDelimiter:"~",cookiePrefix:void 0,cookie:{raw:!1,expires:"",path:"",domain:"",secure:!1},expandLazy:!1,expandOpts:void 0,fireActivate:!0,overrideSource:!0,store:"auto",types:"active expanded focus selected"},_data:function(e,t){var n=this._local.store;if(void 0===t)return n.get.call(this,e);null===t?n.remove.call(this,e):n.set.call(this,e,t)},_appendKey:function(e,t,n){t=""+t;var i=this._local,r=this.options.persist.cookieDelimiter,o=i.cookiePrefix+e,s=i._data(o),a=s?s.split(r):[],l=p.inArray(t,a);0<=l&&a.splice(l,1),n&&a.push(t),i._data(o,a.join(r))},treeInit:function(e){var a=e.tree,l=e.options,d=this._local,c=this.options.persist;return d.cookiePrefix=c.cookiePrefix||"fancytree-"+a._id+"-",d.storeActive=0<=c.types.indexOf(u),d.storeExpanded=0<=c.types.indexOf(g),d.storeSelected=0<=c.types.indexOf(f),d.storeFocus=0<=c.types.indexOf(h),d.store=null,"auto"===c.store&&(c.store=n?"local":"cookie"),p.isPlainObject(c.store)?d.store=c.store:"cookie"===c.store?d.store=t:"local"===c.store?d.store="local"===c.store?n:i:"session"===c.store&&(d.store="local"===c.store?n:i),r(d.store,"Need a valid store."),a.$div.on("fancytreeinit",function(e){if(!1!==a._triggerTreeEvent("beforeRestore",null,{})){var t,n,i,r,o=d._data(d.cookiePrefix+h),s=!1===c.fireActivate;t=d._data(d.cookiePrefix+g),i=t&&t.split(c.cookieDelimiter),(d.storeExpanded?function e(t,n,i,r,o){var s,a,l,d,c=!1,u=t.options.persist.expandOpts,h=[],f=[];for(i=i||[],o=o||p.Deferred(),s=0,l=i.length;stbody")).length||(l.find(">tr").length&&C.error("Expected table > tbody > tr. If you see this please open an issue."),r=C("").appendTo(l)),o.tbody=r[0],o.columnCount=C("thead >tr",l).last().find(">th",l).length,(i=r.children("tr").first()).length)n=i.children("td").length,o.columnCount&&n!==o.columnCount&&(o.warn("Column count mismatch between thead ("+o.columnCount+") and tbody ("+n+"): using tbody."),o.columnCount=n),i=i.clone();else for(_(1<=o.columnCount,"Need either or with elements to determine column count."),i=C(""),t=0;t");i.find(">td").eq(a.nodeColumnIdx).html(""),s.aria&&(i.attr("role","row"),i.find("td").attr("role","gridcell")),o.rowFragment=document.createDocumentFragment(),o.rowFragment.appendChild(i.get(0)),r.empty(),o.statusClassPropName="tr",o.ariaPropName="tr",this.nodeContainerAttrName="tr",o.$container=l,this._superApply(arguments),C(o.rootNode.ul).remove(),o.rootNode.ul=null,this.$container.attr("tabindex",s.tabindex),s.aria&&o.$container.attr("role","treegrid").attr("aria-readonly",!0)},nodeRemoveChildMarkup:function(e){e.node.visit(function(e){e.tr&&(C(e.tr).remove(),e.tr=null)})},nodeRemoveMarkup:function(e){var t=e.node;t.tr&&(C(t.tr).remove(),t.tr=null),this.nodeRemoveChildMarkup(e)},nodeRender:function(e,t,n,i,r){var o,s,a,l,d,c,u,h,f=e.tree,p=e.node,g=e.options,y=!p.parent;if(!1!==f._enableUpdate){if(r||(e.hasCollapsedParents=p.parent&&!p.parent.expanded),!y)if(p.tr&&t&&this.nodeRemoveMarkup(e),p.tr)t?this.nodeRenderTitle(e):this.nodeRenderStatus(e);else{if(e.hasCollapsedParents&&!n)return;d=f.rowFragment.firstChild.cloneNode(!0),_(c=function(e){var t,n,i=e.parent,r=i?i.children:null;if(r&&1td").eq(0).prop("colspan",r.columnCount).text(o.title).addClass("fancytree-status-merged").nextAll().remove():s.renderColumns&&s.renderColumns.call(r,{type:"renderColumns"},e)),i},nodeRenderStatus:function(e){var t,n=e.node,i=e.options;this._super(e),C(n.tr).removeClass("fancytree-node"),t=(n.getLevel()-1)*i.table.indentation,i.rtl?C(n.span).css({paddingRight:t+"px"}):C(n.span).css({paddingLeft:t+"px"})},nodeSetExpanded:function(t,n,i){if(n=!1!==n,t.node.expanded&&n||!t.node.expanded&&!n)return this._superApply(arguments);var r=new C.Deferred,e=C.extend({},i,{noEvents:!0,noAnimation:!0});function o(e){k(t.node,n),e?n&&t.options.autoScroll&&!i.noAnimation&&t.node.hasChildren()?t.node.getLastChild().scrollIntoView(!0,{topNode:t.node}).always(function(){i.noEvents||t.tree._triggerNodeEvent(n?"expand":"collapse",t),r.resolveWith(t.node)}):(i.noEvents||t.tree._triggerNodeEvent(n?"expand":"collapse",t),r.resolveWith(t.node)):(i.noEvents||t.tree._triggerNodeEvent(n?"expand":"collapse",t),r.rejectWith(t.node))}return i=i||{},this._super(t,n,e).done(function(){o(!0)}).fail(function(){o(!1)}),r.promise()},nodeSetStatus:function(e,t,n,i){if("ok"===t){var r=e.node,o=r.children?r.children[0]:null;o&&o.isStatusNode()&&C(o.tr).remove()}return this._superApply(arguments)},treeClear:function(e){return this.nodeRemoveChildMarkup(this._makeHookContext(this.rootNode)),this._superApply(arguments)},treeDestroy:function(e){return this.$container.find("tbody").empty(),this.$source&&this.$source.removeClass("fancytree-helper-hidden"),this._superApply(arguments)}}),C.ui.fancytree},"function"==typeof define&&define.amd?define(["jquery","./jquery.fancytree"],u):"object"==typeof module&&module.exports?(require("./jquery.fancytree"),module.exports=u(require("jquery"))):u(jQuery),h=function(o){"use strict";return o.ui.fancytree.registerExtension({name:"themeroller",version:"2.37.0",options:{activeClass:"ui-state-active",addClass:"ui-corner-all",focusClass:"ui-state-focus",hoverClass:"ui-state-hover",selectedClass:"ui-state-highlight"},treeInit:function(e){var t=e.widget.element,i=e.options.themeroller;this._superApply(arguments),"TABLE"===t[0].nodeName?(t.addClass("ui-widget ui-corner-all"),t.find(">thead tr").addClass("ui-widget-header"),t.find(">tbody").addClass("ui-widget-conent")):t.addClass("ui-widget ui-widget-content ui-corner-all"),t.on("mouseenter mouseleave",".fancytree-node",function(e){var t=o.ui.fancytree.getNode(e.target),n="mouseenter"===e.type;o(t.tr?t.tr:t.span).toggleClass(i.hoverClass+" "+i.addClass,n)})},treeDestroy:function(e){this._superApply(arguments),e.widget.element.removeClass("ui-widget ui-widget-content ui-corner-all")},nodeRenderStatus:function(e){var t={},n=e.node,i=o(n.tr?n.tr:n.span),r=e.options.themeroller;this._super(e),t[r.activeClass]=!1,t[r.focusClass]=!1,t[r.selectedClass]=!1,n.isActive()&&(t[r.activeClass]=!0),n.hasFocus()&&(t[r.focusClass]=!0),n.isSelected()&&!n.isActive()&&(t[r.selectedClass]=!0),i.toggleClass(r.activeClass,t[r.activeClass]),i.toggleClass(r.focusClass,t[r.focusClass]),i.toggleClass(r.selectedClass,t[r.selectedClass]),i.addClass(r.addClass)}}),o.ui.fancytree},"function"==typeof define&&define.amd?define(["jquery","./jquery.fancytree"],h):"object"==typeof module&&module.exports?(require("./jquery.fancytree"),module.exports=h(require("jquery"))):h(jQuery),f=function(p){"use strict";var g=/^([+-]?(?:\d+|\d*\.\d+))([a-z]*|%)$/;function y(e,t){var n=p("#"+(e="fancytree-style-"+e));if(!t)return n.remove(),null;n.length||(n=p("