diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6817020fcabf4b7551e264709fc89cc487e4f0a0..79f7cbfeb42794625970722f3d4c52b274969092 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,10 +3,10 @@ # # Copyright (C) 2018 Research Group Biomedical Physics, # Max-Planck-Institute for Dynamics and Self-Organization Göttingen +# Copyright (C) 2020-2022 IndiScale GmbH (info@indiscale.com) # Copyright (C) 2019 Henrik tom Wörden # Copyright (C) 2020 Timm Fitschen (t.fitschen@indiscale.com) -# Copyright (C) 2020 IndiScale GmbH (info@indiscale.com) -# Copyright (C) 2020 Daniel Hornung <d.hornung@indiscale.com> +# Copyright (C) 2020-2022 Daniel Hornung <d.hornung@indiscale.com> # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -65,20 +65,6 @@ test-server-side-scripting: - whereis pytest pytest3 py.test pytest-3 py.test-3 - make test-sss -# Trigger building of server image and integration tests -trigger_build: - timeout: 15 minutes - tags: [ docker ] - stage: deploy - script: - - /usr/bin/curl -X POST - -F token=$CI_JOB_TOKEN - -F "variables[F_BRANCH]=$CI_COMMIT_REF_NAME" - -F "variables[WEBUI]=$CI_COMMIT_REF_NAME" - -F "variables[TriggerdBy]=WEBUI" - -F "variables[TriggerdByHash]=$CI_COMMIT_SHORT_SHA" - -F ref=$DEPLOY_REF https://gitlab.indiscale.com/api/v4/projects/14/trigger/pipeline - # Build a docker image in which tests for this repository can run build-testenv: tags: [ cached-dind ] diff --git a/CHANGELOG.md b/CHANGELOG.md index 0084fa47c249dd092d569d136dea3f86d0c7b65b..98f4d2c763b5372dc8c7cdcf79c7bd53c629a683 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,22 +4,116 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unpublished] +## [Unreleased] -### Added (for new features, dependecies etc.) +### Added ### Changed (for changes in existing functionality) - ext_references: names will be shown instead of file names if they exist -### Deprecated (for soon-to-be removed features) +### Deprecated -### Removed (for now removed features) +### Removed -### Fixed (for any bug fixes) +### Fixed -### Security (in case of vulnerabilities) +### Security -### Documentation (for notable additions or changes of the documentation) +### Documentation + + +## [0.8.0] - 2022-07-12 +(Timm Fitschen) + +### Added + +* Cache for queries in the edit_mode. + +### Changed + +* The "google query" now splits the given string at the spaces and creates an + AND query (e.g., `john gibson` will result in `FIND ENTITY WHICH HAS A + PROPERTY LIKE '*john*' AND A PROPERTY LIKE '*gibson*'`, not in `FIND ENTITY + WHICH HAS A PROPERTY LIKE '*john gibson*'` as before). + +### Deprecated + +### Removed + +### Fixed + +* Styling of the selects in the edit_mode. + +### Security + +### Documentation + +## [0.7.0] - 2022-05-31 +(Florian Spreckelsen) + +### Added + +* [#172](https://gitlab.com/caosdb/caosdb-webui/-/issues/172) - Map can handle + geo locations in list of references. + +### Fixed + +* [#276](https://gitlab.indiscale.com/caosdb/src/caosdb-webui/-/issues/276) + documentation couldn't be built because of a too long module name. + +## [0.6.0] - 2022-05-03 +(Daniel Hornung) + +### Changed + +* Renamed the person reference resolver. + +### Fixed + +* [webui#170](https://gitlab.com/caosdb/caosdb-webui/-/issues/170) + Autocompletion for "IS REFERENCED BY" leads to query syntax error + +## [0.5.0] - 2022-03-25 +(Timm Fitschen) + +### Added + +* entity_acl module which adds a button to each entity which links to the + webui-acm module. Enable via BUILD_MODULE_EXT_ENTITY_ACL=ENABLED. +* A `#version_history` URI fragment which can be used to directly open the modal + with the full version history of the first entity on the page. +* `BUILD_MODULE_SHOW_ID_IN_LABEL` build variable with which the showing of + entity ids together with their names if it is enabled (disabled by default). +* Introduced `caosdb-f-form-required-marker` and `caosdb-f-form-required-label` + css classes for the markers of required fields in CaosDB form elements. +* The navbar has now an html id `caosdb-navbar-full` + +### Changed + +* Added `show` option to caosdb_map.MapConfig for showing the map initially and + storing the state accross page reloads. Defaults to `false`. This also bumps + the version of the map to 0.4.1 as it changes the behavior but the change is + backwards-compatible for the map config. Clients need to update their + version string in their config file, tho. + +### Deprecated + +### Removed + +* globla `setNameID` function (replaced by `_setDescriptionNameID` which should + only be used be the non-public XML-serialization functions.) + +### Fixed + +* Fixed saving of text properties that were changed in the Source-Editing mode + of the WYSIWYG editor. +* [#266](https://gitlab.indiscale.com/caosdb/src/caosdb-webui/-/issues/266) + Fixed an issue whereby missing property descriptions in the edit mode would + lead to wrongly detected entity updates in the server + +### Security + +### Documentation ## [0.4.2] - 2021-12-06 @@ -38,7 +132,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Optional WYSIWYG editor with markdown output for text properties. Controled by the `BUILD_MODULE_EXT_EDITMODE_WYSIWYG_TEXT` build variable which is set do `DISABLED` by default. - - Added button to version history panel that allows restoring old versions +* Added button to version history panel that allows restoring old versions ### Changed (for changes in existing functionality) @@ -405,4 +499,3 @@ This is the last Bootstrap-3 compatible release. property value if the actual value was an empty string. ### Security (in case of vulnerabilities) - diff --git a/DEPENDENCIES.md b/DEPENDENCIES.md index f3e1d1706dd8dd9bdbdab16377c2c75ed0299f7f..9c6f7e4c7880c488ec116662c2ed49e2bd85d8a1 100644 --- a/DEPENDENCIES.md +++ b/DEPENDENCIES.md @@ -1,11 +1,11 @@ -* CaosDB Server 0.5.x +* CaosDB Server 0.8.0 * Make 4.2.0 # Java Script Libraries (included in this repository) * bootstrap-5.0.1 * bootstrap-autocomplete-2.3.5 * bootstrap-icons-1.4.1 -* bootstrap-select-1.14.0-beta2 +* bootstrap-select-1.14.0-beta3 * dropzone-5.5.0 * javascript-state-machine-master * jquery-3.6.0.min.js @@ -26,8 +26,8 @@ ## For CKEditor (WYSIWYG editor in edit mode) -* we're using a custom-built ckeditor 31.0.0 from - https://ckeditor.com/ckeditor-5/online-builder/ with a customized set of +* We're using a custom-built ckeditor 32.0.0 from [IndiScale's fork of CKEditor + 5](https://gitlab.indiscale.com/caosdb/src/ckeditor5) with a customized set of editor plugins. Please refer to the `package.json` within `libs/ckeditor...zip` for a full list of said plugins. diff --git a/Makefile b/Makefile index 5cca686319c617006007e4884cfb269796ac7af4..b606d4720e76b7ea550ba50a27523b76c4a850de 100644 --- a/Makefile +++ b/Makefile @@ -231,10 +231,10 @@ $(LIBS_DIR)/css/bootstrap-icons.css: unzip $(LIBS_DIR)/css ln -s $(LIBS_DIR)/bootstrap-icons-1.4.1/bootstrap-icons.css $@ $(LIBS_DIR)/js/bootstrap-select.js: unzip $(LIBS_DIR)/js - ln -s $(LIBS_DIR)/bootstrap-select-1.14.0-beta2/js/bootstrap-select.min.js $@ + ln -s $(LIBS_DIR)/bootstrap-select-1.14.0-beta3/js/bootstrap-select.min.js $@ $(LIBS_DIR)/css/bootstrap-select.css: unzip $(LIBS_DIR)/css - ln -s $(LIBS_DIR)/bootstrap-select-1.14.0-beta2/css/bootstrap-select.min.css $@ + ln -s $(LIBS_DIR)/bootstrap-select-1.14.0-beta3/css/bootstrap-select.min.css $@ $(LIBS_DIR)/js/jquery.js: unzip $(LIBS_DIR)/js ln -s $(LIBS_DIR)/jquery-3.6.0.min.js $@ @@ -306,7 +306,7 @@ $(LIBS_DIR)/js/qrcode.js: unzip $(LIBS_DIR)/js ln -s $(LIBS_DIR)/qrcode-1.4.4/qrcode.min.js $@ $(LIBS_DIR)/js/ckeditor.js: unzip $(LIBS_DIR)/js - ln -s $(LIBS_DIR)/ckeditor5-31.0.0-k356w86hp13l/build/ckeditor.js $@ + ln -s $(LIBS_DIR)/ckeditor5-build-custom/build/ckeditor.js $@ $(addprefix $(LIBS_DIR)/, js css): diff --git a/RELEASE_GUIDELINES.md b/RELEASE_GUIDELINES.md index 9dd648c235258b052332212bd906525e87863629..e9a9bb8154c22648d602ceecc78ee41ac666e310 100644 --- a/RELEASE_GUIDELINES.md +++ b/RELEASE_GUIDELINES.md @@ -7,7 +7,6 @@ guidelines of the CaosDB Project ## General Prerequisites * All tests are passing. -* FEATURES.md is up-to-date and a public API is being declared in that document. * CHANGELOG.md is up-to-date (insert version number and remove unpublished) * DEPENDENCIES.md is up-to-date. @@ -18,12 +17,20 @@ guidelines of the CaosDB Project 2. Check all general prerequisites. -3. Merge the release branch into the main branch. +3. Update `src/doc/conf.py` version and check that the correct caosdb-server + version is listed in `DEPENDENCIES.md`. -4. Tag the latest commit of the main branch with `v<VERSION>`. +4. Merge the release branch into the main branch. -5. Delete the release branch. +5. Tag the latest commit of the main branch with `v<VERSION>`. -6. Merge the main branch back into the dev branch. +6. Create gitlab releases on gitlab.indiscale.com and on gitlab.com for new + tag. Add most recent section of the changelog to release description. -7. Prepare CHANGELOG for next release cycle. +7. Delete the release branch. + +8. Merge the main branch back into the dev branch. + +9. Prepare for next release cycle: + * `CHANGELOG.md`: "Unreleased" section + * `src/doc/conf.py`: Bump to next version number and `x.y.z-SNAPSHOT` for the `release` variable. diff --git a/build.properties.d/00_default.properties b/build.properties.d/00_default.properties index 535a6c846a5c39c48937dee43f087501f43285ae..cd57b632e2ea2b9ab005c7185a309a9594f123ff 100644 --- a/build.properties.d/00_default.properties +++ b/build.properties.d/00_default.properties @@ -53,12 +53,13 @@ BUILD_MODULE_EXT_ADD_QUERY_TO_BOOKMARKS=DISABLED BUILD_MODULE_EXT_ANNOTATION=ENABLED BUILD_MODULE_EXT_COSMETICS_LINKIFY=DISABLED BUILD_MODULE_EXT_QRCODE=ENABLED +BUILD_MODULE_SHOW_ID_IN_LABEL=DISABLED BUILD_MODULE_USER_MANAGEMENT=ENABLED BUILD_MODULE_USER_MANAGEMENT_CHANGE_OWN_PASSWORD_REALM=CaosDB BUILD_MODULE_EXT_RESOLVE_REFERENCES=ENABLED -BUILD_EXT_REFERENCES_CUSTOM_RESOLVER=person_reference +BUILD_EXT_REFERENCES_CUSTOM_RESOLVER=caosdb_default_person_reference BUILD_MODULE_EXT_EDITMODE_WYSIWYG_TEXT=DISABLED @@ -177,4 +178,5 @@ MODULE_DEPENDENCIES=( form_panel.js ckeditor.js ext_editmode_wysiwyg_text.js + reference_resolver/caosdb_default_person.js ) diff --git a/libs/bootstrap-select-1.14.0-beta2.zip b/libs/bootstrap-select-1.14.0-beta2.zip deleted file mode 100644 index 9d7a97280ee9825b6a23aaddb40d20256a892f85..0000000000000000000000000000000000000000 Binary files a/libs/bootstrap-select-1.14.0-beta2.zip and /dev/null differ diff --git a/libs/bootstrap-select-1.14.0-beta3/css/bootstrap-select.min.css b/libs/bootstrap-select-1.14.0-beta3/css/bootstrap-select.min.css new file mode 100644 index 0000000000000000000000000000000000000000..9c50c7b187dc36b4ad8420ab0d314687800f5a0c --- /dev/null +++ b/libs/bootstrap-select-1.14.0-beta3/css/bootstrap-select.min.css @@ -0,0 +1,6 @@ +/*! + * Bootstrap-select v1.14.0-beta3 (https://developer.snapappointments.com/bootstrap-select) + * + * Copyright 2012-2022 SnapAppointments, LLC + * Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE) + */@-webkit-keyframes bs-notify-fadeOut{0%{opacity:.9}100%{opacity:0}}@-o-keyframes bs-notify-fadeOut{0%{opacity:.9}100%{opacity:0}}@keyframes bs-notify-fadeOut{0%{opacity:.9}100%{opacity:0}}.bootstrap-select>select.bs-select-hidden,select.bs-select-hidden,select.selectpicker{display:none!important}.bootstrap-select{width:220px;vertical-align:middle}.bootstrap-select>.dropdown-toggle{position:relative;width:100%;text-align:right;white-space:nowrap;display:-webkit-inline-box;display:-webkit-inline-flex;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between}.bootstrap-select>.dropdown-toggle:after{margin-top:-1px}.bootstrap-select>.dropdown-toggle.bs-placeholder,.bootstrap-select>.dropdown-toggle.bs-placeholder:active,.bootstrap-select>.dropdown-toggle.bs-placeholder:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder:hover{color:#999}.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-danger,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-danger:active,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-danger:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-danger:hover,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-dark,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-dark:active,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-dark:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-dark:hover,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-info,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-info:active,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-info:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-info:hover,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-primary,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-primary:active,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-primary:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-primary:hover,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-secondary,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-secondary:active,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-secondary:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-secondary:hover,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-success,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-success:active,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-success:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-success:hover{color:rgba(255,255,255,.5)}.bootstrap-select>select{position:absolute!important;bottom:0;left:50%;display:block!important;width:.5px!important;height:100%!important;padding:0!important;opacity:0!important;border:none;z-index:0!important}.bootstrap-select>select.mobile-device{top:0;left:0;display:block!important;width:100%!important;z-index:2!important}.bootstrap-select.is-invalid .dropdown-toggle,.error .bootstrap-select .dropdown-toggle,.has-error .bootstrap-select .dropdown-toggle,.was-validated .bootstrap-select select:invalid+.dropdown-toggle{border-color:#b94a48}.bootstrap-select.is-valid .dropdown-toggle,.was-validated .bootstrap-select select:valid+.dropdown-toggle{border-color:#28a745}.bootstrap-select.fit-width{width:auto!important}.bootstrap-select:not([class*=col-]):not([class*=form-control]):not(.input-group-btn){width:220px}.bootstrap-select .dropdown-toggle:focus,.bootstrap-select>select.mobile-device:focus+.dropdown-toggle{outline:thin dotted #333!important;outline:5px auto -webkit-focus-ring-color!important;outline-offset:-2px}.bootstrap-select.form-control{margin-bottom:0;padding:0;border:none;height:auto}:not(.input-group)>.bootstrap-select.form-control:not([class*=col-]){width:100%}.bootstrap-select.form-control.input-group-btn{float:none;z-index:auto}.form-inline .bootstrap-select,.form-inline .bootstrap-select.form-control:not([class*=col-]){width:auto}.bootstrap-select:not(.input-group-btn),.bootstrap-select[class*=col-]{float:none;display:inline-block;margin-left:0}.bootstrap-select.dropdown-menu-right,.bootstrap-select[class*=col-].dropdown-menu-right,.row .bootstrap-select[class*=col-].dropdown-menu-right{float:right}.form-group .bootstrap-select,.form-horizontal .bootstrap-select,.form-inline .bootstrap-select{margin-bottom:0}.form-group-lg .bootstrap-select.form-control,.form-group-sm .bootstrap-select.form-control{padding:0}.form-group-lg .bootstrap-select.form-control .dropdown-toggle,.form-group-sm .bootstrap-select.form-control .dropdown-toggle{height:100%;font-size:inherit;line-height:inherit;border-radius:inherit}.bootstrap-select.form-control-lg .dropdown-toggle,.bootstrap-select.form-control-sm .dropdown-toggle{font-size:inherit;line-height:inherit;border-radius:inherit}.bootstrap-select.form-control-sm .dropdown-toggle{padding:.25rem .5rem}.bootstrap-select.form-control-lg .dropdown-toggle{padding:.5rem 1rem}.form-inline .bootstrap-select .form-control{width:100%}.bootstrap-select.disabled,.bootstrap-select>.disabled{cursor:not-allowed}.bootstrap-select.disabled:focus,.bootstrap-select>.disabled:focus{outline:0!important}.bootstrap-select.bs-container{position:absolute;top:0;left:0;height:0!important;padding:0!important}.bootstrap-select.bs-container .dropdown-menu{z-index:1060}.bootstrap-select .dropdown-toggle .filter-option{position:static;top:0;left:0;float:left;height:100%;width:100%;text-align:left;overflow:hidden;-webkit-box-flex:0;-webkit-flex:0 1 auto;-ms-flex:0 1 auto;flex:0 1 auto}.bs3.bootstrap-select .dropdown-toggle .filter-option{padding-right:inherit}.input-group .bs3-has-addon.bootstrap-select .dropdown-toggle .filter-option{position:absolute;padding-top:inherit;padding-bottom:inherit;padding-left:inherit;float:none}.input-group .bs3-has-addon.bootstrap-select .dropdown-toggle .filter-option .filter-option-inner{padding-right:inherit}.bootstrap-select .dropdown-toggle .filter-option-inner-inner{overflow:hidden}.bootstrap-select .dropdown-toggle .filter-expand{width:0!important;float:left;opacity:0!important;overflow:hidden}.bootstrap-select .dropdown-toggle .caret{position:absolute;top:50%;right:12px;margin-top:-2px;vertical-align:middle}.bootstrap-select .dropdown-toggle .bs-select-clear-selected{position:relative;display:block;margin-right:5px;text-align:center}.bs3.bootstrap-select .dropdown-toggle .bs-select-clear-selected{padding-right:inherit}.bootstrap-select .dropdown-toggle .bs-select-clear-selected span{position:relative;top:-webkit-calc(((-1em / 1.5) + 1ex)/ 2);top:calc(((-1em / 1.5) + 1ex)/ 2);pointer-events:none}.bs3.bootstrap-select .dropdown-toggle .bs-select-clear-selected span{top:auto}.bootstrap-select .dropdown-toggle.bs-placeholder .bs-select-clear-selected{display:none}.input-group .bootstrap-select.form-control .dropdown-toggle{border-radius:inherit}.bootstrap-select[class*=col-] .dropdown-toggle{width:100%}.bootstrap-select .dropdown-menu{min-width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bootstrap-select .dropdown-menu>.inner:focus{outline:0!important}.bootstrap-select .dropdown-menu.inner{position:static;float:none;border:0;padding:0;margin:0;border-radius:0;-webkit-box-shadow:none;box-shadow:none}.bootstrap-select .dropdown-menu li{position:relative}.bootstrap-select .dropdown-menu li.active small{color:rgba(255,255,255,.5)!important}.bootstrap-select .dropdown-menu li.disabled a{cursor:not-allowed}.bootstrap-select .dropdown-menu li a{cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.bootstrap-select .dropdown-menu li a.opt{position:relative;padding-left:2.25em}.bootstrap-select .dropdown-menu li a span.check-mark{display:none}.bootstrap-select .dropdown-menu li a span.text{display:inline-block}.bootstrap-select .dropdown-menu li small{padding-left:.5em}.bootstrap-select .dropdown-menu .notify{position:absolute;bottom:5px;width:96%;margin:0 2%;min-height:26px;padding:3px 5px;background:#f5f5f5;border:1px solid #e3e3e3;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05);pointer-events:none;opacity:.9;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bootstrap-select .dropdown-menu .notify.fadeOut{-webkit-animation:.3s linear 750ms forwards bs-notify-fadeOut;-o-animation:.3s linear 750ms forwards bs-notify-fadeOut;animation:.3s linear 750ms forwards bs-notify-fadeOut}.bootstrap-select .no-results{padding:3px;background:#f5f5f5;margin:0 5px;white-space:nowrap}.bootstrap-select.fit-width .dropdown-toggle .filter-option{position:static;display:inline;padding:0}.bootstrap-select.fit-width .dropdown-toggle .filter-option-inner,.bootstrap-select.fit-width .dropdown-toggle .filter-option-inner-inner{display:inline}.bootstrap-select.fit-width .dropdown-toggle .bs-caret:before{content:'\00a0'}.bootstrap-select.fit-width .dropdown-toggle .caret{position:static;top:auto;margin-top:-1px}.bootstrap-select.show-tick .dropdown-menu .selected span.check-mark{position:absolute;display:inline-block;right:15px;top:5px}.bootstrap-select.show-tick .dropdown-menu li a span.text{margin-right:34px}.bootstrap-select .bs-ok-default:after{content:'';display:block;width:.5em;height:1em;border-style:solid;border-width:0 .26em .26em 0;-webkit-transform-style:preserve-3d;transform-style:preserve-3d;-webkit-transform:rotate(45deg);-ms-transform:rotate(45deg);-o-transform:rotate(45deg);transform:rotate(45deg)}.bootstrap-select.show-menu-arrow.open>.dropdown-toggle,.bootstrap-select.show-menu-arrow.show>.dropdown-toggle{z-index:1061}.bootstrap-select.show-menu-arrow .dropdown-toggle .filter-option:before{content:'';border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid rgba(204,204,204,.2);position:absolute;bottom:-4px;left:9px;display:none}.bootstrap-select.show-menu-arrow .dropdown-toggle .filter-option:after{content:'';border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #fff;position:absolute;bottom:-4px;left:10px;display:none}.bootstrap-select.show-menu-arrow.dropup .dropdown-toggle .filter-option:before{bottom:auto;top:-4px;border-top:7px solid rgba(204,204,204,.2);border-bottom:0}.bootstrap-select.show-menu-arrow.dropup .dropdown-toggle .filter-option:after{bottom:auto;top:-4px;border-top:6px solid #fff;border-bottom:0}.bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle .filter-option:before{right:12px;left:auto}.bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle .filter-option:after{right:13px;left:auto}.bootstrap-select.show-menu-arrow.open>.dropdown-toggle .filter-option:after,.bootstrap-select.show-menu-arrow.open>.dropdown-toggle .filter-option:before,.bootstrap-select.show-menu-arrow.show>.dropdown-toggle .filter-option:after,.bootstrap-select.show-menu-arrow.show>.dropdown-toggle .filter-option:before{display:block}.bs-actionsbox,.bs-donebutton,.bs-searchbox{padding:4px 8px}.bs-actionsbox{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bs-actionsbox .btn-group{display:block}.bs-actionsbox .btn-group button{width:50%}.bs-donebutton{float:left;width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bs-donebutton .btn-group{display:block}.bs-donebutton .btn-group button{width:100%}.bs-searchbox+.bs-actionsbox{padding:0 8px 4px}.bs-searchbox .form-control{margin-bottom:0;width:100%;float:none} \ No newline at end of file diff --git a/libs/bootstrap-select-1.14.0-beta3/js/bootstrap-select.min.js b/libs/bootstrap-select-1.14.0-beta3/js/bootstrap-select.min.js new file mode 100644 index 0000000000000000000000000000000000000000..c64297688af958d702bb626b908f13f6b0db1d0a --- /dev/null +++ b/libs/bootstrap-select-1.14.0-beta3/js/bootstrap-select.min.js @@ -0,0 +1,8 @@ +/*! + * Bootstrap-select v1.14.0-beta3 (https://developer.snapappointments.com/bootstrap-select) + * + * Copyright 2012-2022 SnapAppointments, LLC + * Licensed under MIT (https://github.com/snapappointments/bootstrap-select/blob/master/LICENSE) + */ +!function(e,t){void 0===e&&void 0!==window&&(e=window),"function"==typeof define&&define.amd?define(["jquery"],t):"object"==typeof module&&module.exports?module.exports=t(require("jquery")):t(e.jQuery)}(this,function(e){!function($){"use strict";var M=["sanitize","whiteList","sanitizeFn"],W=["background","cite","href","itemtype","longdesc","poster","src","xlink:href"],P={"*":["class","dir","id","lang","role","tabindex","style",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],div:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},B=/^(?:(?:https?|mailto|ftp|tel|file):|[^&:/?#]*(?:[/?#]|$))/gi,R=/^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[a-z0-9+/]+=*$/i,U=["title","placeholder"];function S(e,t,i){if(i&&"function"==typeof i)return i(e);for(var s=Object.keys(t),n=0,o=e.length;n<o;n++)for(var l=e[n].querySelectorAll("*"),r=0,a=l.length;r<a;r++){var c=l[r],d=c.nodeName.toLowerCase();if(-1===s.indexOf(d))c.parentNode.removeChild(c);else for(var h=[].slice.call(c.attributes),p=[].concat(t["*"]||[],t[d]||[]),u=0,f=h.length;u<f;u++){var m=h[u];!function(e,t){var i=e.nodeName.toLowerCase();if(-1!==$.inArray(i,t))return-1===$.inArray(i,W)||Boolean(e.nodeValue.match(B)||e.nodeValue.match(R));for(var s=$(t).filter(function(e,t){return t instanceof RegExp}),n=0,o=s.length;n<o;n++)if(i.match(s[n]))return 1}(m,p)&&c.removeAttribute(m.nodeName)}}}function d(t){var i,s={};return U.forEach(function(e){(i=t.attr(e))&&(s[e]=i)}),!s.placeholder&&s.title&&(s.placeholder=s.title),s}if(!("classList"in document.createElement("_"))&&"Element"in(i=window)){var t="classList",e="prototype",i=i.Element[e],s=Object,n=function(){var i=$(this);return{add:function(e){return e=Array.prototype.slice.call(arguments).join(" "),i.addClass(e)},remove:function(e){return e=Array.prototype.slice.call(arguments).join(" "),i.removeClass(e)},toggle:function(e,t){return i.toggleClass(e,t)},contains:function(e){return i.hasClass(e)}}};if(s.defineProperty){var o={get:n,enumerable:!0,configurable:!0};try{s.defineProperty(i,t,o)}catch(e){void 0!==e.number&&-2146823252!==e.number||(o.enumerable=!1,s.defineProperty(i,t,o))}}else s[e].__defineGetter__&&i.__defineGetter__(t,n)}var l,r,a,c,o=document.createElement("_");function h(e){if(null==this)throw new TypeError;var t=String(this);if(e&&"[object RegExp]"==c.call(e))throw new TypeError;var i=t.length,s=String(e),n=s.length,e=1<arguments.length?arguments[1]:void 0,e=e?Number(e):0,o=(e!=e&&(e=0),Math.min(Math.max(e,0),i));if(i<n+o)return!1;for(var l=-1;++l<n;)if(t.charCodeAt(o+l)!=s.charCodeAt(l))return!1;return!0}function y(){var e=this.selectpicker.main.data,t=(e=this.options.source.data||this.options.source.search?Object.values(this.selectpicker.optionValuesDataMap):e).filter(function(e){return!!e.selected&&(!this.options.hideDisabled||!e.disabled)},this);if(this.options.source.data&&!this.multiple&&1<t.length){for(var i=0;i<t.length-1;i++)t[i].selected=!1;t=[t[t.length-1]]}return t}function x(e){for(var t,i=[],s=e||y.call(this),n=0,o=s.length;n<o;n++)(t=s[n]).disabled||i.push(void 0===t.value?t.text:t.value);return this.multiple?i:i.length?i[0]:null}o.classList.add("c1","c2"),o.classList.contains("c2")||(l=DOMTokenList.prototype.add,r=DOMTokenList.prototype.remove,DOMTokenList.prototype.add=function(){Array.prototype.forEach.call(arguments,l.bind(this))},DOMTokenList.prototype.remove=function(){Array.prototype.forEach.call(arguments,r.bind(this))}),o.classList.toggle("c3",!1),o.classList.contains("c3")&&(a=DOMTokenList.prototype.toggle,DOMTokenList.prototype.toggle=function(e,t){return 1 in arguments&&!this.contains(e)==!t?t:a.call(this,e)}),o=null,Object.values="function"==typeof Object.values?Object.values:function(t){return Object.keys(t).map(function(e){return t[e]})},String.prototype.startsWith||(c={}.toString,Object.defineProperty?Object.defineProperty(String.prototype,"startsWith",{value:h,configurable:!0,writable:!0}):String.prototype.startsWith=h);var p={useDefault:!1,_set:$.valHooks.select.set},E=($.valHooks.select.set=function(e,t){return t&&!p.useDefault&&$(e).data("selected",!0),p._set.apply(this,arguments)},null),V=function(){try{return new Event("change"),!0}catch(e){return!1}}();function b(e,t,i,s){for(var n=["display","subtext","tokens"],o=!1,l=0;l<n.length;l++){var r=n[l],a=e[r];if(a&&(a=a.toString(),"display"===r&&(a=a.replace(/<[^>]+>/g,"")),a=(a=s?u(a):a).toUpperCase(),o="function"==typeof i?i(a,t):"contains"===i?0<=a.indexOf(t):a.startsWith(t)))break}return o}function v(e){return parseInt(e,10)||0}$.fn.triggerNative=function(e){var t,i=this[0];i.dispatchEvent&&(V?t=new Event(e,{bubbles:!0}):(t=document.createEvent("Event")).initEvent(e,!0,!1),i.dispatchEvent(t))};var j={"\xc0":"A","\xc1":"A","\xc2":"A","\xc3":"A","\xc4":"A","\xc5":"A","\xe0":"a","\xe1":"a","\xe2":"a","\xe3":"a","\xe4":"a","\xe5":"a","\xc7":"C","\xe7":"c","\xd0":"D","\xf0":"d","\xc8":"E","\xc9":"E","\xca":"E","\xcb":"E","\xe8":"e","\xe9":"e","\xea":"e","\xeb":"e","\xcc":"I","\xcd":"I","\xce":"I","\xcf":"I","\xec":"i","\xed":"i","\xee":"i","\xef":"i","\xd1":"N","\xf1":"n","\xd2":"O","\xd3":"O","\xd4":"O","\xd5":"O","\xd6":"O","\xd8":"O","\xf2":"o","\xf3":"o","\xf4":"o","\xf5":"o","\xf6":"o","\xf8":"o","\xd9":"U","\xda":"U","\xdb":"U","\xdc":"U","\xf9":"u","\xfa":"u","\xfb":"u","\xfc":"u","\xdd":"Y","\xfd":"y","\xff":"y","\xc6":"Ae","\xe6":"ae","\xde":"Th","\xfe":"th","\xdf":"ss","\u0100":"A","\u0102":"A","\u0104":"A","\u0101":"a","\u0103":"a","\u0105":"a","\u0106":"C","\u0108":"C","\u010a":"C","\u010c":"C","\u0107":"c","\u0109":"c","\u010b":"c","\u010d":"c","\u010e":"D","\u0110":"D","\u010f":"d","\u0111":"d","\u0112":"E","\u0114":"E","\u0116":"E","\u0118":"E","\u011a":"E","\u0113":"e","\u0115":"e","\u0117":"e","\u0119":"e","\u011b":"e","\u011c":"G","\u011e":"G","\u0120":"G","\u0122":"G","\u011d":"g","\u011f":"g","\u0121":"g","\u0123":"g","\u0124":"H","\u0126":"H","\u0125":"h","\u0127":"h","\u0128":"I","\u012a":"I","\u012c":"I","\u012e":"I","\u0130":"I","\u0129":"i","\u012b":"i","\u012d":"i","\u012f":"i","\u0131":"i","\u0134":"J","\u0135":"j","\u0136":"K","\u0137":"k","\u0138":"k","\u0139":"L","\u013b":"L","\u013d":"L","\u013f":"L","\u0141":"L","\u013a":"l","\u013c":"l","\u013e":"l","\u0140":"l","\u0142":"l","\u0143":"N","\u0145":"N","\u0147":"N","\u014a":"N","\u0144":"n","\u0146":"n","\u0148":"n","\u014b":"n","\u014c":"O","\u014e":"O","\u0150":"O","\u014d":"o","\u014f":"o","\u0151":"o","\u0154":"R","\u0156":"R","\u0158":"R","\u0155":"r","\u0157":"r","\u0159":"r","\u015a":"S","\u015c":"S","\u015e":"S","\u0160":"S","\u015b":"s","\u015d":"s","\u015f":"s","\u0161":"s","\u0162":"T","\u0164":"T","\u0166":"T","\u0163":"t","\u0165":"t","\u0167":"t","\u0168":"U","\u016a":"U","\u016c":"U","\u016e":"U","\u0170":"U","\u0172":"U","\u0169":"u","\u016b":"u","\u016d":"u","\u016f":"u","\u0171":"u","\u0173":"u","\u0174":"W","\u0175":"w","\u0176":"Y","\u0177":"y","\u0178":"Y","\u0179":"Z","\u017b":"Z","\u017d":"Z","\u017a":"z","\u017c":"z","\u017e":"z","\u0132":"IJ","\u0133":"ij","\u0152":"Oe","\u0153":"oe","\u0149":"'n","\u017f":"s"},_=/[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g,F=RegExp("[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff\\u1ab0-\\u1aff\\u1dc0-\\u1dff]","g");function G(e){return j[e]}function u(e){return(e=e.toString())&&e.replace(_,G).replace(F,"")}f={"&":"&","<":"<",">":">",'"':""","'":"'","`":"`"},s="(?:"+Object.keys(f).join("|")+")",q=RegExp(s),K=RegExp(s,"g");var f,q,K,k=function(e){return q.test(e=null==e?"":""+e)?e.replace(K,Q):e};function Q(e){return f[e]}var Y={32:" ",48:"0",49:"1",50:"2",51:"3",52:"4",53:"5",54:"6",55:"7",56:"8",57:"9",59:";",65:"A",66:"B",67:"C",68:"D",69:"E",70:"F",71:"G",72:"H",73:"I",74:"J",75:"K",76:"L",77:"M",78:"N",79:"O",80:"P",81:"Q",82:"R",83:"S",84:"T",85:"U",86:"V",87:"W",88:"X",89:"Y",90:"Z",96:"0",97:"1",98:"2",99:"3",100:"4",101:"5",102:"6",103:"7",104:"8",105:"9"},Z=27,J=13,w=32,I=9,C=38,O=40,m=window.Dropdown||bootstrap.Dropdown;function X(){var t;try{t=$.fn.dropdown.Constructor.VERSION}catch(e){t=m.VERSION}return t}var g={success:!1,major:"3"};try{g.full=(X()||"").split(" ")[0].split("."),g.major=g.full[0],g.success=!0}catch(e){}var ee=0,A=".bs.select",T={DISABLED:"disabled",DIVIDER:"divider",SHOW:"open",DROPUP:"dropup",MENU:"dropdown-menu",MENURIGHT:"dropdown-menu-right",MENULEFT:"dropdown-menu-left",BUTTONCLASS:"btn-default",POPOVERHEADER:"popover-title",ICONBASE:"glyphicon",TICKICON:"glyphicon-ok"},z={MENU:"."+T.MENU,DATA_TOGGLE:'data-toggle="dropdown"'},D={div:document.createElement("div"),span:document.createElement("span"),i:document.createElement("i"),subtext:document.createElement("small"),a:document.createElement("a"),li:document.createElement("li"),whitespace:document.createTextNode("\xa0"),fragment:document.createDocumentFragment(),option:document.createElement("option")},te=(D.selectedOption=D.option.cloneNode(!1),D.selectedOption.setAttribute("selected",!0),D.noResults=D.li.cloneNode(!1),D.noResults.className="no-results",D.a.setAttribute("role","option"),D.a.className="dropdown-item",D.subtext.className="text-muted",D.text=D.span.cloneNode(!1),D.text.className="text",D.checkMark=D.span.cloneNode(!1),new RegExp(C+"|"+O)),ie=new RegExp("^"+I+"$|"+Z),L={li:function(e,t,i){var s=D.li.cloneNode(!1);return e&&(1===e.nodeType||11===e.nodeType?s.appendChild(e):s.innerHTML=e),void 0!==t&&""!==t&&(s.className=t),null!=i&&s.classList.add("optgroup-"+i),s},a:function(e,t,i){var s=D.a.cloneNode(!0);return e&&(11===e.nodeType?s.appendChild(e):s.insertAdjacentHTML("beforeend",e)),void 0!==t&&""!==t&&s.classList.add.apply(s.classList,t.split(/\s+/)),i&&s.setAttribute("style",i),s},text:function(e,t){var i,s,n=D.text.cloneNode(!1);if(e.content?n.innerHTML=e.content:(n.textContent=e.text,e.icon&&(i=D.whitespace.cloneNode(!1),(s=(!0===t?D.i:D.span).cloneNode(!1)).className=this.options.iconBase+" "+e.icon,D.fragment.appendChild(s),D.fragment.appendChild(i)),e.subtext&&((s=D.subtext.cloneNode(!1)).textContent=e.subtext,n.appendChild(s))),!0===t)for(;0<n.childNodes.length;)D.fragment.appendChild(n.childNodes[0]);else D.fragment.appendChild(n);return D.fragment},label:function(e){var t,i,s=D.text.cloneNode(!1);return s.innerHTML=e.display,e.icon&&(t=D.whitespace.cloneNode(!1),(i=D.span.cloneNode(!1)).className=this.options.iconBase+" "+e.icon,D.fragment.appendChild(i),D.fragment.appendChild(t)),e.subtext&&((i=D.subtext.cloneNode(!1)).textContent=e.subtext,s.appendChild(i)),D.fragment.appendChild(s),D.fragment}},N={fromOption:function(e,t){var i;switch(t){case"divider":i="true"===e.getAttribute("data-divider");break;case"text":i=e.textContent;break;case"label":i=e.label;break;case"style":i=e.style.cssText;break;case"title":i=e.title;break;default:i=e.getAttribute("data-"+t.replace(/[A-Z]+(?![a-z])|[A-Z]/g,function(e,t){return(t?"-":"")+e.toLowerCase()}))}return i},fromDataSource:function(e,t){var i;switch(t){case"text":case"label":i=e.text||e.value||"";break;default:i=e[t]}return i}};function se(e,t){e.length||(D.noResults.innerHTML=this.options.noneResultsText.replace("{0}",'"'+k(t)+'"'),this.$menuInner[0].firstChild.appendChild(D.noResults))}function ne(e){return!(e.hidden||this.options.hideDisabled&&e.disabled)}function H(e,t){var i=this;p.useDefault||($.valHooks.select.set=p._set,p.useDefault=!0),this.$element=$(e),this.$newElement=null,this.$button=null,this.$menu=null,this.options=t,this.selectpicker={main:{data:[],optionQueue:D.fragment.cloneNode(!1),hasMore:!1},search:{data:[],hasMore:!1},current:{},view:{},optionValuesDataMap:{},isSearching:!1,keydown:{keyHistory:"",resetKeyHistory:{start:function(){return setTimeout(function(){i.selectpicker.keydown.keyHistory=""},800)}}}},this.sizeInfo={},"number"==typeof(e=this.options.windowPadding)&&(this.options.windowPadding=[e,e,e,e]),this.val=H.prototype.val,this.render=H.prototype.render,this.refresh=H.prototype.refresh,this.setStyle=H.prototype.setStyle,this.selectAll=H.prototype.selectAll,this.deselectAll=H.prototype.deselectAll,this.destroy=H.prototype.destroy,this.remove=H.prototype.remove,this.show=H.prototype.show,this.hide=H.prototype.hide,this.init()}function oe(e){var r,a=arguments,c=e;if([].shift.apply(a),!g.success){try{g.full=(X()||"").split(" ")[0].split(".")}catch(e){H.BootstrapVersion?g.full=H.BootstrapVersion.split(" ")[0].split("."):(g.full=[g.major,"0","0"],console.warn("There was an issue retrieving Bootstrap's version. Ensure Bootstrap is being loaded before bootstrap-select and there is no namespace collision. If loading Bootstrap asynchronously, the version may need to be manually specified via $.fn.selectpicker.Constructor.BootstrapVersion.",e))}g.major=g.full[0],g.success=!0}if("4"<=g.major){var t=[];H.DEFAULTS.style===T.BUTTONCLASS&&t.push({name:"style",className:"BUTTONCLASS"}),H.DEFAULTS.iconBase===T.ICONBASE&&t.push({name:"iconBase",className:"ICONBASE"}),H.DEFAULTS.tickIcon===T.TICKICON&&t.push({name:"tickIcon",className:"TICKICON"}),T.DIVIDER="dropdown-divider",T.SHOW="show",T.BUTTONCLASS="btn-light",T.POPOVERHEADER="popover-header",T.ICONBASE="",T.TICKICON="bs-ok-default";for(var i=0;i<t.length;i++){e=t[i];H.DEFAULTS[e.name]=T[e.className]}}"4"<g.major&&(z.DATA_TOGGLE='data-bs-toggle="dropdown"');var s=this.each(function(){var e=$(this);if(e.is("select")){var t=e.data("selectpicker"),i="object"==typeof c&&c;if(i.title&&(i.placeholder=i.title),t){if(i)for(var s in i)Object.prototype.hasOwnProperty.call(i,s)&&(t.options[s]=i[s])}else{var n,o=e.data();for(n in o)Object.prototype.hasOwnProperty.call(o,n)&&-1!==$.inArray(n,M)&&delete o[n];var l=$.extend({},H.DEFAULTS,$.fn.selectpicker.defaults||{},d(e),o,i);l.template=$.extend({},H.DEFAULTS.template,$.fn.selectpicker.defaults?$.fn.selectpicker.defaults.template:{},o.template,i.template),l.source=$.extend({},H.DEFAULTS.source,$.fn.selectpicker.defaults?$.fn.selectpicker.defaults.source:{},i.source),e.data("selectpicker",t=new H(this,l))}"string"==typeof c&&(r=t[c]instanceof Function?t[c].apply(t,a):t.options[c])}});return void 0!==r?r:s}H.VERSION="1.14.0-beta3",H.DEFAULTS={noneSelectedText:"Nothing selected",noneResultsText:"No results matched {0}",countSelectedText:function(e,t){return 1==e?"{0} item selected":"{0} items selected"},maxOptionsText:function(e,t){return[1==e?"Limit reached ({n} item max)":"Limit reached ({n} items max)",1==t?"Group limit reached ({n} item max)":"Group limit reached ({n} items max)"]},selectAllText:"Select All",deselectAllText:"Deselect All",source:{pageSize:40},chunkSize:40,doneButton:!1,doneButtonText:"Close",multipleSeparator:", ",styleBase:"btn",style:T.BUTTONCLASS,size:"auto",title:null,placeholder:null,allowClear:!1,selectedTextFormat:"values",width:!1,container:!1,hideDisabled:!1,showSubtext:!1,showIcon:!0,showContent:!0,dropupAuto:!0,header:!1,liveSearch:!1,liveSearchPlaceholder:null,liveSearchNormalize:!1,liveSearchStyle:"contains",actionsBox:!1,iconBase:T.ICONBASE,tickIcon:T.TICKICON,showTick:!1,template:{caret:'<span class="caret"></span>'},maxOptions:!1,mobile:!1,selectOnTab:!0,dropdownAlignRight:!1,windowPadding:0,virtualScroll:600,display:!1,sanitize:!0,sanitizeFn:null,whiteList:P},H.prototype={constructor:H,init:function(){var i=this,e=this.$element.attr("id"),t=this.$element[0],s=t.form;ee++,this.selectId="bs-select-"+ee,t.classList.add("bs-select-hidden"),this.multiple=this.$element.prop("multiple"),this.autofocus=this.$element.prop("autofocus"),t.classList.contains("show-tick")&&(this.options.showTick=!0),this.$newElement=this.createDropdown(),this.$element.after(this.$newElement).prependTo(this.$newElement),s&&null===t.form&&(s.id||(s.id="form-"+this.selectId),t.setAttribute("form",s.id)),this.$button=this.$newElement.children("button"),this.options.allowClear&&(this.$clearButton=this.$button.children(".bs-select-clear-selected")),this.$menu=this.$newElement.children(z.MENU),this.$menuInner=this.$menu.children(".inner"),this.$searchbox=this.$menu.find("input"),t.classList.remove("bs-select-hidden"),this.fetchData(function(){i.render(!0),i.buildList(),requestAnimationFrame(function(){i.$element.trigger("loaded"+A)})}),!0===this.options.dropdownAlignRight&&this.$menu[0].classList.add(T.MENURIGHT),void 0!==e&&this.$button.attr("data-id",e),this.checkDisabled(),this.clickListener(),4<g.major&&(this.dropdown=new m(this.$button[0])),this.options.liveSearch?(this.liveSearchListener(),this.focusedParent=this.$searchbox[0]):this.focusedParent=this.$menuInner[0],this.setStyle(),this.setWidth(),this.options.container?this.selectPosition():this.$element.on("hide"+A,function(){var e,t;i.isVirtual()&&(t=(e=i.$menuInner[0]).firstChild.cloneNode(!1),e.replaceChild(t,e.firstChild),e.scrollTop=0)}),this.$menu.data("this",this),this.$newElement.data("this",this),this.options.mobile&&this.mobile(),this.$newElement.on({"hide.bs.dropdown":function(e){i.$element.trigger("hide"+A,e)},"hidden.bs.dropdown":function(e){i.$element.trigger("hidden"+A,e)},"show.bs.dropdown":function(e){i.$element.trigger("show"+A,e)},"shown.bs.dropdown":function(e){i.$element.trigger("shown"+A,e)}}),t.hasAttribute("required")&&this.$element.on("invalid"+A,function(){i.$button[0].classList.add("bs-invalid"),i.$element.on("shown"+A+".invalid",function(){i.$element.val(i.$element.val()).off("shown"+A+".invalid")}).on("rendered"+A,function(){this.validity.valid&&i.$button[0].classList.remove("bs-invalid"),i.$element.off("rendered"+A)}),i.$button.on("blur"+A,function(){i.$element.trigger("focus").trigger("blur"),i.$button.off("blur"+A)})}),s&&$(s).on("reset"+A,function(){requestAnimationFrame(function(){i.render()})})},createDropdown:function(){var e=this.multiple||this.options.showTick?" show-tick":"",t=this.multiple?' aria-multiselectable="true"':"",i="",s=this.autofocus?" autofocus":"";g.major<4&&this.$element.parent().hasClass("input-group")&&(i=" input-group-btn");var n="",o="",l="",r="",a="";return this.options.header&&(n='<div class="'+T.POPOVERHEADER+'"><button type="button" class="close" aria-hidden="true">×</button>'+this.options.header+"</div>"),this.options.liveSearch&&(o='<div class="bs-searchbox"><input type="search" class="form-control" autocomplete="off"'+(null===this.options.liveSearchPlaceholder?"":' placeholder="'+k(this.options.liveSearchPlaceholder)+'"')+' role="combobox" aria-label="Search" aria-controls="'+this.selectId+'" aria-autocomplete="list"></div>'),this.multiple&&this.options.actionsBox&&(l='<div class="bs-actionsbox"><div class="btn-group btn-group-sm"><button type="button" class="actions-btn bs-select-all btn '+T.BUTTONCLASS+'">'+this.options.selectAllText+'</button><button type="button" class="actions-btn bs-deselect-all btn '+T.BUTTONCLASS+'">'+this.options.deselectAllText+"</button></div></div>"),this.multiple&&this.options.doneButton&&(r='<div class="bs-donebutton"><div class="btn-group"><button type="button" class="btn btn-sm '+T.BUTTONCLASS+'">'+this.options.doneButtonText+"</button></div></div>"),this.options.allowClear&&(a='<span class="close bs-select-clear-selected" title="'+this.options.deselectAllText+'"><span>×</span>'),e='<div class="dropdown bootstrap-select'+e+i+'"><button type="button" tabindex="-1" class="'+this.options.styleBase+' dropdown-toggle" '+("static"===this.options.display?'data-display="static"':"")+z.DATA_TOGGLE+s+' role="combobox" aria-owns="'+this.selectId+'" aria-haspopup="listbox" aria-expanded="false"><div class="filter-option"><div class="filter-option-inner"><div class="filter-option-inner-inner"> </div></div> </div>'+a+"</span>"+("4"<=g.major?"":'<span class="bs-caret">'+this.options.template.caret+"</span>")+'</button><div class="'+T.MENU+" "+("4"<=g.major?"":T.SHOW)+'">'+n+o+l+'<div class="inner '+T.SHOW+'" role="listbox" id="'+this.selectId+'" tabindex="-1" '+t+'><ul class="'+T.MENU+" inner "+("4"<=g.major?T.SHOW:"")+'" role="presentation"></ul></div>'+r+"</div></div>",$(e)},setPositionData:function(){this.selectpicker.view.canHighlight=[],this.selectpicker.view.size=0,this.selectpicker.view.firstHighlightIndex=!1;for(var e=0;e<this.selectpicker.current.data.length;e++){var t=this.selectpicker.current.data[e],i=!0;"divider"===t.type?(i=!1,t.height=this.sizeInfo.dividerHeight):"optgroup-label"===t.type?(i=!1,t.height=this.sizeInfo.dropdownHeaderHeight):t.height=this.sizeInfo.liHeight,t.disabled&&(i=!1),this.selectpicker.view.canHighlight.push(i),i&&(this.selectpicker.view.size++,t.posinset=this.selectpicker.view.size,!1===this.selectpicker.view.firstHighlightIndex&&(this.selectpicker.view.firstHighlightIndex=e)),t.position=(0===e?0:this.selectpicker.current.data[e-1].position)+t.height}},isVirtual:function(){return!1!==this.options.virtualScroll&&this.selectpicker.main.data.length>=this.options.virtualScroll||!0===this.options.virtualScroll},createView:function(y,e,t){var x=this,i=0;function E(e,t){var i,s=x.selectpicker.current.data.length,n=[],o=!0,l=x.isVirtual();x.selectpicker.view.scrollTop=e;for(var r,a=x.options.chunkSize,c=Math.ceil(s/a)||1,d=0;d<c;d++){var h=d===c-1?s:(d+1)*a;if(n[d]=[d*a+(d?1:0),h],!s)break;void 0===i&&e-1<=x.selectpicker.current.data[h-1].position-x.sizeInfo.menuInnerHeight&&(i=d)}if(void 0===i&&(i=0),g=[x.selectpicker.view.position0,x.selectpicker.view.position1],p=Math.max(0,i-1),f=Math.min(c-1,i+1),x.selectpicker.view.position0=!1!==l&&Math.max(0,n[p][0])||0,x.selectpicker.view.position1=!1===l?s:Math.min(s,n[f][1])||0,p=g[0]!==x.selectpicker.view.position0||g[1]!==x.selectpicker.view.position1,void 0!==x.activeElement&&(t&&(x.activeElement!==x.selectedElement&&x.defocusItem(x.activeElement),x.activeElement=void 0),x.activeElement!==x.selectedElement&&x.defocusItem(x.selectedElement)),void 0!==x.prevActiveElement&&x.prevActiveElement!==x.activeElement&&x.prevActiveElement!==x.selectedElement&&x.defocusItem(x.prevActiveElement),t||p||x.selectpicker.current.hasMore){if(f=x.selectpicker.view.visibleElements?x.selectpicker.view.visibleElements.slice():[],x.selectpicker.view.visibleElements=!1===l?x.selectpicker.current.elements:x.selectpicker.current.elements.slice(x.selectpicker.view.position0,x.selectpicker.view.position1),x.setOptionStatus(),(y||!1===l&&t)&&(g=f,r=x.selectpicker.view.visibleElements,o=!(g.length===r.length&&g.every(function(e,t){return e===r[t]}))),(t||!0===l)&&o){var p=x.$menuInner[0],u=document.createDocumentFragment(),f=p.firstChild.cloneNode(!1),m=x.selectpicker.view.visibleElements,v=[];p.replaceChild(f,p.firstChild);for(var g,d=0,b=m.length;d<b;d++){var w,k,I=m[d];x.options.sanitize&&(w=I.lastChild)&&(k=x.selectpicker.current.data[d+x.selectpicker.view.position0])&&k.content&&!k.sanitized&&(v.push(w),k.sanitized=!0),u.appendChild(I)}x.options.sanitize&&v.length&&S(v,x.options.whiteList,x.options.sanitizeFn),!0===l?(g=0===x.selectpicker.view.position0?0:x.selectpicker.current.data[x.selectpicker.view.position0-1].position,o=x.selectpicker.view.position1>s-1?0:x.selectpicker.current.data[s-1].position-x.selectpicker.current.data[x.selectpicker.view.position1-1].position,p.firstChild.style.marginTop=g+"px",p.firstChild.style.marginBottom=o+"px"):(p.firstChild.style.marginTop=0,p.firstChild.style.marginBottom=0),p.firstChild.appendChild(u),!0===l&&x.sizeInfo.hasScrollBar&&(f=p.firstChild.offsetWidth,t&&f<x.sizeInfo.menuInnerInnerWidth&&x.sizeInfo.totalMenuWidth>x.sizeInfo.selectWidth?p.firstChild.style.minWidth=x.sizeInfo.menuInnerInnerWidth+"px":f>x.sizeInfo.menuInnerInnerWidth&&(x.$menu[0].style.minWidth=0,(g=p.firstChild.offsetWidth)>x.sizeInfo.menuInnerInnerWidth&&(x.sizeInfo.menuInnerInnerWidth=g,p.firstChild.style.minWidth=x.sizeInfo.menuInnerInnerWidth+"px"),x.$menu[0].style.minWidth=""))}(!y&&x.options.source.data||y&&x.options.source.search)&&x.selectpicker.current.hasMore&&i===c-1&&0<e&&(o=Math.floor(i*x.options.chunkSize/x.options.source.pageSize)+2,x.fetchData(function(){x.render(),x.buildList(s,y),x.setPositionData(),E(e)},y?"search":"data",o,y?x.selectpicker.search.previousValue:void 0))}x.prevActiveElement=x.activeElement,x.options.liveSearch?y&&t&&(x.selectpicker.view.canHighlight[l=0]||(l=1+x.selectpicker.view.canHighlight.slice(1).indexOf(!0)),f=x.selectpicker.view.visibleElements[l],x.defocusItem(x.selectpicker.view.currentActive),x.activeElement=(x.selectpicker.current.data[l]||{}).element,x.focusItem(f)):x.$menuInner.trigger("focus")}this.selectpicker.isSearching=y,this.selectpicker.current=y?this.selectpicker.search:this.selectpicker.main,this.setPositionData(),e&&(t?i=this.$menuInner[0].scrollTop:x.multiple||"number"==typeof(t=((e=x.$element[0]).options[e.selectedIndex]||{}).liIndex)&&!1!==x.options.size&&(t=(e=x.selectpicker.main.data[t])&&e.position)&&(i=t-(x.sizeInfo.menuInnerHeight+x.sizeInfo.liHeight)/2)),E(i,!0),this.$menuInner.off("scroll.createView").on("scroll.createView",function(e,t){x.noScroll||E(this.scrollTop,t),x.noScroll=!1}),$(window).off("resize"+A+"."+this.selectId+".createView").on("resize"+A+"."+this.selectId+".createView",function(){x.$newElement.hasClass(T.SHOW)&&E(x.$menuInner[0].scrollTop)})},focusItem:function(e,t,i){var s;e&&(t=t||this.selectpicker.current.data[this.selectpicker.current.elements.indexOf(this.activeElement)],(s=e.firstChild)&&(s.setAttribute("aria-setsize",this.selectpicker.view.size),s.setAttribute("aria-posinset",t.posinset),!0!==i&&(this.focusedParent.setAttribute("aria-activedescendant",s.id),e.classList.add("active"),s.classList.add("active"))))},defocusItem:function(e){e&&(e.classList.remove("active"),e.firstChild&&e.firstChild.classList.remove("active"))},setPlaceholder:function(){var e,t,i,s,n,o,l,r=this,a=!1;return!this.options.placeholder&&!this.options.allowClear||this.multiple||(this.selectpicker.view.titleOption||(this.selectpicker.view.titleOption=document.createElement("option")),e=this.$element[0],t=!(a=!0),i=!this.selectpicker.view.titleOption.parentNode,s=e.selectedIndex,n=e.options[s],o=(o=e.querySelector("select > *:not(:disabled)"))?o.index:0,l=(l=window.performance&&window.performance.getEntriesByType("navigation"))&&l.length?"back_forward"!==l[0].type:2!==window.performance.navigation.type,i&&(this.selectpicker.view.titleOption.className="bs-title-option",this.selectpicker.view.titleOption.value="",t=!n||s===o&&!1===n.defaultSelected&&void 0===this.$element.data("selected")),!i&&0===this.selectpicker.view.titleOption.index||e.insertBefore(this.selectpicker.view.titleOption,e.firstChild),t&&l?e.selectedIndex=0:"complete"!==document.readyState&&window.addEventListener("pageshow",function(){r.selectpicker.view.displayedValue!==e.value&&r.render()})),a},fetchData:function(n,o,e,t){e=e||1,o=o||"data";var l,r=this,i=this.options.source[o];i?(this.options.virtualScroll=!0,"function"==typeof i?i.call(this,function(e,t,i){var s=r.selectpicker["search"===o?"search":"main"];s.hasMore=t,s.totalItems=i,l=r.buildData(e,o),n.call(r,l),r.$element.trigger("fetched"+A)},e,t):Array.isArray(i)&&(l=r.buildData(i,o),n.call(r,l))):(l=this.buildData(!1,o),n.call(r,l))},buildData:function(h,e){var o=this,p=!1===h?N.fromOption:N.fromDataSource,u=':not([hidden]):not([data-hidden="true"]):not([style*="display: none"])',f=[],l=this.selectpicker.main.data?this.selectpicker.main.data.length:0,m=0,v=this.setPlaceholder()&&!h?1:0,t=("search"===e&&(l=this.selectpicker.search.data.length),this.options.hideDisabled&&(u+=":not(:disabled)"),h?h.filter(ne,this):this.$element[0].querySelectorAll("select > *"+u));function g(e){var t=f[f.length-1];t&&"divider"===t.type&&(t.optID||e.optID)||((e=e||{}).type="divider",f.push(e))}function b(e,t){var i,s,n;(t=t||{}).divider=p(e,"divider"),!0===t.divider?g({optID:t.optID}):(i=f.length+l,s=(s=p(e,"style"))?k(s):"",n=(e.className||"")+(t.optgroupClass||""),t.optID&&(n="opt "+n),t.optionClass=n.trim(),t.inlineStyle=s,t.text=p(e,"text"),t.title=p(e,"title"),t.content=p(e,"content"),t.tokens=p(e,"tokens"),t.subtext=p(e,"subtext"),t.icon=p(e,"icon"),t.display=t.content||t.text,t.value=void 0===e.value?e.text:e.value,t.type="option",t.index=i,t.option=e.option||e,t.option.liIndex=i,t.selected=!!e.selected,t.disabled=t.disabled||!!e.disabled,!1!==h&&(o.selectpicker.optionValuesDataMap[t.value]?t=$.extend(o.selectpicker.optionValuesDataMap[t.value],t):o.selectpicker.optionValuesDataMap[t.value]=t),f.push(t))}function i(e,t){var i=t[e],s=!(e-1<v)&&t[e-1],t=t[e+1],n=h?i.children.filter(ne,this):i.querySelectorAll("option"+u);if(n.length){var o,l,r={display:k(p(w,"label")),subtext:p(i,"subtext"),icon:p(i,"icon"),type:"optgroup-label",optgroupClass:" "+(i.className||""),optgroup:i};m++,s&&g({optID:m}),r.optID=m,f.push(r);for(var a=0,c=n.length;a<c;a++){var d=n[a];0===a&&(l=(o=f.length-1)+c),b(d,{headerIndex:o,lastIndex:l,optID:r.optID,optgroupClass:r.optgroupClass,disabled:i.disabled})}t&&g({optID:m})}}for(var s=t.length,n=v;n<s;n++){var w=t[n],r=w.children;r&&r.length?i.call(this,n,t):b.call(this,w,{})}switch(e){case"data":this.selectpicker.main.data||(this.selectpicker.main.data=[]),Array.prototype.push.apply(this.selectpicker.main.data,f),this.selectpicker.current.data=this.selectpicker.main.data;break;case"search":Array.prototype.push.apply(this.selectpicker.search.data,f)}return f},buildList:function(e,t){var i=this,s=(t?this.selectpicker.search:this.selectpicker.main).data,n=[],o=0;!i.options.showTick&&!i.multiple||D.checkMark.parentNode||(D.checkMark.className=this.options.iconBase+" "+i.options.tickIcon+" check-mark",D.a.appendChild(D.checkMark));for(var l=s.length,r=e||0;r<l;r++){var a,c=s[r],d=(p=a=h=d=void 0,n),h=c,p=0;switch(h.type){case"divider":a=L.li(!1,T.DIVIDER,h.optID?h.optID+"div":void 0);break;case"option":(a=L.li(L.a(L.text.call(i,h),h.optionClass,h.inlineStyle),"",h.optID)).firstChild&&(a.firstChild.id=i.selectId+"-"+h.index);break;case"optgroup-label":a=L.li(L.label.call(i,h),"dropdown-header"+h.optgroupClass,h.optID)}h.element?h.element.innerHTML=a.innerHTML:h.element=a,d.push(h.element),h.display&&(p+=h.display.length),h.subtext&&(p+=h.subtext.length),h.icon&&(p+=1),o<p&&(o=p,i.selectpicker.view.widestOption=d[d.length-1])}e?t?Array.prototype.push.apply(this.selectpicker.search.elements,n):(Array.prototype.push.apply(this.selectpicker.main.elements,n),this.selectpicker.current.elements=this.selectpicker.main.elements):t?this.selectpicker.search.elements=n:this.selectpicker.main.elements=this.selectpicker.current.elements=n},findLis:function(){return this.$menuInner.find(".inner > li")},render:function(e){var i=this,t=this.$element[0],s=this.setPlaceholder()&&0===t.selectedIndex,n=y.call(this),o=n.length,l=x.call(this,n),r=this.$button[0],a=r.querySelector(".filter-option-inner-inner"),c=document.createTextNode(this.options.multipleSeparator),d=D.fragment.cloneNode(!1),h=!1;if(this.options.source.data&&e&&(n.map(function e(t){t.selected?i.createOption(t,!0):t.children&&t.children.length&&t.children.map(e)}),t.appendChild(this.selectpicker.main.optionQueue),s=s&&0===t.selectedIndex),r.classList.toggle("bs-placeholder",i.multiple?!o:!l&&0!==l),i.multiple||1!==n.length||(i.selectpicker.view.displayedValue=l),"static"===this.options.selectedTextFormat)d=L.text.call(this,{text:this.options.placeholder},!0);else if(!1===(this.multiple&&-1!==this.options.selectedTextFormat.indexOf("count")&&0<o&&(1<(e=this.options.selectedTextFormat.split(">")).length&&o>e[1]||1===e.length&&2<=o))){if(!s){for(var p=0;p<o&&p<50;p++){var u=n[p],f={};u&&(this.multiple&&0<p&&d.appendChild(c.cloneNode(!1)),u.title?f.text=u.title:u.content&&i.options.showContent?(f.content=u.content.toString(),h=!0):(i.options.showIcon&&(f.icon=u.icon),i.options.showSubtext&&!i.multiple&&u.subtext&&(f.subtext=" "+u.subtext),f.text=u.text.trim()),d.appendChild(L.text.call(this,f,!0)))}49<o&&d.appendChild(document.createTextNode("..."))}}else var t=':not([hidden]):not([data-hidden="true"]):not([data-divider="true"]):not([style*="display: none"])',l=(this.options.hideDisabled&&(t+=":not(:disabled)"),this.$element[0].querySelectorAll("select > option"+t+", optgroup"+t+" option"+t).length),e="function"==typeof this.options.countSelectedText?this.options.countSelectedText(o,l):this.options.countSelectedText,d=L.text.call(this,{text:e.replace("{0}",o.toString()).replace("{1}",l.toString())},!0);d.childNodes.length||(d=L.text.call(this,{text:this.options.placeholder||this.options.noneSelectedText},!0)),r.title=d.textContent.replace(/<[^>]*>?/g,"").trim(),this.options.sanitize&&h&&S([d],i.options.whiteList,i.options.sanitizeFn),a.innerHTML="",a.appendChild(d),g.major<4&&this.$newElement[0].classList.contains("bs3-has-addon")&&(s=r.querySelector(".filter-expand"),(t=a.cloneNode(!0)).className="filter-expand",s?r.replaceChild(t,s):r.appendChild(t)),this.$element.trigger("rendered"+A)},setStyle:function(e,t){var i=this.$button[0],s=this.$newElement[0],n=this.options.style.trim();this.$element.attr("class")&&this.$newElement.addClass(this.$element.attr("class").replace(/selectpicker|mobile-device|bs-select-hidden|validate\[.*\]/gi,"")),g.major<4&&(s.classList.add("bs3"),s.parentNode.classList&&s.parentNode.classList.contains("input-group")&&(s.previousElementSibling||s.nextElementSibling)&&(s.previousElementSibling||s.nextElementSibling).classList.contains("input-group-addon")&&s.classList.add("bs3-has-addon")),s=e?e.trim():n,"add"==t?s&&i.classList.add.apply(i.classList,s.split(" ")):"remove"==t?s&&i.classList.remove.apply(i.classList,s.split(" ")):(n&&i.classList.remove.apply(i.classList,n.split(" ")),s&&i.classList.add.apply(i.classList,s.split(" ")))},liHeight:function(e){if(e||!1!==this.options.size&&!Object.keys(this.sizeInfo).length){var t,e=D.div.cloneNode(!1),i=D.div.cloneNode(!1),s=D.div.cloneNode(!1),n=document.createElement("ul"),o=D.li.cloneNode(!1),l=D.li.cloneNode(!1),r=D.a.cloneNode(!1),a=D.span.cloneNode(!1),c=this.options.header&&0<this.$menu.find("."+T.POPOVERHEADER).length?this.$menu.find("."+T.POPOVERHEADER)[0].cloneNode(!0):null,d=this.options.liveSearch?D.div.cloneNode(!1):null,h=this.options.actionsBox&&this.multiple&&0<this.$menu.find(".bs-actionsbox").length?this.$menu.find(".bs-actionsbox")[0].cloneNode(!0):null,p=this.options.doneButton&&this.multiple&&0<this.$menu.find(".bs-donebutton").length?this.$menu.find(".bs-donebutton")[0].cloneNode(!0):null,u=this.$element[0].options[0];if(this.sizeInfo.selectWidth=this.$newElement[0].offsetWidth,a.className="text",r.className="dropdown-item "+(u?u.className:""),e.className=this.$menu[0].parentNode.className+" "+T.SHOW,e.style.width=0,"auto"===this.options.width&&(i.style.minWidth=0),i.className=T.MENU+" "+T.SHOW,s.className="inner "+T.SHOW,n.className=T.MENU+" inner "+("4"<=g.major?T.SHOW:""),o.className=T.DIVIDER,l.className="dropdown-header",a.appendChild(document.createTextNode("\u200b")),this.selectpicker.current.data.length)for(var f=0;f<this.selectpicker.current.data.length;f++){var m=this.selectpicker.current.data[f];if("option"===m.type&&"none"!==$(m.element.firstChild).css("display")){t=m.element;break}}else t=D.li.cloneNode(!1),r.appendChild(a),t.appendChild(r);l.appendChild(a.cloneNode(!0)),this.selectpicker.view.widestOption&&n.appendChild(this.selectpicker.view.widestOption.cloneNode(!0)),n.appendChild(t),n.appendChild(o),n.appendChild(l),c&&i.appendChild(c),d&&(u=document.createElement("input"),d.className="bs-searchbox",u.className="form-control",d.appendChild(u),i.appendChild(d)),h&&i.appendChild(h),s.appendChild(n),i.appendChild(s),p&&i.appendChild(p),e.appendChild(i),document.body.appendChild(e);r=t.offsetHeight,a=l?l.offsetHeight:0,u=c?c.offsetHeight:0,n=d?d.offsetHeight:0,l=h?h.offsetHeight:0,c=p?p.offsetHeight:0,d=$(o).outerHeight(!0),h=window.getComputedStyle(i),p=i.offsetWidth,o={vert:v(h.paddingTop)+v(h.paddingBottom)+v(h.borderTopWidth)+v(h.borderBottomWidth),horiz:v(h.paddingLeft)+v(h.paddingRight)+v(h.borderLeftWidth)+v(h.borderRightWidth)},h={vert:o.vert+v(h.marginTop)+v(h.marginBottom)+2,horiz:o.horiz+v(h.marginLeft)+v(h.marginRight)+2};s.style.overflowY="scroll",s=i.offsetWidth-p,document.body.removeChild(e),this.sizeInfo.liHeight=r,this.sizeInfo.dropdownHeaderHeight=a,this.sizeInfo.headerHeight=u,this.sizeInfo.searchHeight=n,this.sizeInfo.actionsHeight=l,this.sizeInfo.doneButtonHeight=c,this.sizeInfo.dividerHeight=d,this.sizeInfo.menuPadding=o,this.sizeInfo.menuExtras=h,this.sizeInfo.menuWidth=p,this.sizeInfo.menuInnerInnerWidth=p-o.horiz,this.sizeInfo.totalMenuWidth=this.sizeInfo.menuWidth,this.sizeInfo.scrollBarWidth=s,this.sizeInfo.selectHeight=this.$newElement[0].offsetHeight,this.setPositionData()}},getSelectPosition:function(){var e,t=$(window),i=this.$newElement.offset(),s=$(this.options.container),s=(this.options.container&&s.length&&!s.is("body")?((e=s.offset()).top+=parseInt(s.css("borderTopWidth")),e.left+=parseInt(s.css("borderLeftWidth"))):e={top:0,left:0},this.options.windowPadding);this.sizeInfo.selectOffsetTop=i.top-e.top-t.scrollTop(),this.sizeInfo.selectOffsetBot=t.height()-this.sizeInfo.selectOffsetTop-this.sizeInfo.selectHeight-e.top-s[2],this.sizeInfo.selectOffsetLeft=i.left-e.left-t.scrollLeft(),this.sizeInfo.selectOffsetRight=t.width()-this.sizeInfo.selectOffsetLeft-this.sizeInfo.selectWidth-e.left-s[1],this.sizeInfo.selectOffsetTop-=s[0],this.sizeInfo.selectOffsetLeft-=s[3]},setMenuSize:function(e){this.getSelectPosition();var t,i,s,n,o,l,r=this.sizeInfo.selectWidth,a=this.sizeInfo.liHeight,c=this.sizeInfo.headerHeight,d=this.sizeInfo.searchHeight,h=this.sizeInfo.actionsHeight,p=this.sizeInfo.doneButtonHeight,u=this.sizeInfo.dividerHeight,f=this.sizeInfo.menuPadding,m=0;if(this.options.dropupAuto&&(l=a*this.selectpicker.current.data.length+f.vert,l=this.sizeInfo.selectOffsetTop-this.sizeInfo.selectOffsetBot>this.sizeInfo.menuExtras.vert&&l+this.sizeInfo.menuExtras.vert+50>this.sizeInfo.selectOffsetBot,!0===this.selectpicker.isSearching&&(l=this.selectpicker.dropup),this.$newElement.toggleClass(T.DROPUP,l),this.selectpicker.dropup=l),"auto"===this.options.size)l=3<this.selectpicker.current.data.length?3*this.sizeInfo.liHeight+this.sizeInfo.menuExtras.vert-2:0,i=this.sizeInfo.selectOffsetBot-this.sizeInfo.menuExtras.vert,s=l+c+d+h+p,o=Math.max(l-f.vert,0),t=(n=i=this.$newElement.hasClass(T.DROPUP)?this.sizeInfo.selectOffsetTop-this.sizeInfo.menuExtras.vert:i)-c-d-h-p-f.vert;else if(this.options.size&&"auto"!=this.options.size&&this.selectpicker.current.elements.length>this.options.size){for(var v=0;v<this.options.size;v++)"divider"===this.selectpicker.current.data[v].type&&m++;t=(i=a*this.options.size+m*u+f.vert)-f.vert,n=i+c+d+h+p,s=o=""}this.$menu.css({"max-height":n+"px",overflow:"hidden","min-height":s+"px"}),this.$menuInner.css({"max-height":t+"px",overflow:"hidden auto","min-height":o+"px"}),this.sizeInfo.menuInnerHeight=Math.max(t,1),this.selectpicker.current.data.length&&this.selectpicker.current.data[this.selectpicker.current.data.length-1].position>this.sizeInfo.menuInnerHeight&&(this.sizeInfo.hasScrollBar=!0,this.sizeInfo.totalMenuWidth=this.sizeInfo.menuWidth+this.sizeInfo.scrollBarWidth),"auto"===this.options.dropdownAlignRight&&this.$menu.toggleClass(T.MENURIGHT,this.sizeInfo.selectOffsetLeft>this.sizeInfo.selectOffsetRight&&this.sizeInfo.selectOffsetRight<this.sizeInfo.totalMenuWidth-r),this.dropdown&&this.dropdown._popper&&this.dropdown._popper.update()},setSize:function(e){var t,i;this.liHeight(e),this.options.header&&this.$menu.css("padding-top",0),!1!==this.options.size&&(t=this,i=$(window),this.setMenuSize(),this.options.liveSearch&&this.$searchbox.off("input.setMenuSize propertychange.setMenuSize").on("input.setMenuSize propertychange.setMenuSize",function(){return t.setMenuSize()}),"auto"===this.options.size?i.off("resize"+A+"."+this.selectId+".setMenuSize scroll"+A+"."+this.selectId+".setMenuSize").on("resize"+A+"."+this.selectId+".setMenuSize scroll"+A+"."+this.selectId+".setMenuSize",function(){return t.setMenuSize()}):this.options.size&&"auto"!=this.options.size&&this.selectpicker.current.elements.length>this.options.size&&i.off("resize"+A+"."+this.selectId+".setMenuSize scroll"+A+"."+this.selectId+".setMenuSize")),this.createView(!1,!0,e)},setWidth:function(){var i=this;"auto"===this.options.width?requestAnimationFrame(function(){i.$menu.css("min-width","0"),i.$element.on("loaded"+A,function(){i.liHeight(),i.setMenuSize();var e=i.$newElement.clone().appendTo("body"),t=e.css("width","auto").children("button").outerWidth();e.remove(),i.sizeInfo.selectWidth=Math.max(i.sizeInfo.totalMenuWidth,t),i.$newElement.css("width",i.sizeInfo.selectWidth+"px")})}):"fit"===this.options.width?(this.$menu.css("min-width",""),this.$newElement.css("width","").addClass("fit-width")):this.options.width?(this.$menu.css("min-width",""),this.$newElement.css("width",this.options.width)):(this.$menu.css("min-width",""),this.$newElement.css("width","")),this.$newElement.hasClass("fit-width")&&"fit"!==this.options.width&&this.$newElement[0].classList.remove("fit-width")},selectPosition:function(){this.$bsContainer=$('<div class="bs-container" />');function e(e){var t={},i=l.options.display||!!$.fn.dropdown.Constructor.Default&&$.fn.dropdown.Constructor.Default.display;l.$bsContainer.addClass(e.attr("class").replace(/form-control|fit-width/gi,"")).toggleClass(T.DROPUP,e.hasClass(T.DROPUP)),s=e.offset(),r.is("body")?n={top:0,left:0}:((n=r.offset()).top+=parseInt(r.css("borderTopWidth"))-r.scrollTop(),n.left+=parseInt(r.css("borderLeftWidth"))-r.scrollLeft()),o=e.hasClass(T.DROPUP)?0:e[0].offsetHeight,(g.major<4||"static"===i)&&(t.top=s.top-n.top+o,t.left=s.left-n.left),t.width=e[0].offsetWidth,l.$bsContainer.css(t)}var s,n,o,l=this,r=$(this.options.container);this.$button.on("click.bs.dropdown.data-api",function(){l.isDisabled()||(e(l.$newElement),l.$bsContainer.appendTo(l.options.container).toggleClass(T.SHOW,!l.$button.hasClass(T.SHOW)).append(l.$menu))}),$(window).off("resize"+A+"."+this.selectId+" scroll"+A+"."+this.selectId).on("resize"+A+"."+this.selectId+" scroll"+A+"."+this.selectId,function(){l.$newElement.hasClass(T.SHOW)&&e(l.$newElement)}),this.$element.on("hide"+A,function(){l.$menu.data("height",l.$menu.height()),l.$bsContainer.detach()})},createOption:function(e,t){var i,s=e.option||e;s&&1!==s.nodeType&&(i=(t?D.selectedOption:D.option).cloneNode(!0),void 0!==s.value&&(i.value=s.value),i.textContent=s.text,i.selected=!0,void 0!==s.liIndex?i.liIndex=s.liIndex:t||(i.liIndex=e.index),e.option=i,this.selectpicker.main.optionQueue.appendChild(i))},setOptionStatus:function(e){var t=this;if(t.noScroll=!1,t.selectpicker.view.visibleElements&&t.selectpicker.view.visibleElements.length){for(var i=0;i<t.selectpicker.view.visibleElements.length;i++){var s=t.selectpicker.current.data[i+t.selectpicker.view.position0];s.option&&(!0!==e&&t.setDisabled(s),t.setSelected(s))}this.options.source.data&&this.$element[0].appendChild(this.selectpicker.main.optionQueue)}},setSelected:function(e,t){t=void 0===t?e.selected:t;var i,s=e.element,n=void 0!==this.activeElement,o=this.activeElement===s||t&&!this.multiple&&!n;s&&(void 0!==t&&(e.selected=t,e.option&&(e.option.selected=t)),t&&this.options.source.data&&this.createOption(e,!1),i=s.firstChild,t&&(this.selectedElement=s),s.classList.toggle("selected",t),o?(this.focusItem(s,e),this.selectpicker.view.currentActive=s,this.activeElement=s):this.defocusItem(s),i&&(i.classList.toggle("selected",t),t?i.setAttribute("aria-selected",!0):this.multiple?i.setAttribute("aria-selected",!1):i.removeAttribute("aria-selected")),o||n||!t||void 0===this.prevActiveElement||(e=this.prevActiveElement,this.defocusItem(e)))},setDisabled:function(e){var t,i=e.disabled,e=e.element;e&&(t=e.firstChild,e.classList.toggle(T.DISABLED,i),t&&("4"<=g.major&&t.classList.toggle(T.DISABLED,i),i?(t.setAttribute("aria-disabled",i),t.setAttribute("tabindex",-1)):(t.removeAttribute("aria-disabled"),t.setAttribute("tabindex",0))))},isDisabled:function(){return this.$element[0].disabled},checkDisabled:function(){this.isDisabled()?(this.$newElement[0].classList.add(T.DISABLED),this.$button.addClass(T.DISABLED).attr("aria-disabled",!0)):this.$button[0].classList.contains(T.DISABLED)&&(this.$newElement[0].classList.remove(T.DISABLED),this.$button.removeClass(T.DISABLED).attr("aria-disabled",!1))},clickListener:function(){var I=this,t=$(document);function e(){(I.options.liveSearch?I.$searchbox:I.$menuInner).trigger("focus")}function i(){I.dropdown&&I.dropdown._popper&&I.dropdown._popper.state?e():requestAnimationFrame(i)}t.data("spaceSelect",!1),this.$button.on("keyup",function(e){/(32)/.test(e.keyCode.toString(10))&&t.data("spaceSelect")&&(e.preventDefault(),t.data("spaceSelect",!1))}),this.$newElement.on("show.bs.dropdown",function(){I.dropdown||"4"!==g.major||(I.dropdown=I.$button.data("bs.dropdown"),I.dropdown._menu=I.$menu[0])}),this.$button.on("click.bs.dropdown.data-api",function(e){var t,i,s;I.options.allowClear&&(t=e.target,i=I.$clearButton[0],(t=/MSIE|Trident/.test(window.navigator.userAgent)?document.elementFromPoint(e.clientX,e.clientY):t)!==i&&t.parentElement!==i||(e.stopImmediatePropagation(),I.multiple?I.deselectAll():(i=(t=I.$element[0]).value,e=t.selectedIndex,(s=!!(s=t.options[e])&&I.selectpicker.main.data[s.liIndex])&&I.setSelected(s,!1),t.selectedIndex=0,E=[e,!1,i],I.$element.triggerNative("change")),I.$newElement.hasClass(T.SHOW)&&(I.options.liveSearch&&I.$searchbox.trigger("focus"),I.createView(!1)))),I.$newElement.hasClass(T.SHOW)||I.setSize()}),this.$element.on("shown"+A,function(){I.$menuInner[0].scrollTop!==I.selectpicker.view.scrollTop&&(I.$menuInner[0].scrollTop=I.selectpicker.view.scrollTop),3<g.major?requestAnimationFrame(i):e()}),this.$menuInner.on("mouseenter","li a",function(e){var t=this.parentElement,i=I.isVirtual()?I.selectpicker.view.position0:0,s=Array.prototype.indexOf.call(t.parentElement.children,t),s=I.selectpicker.current.data[s+i];I.focusItem(t,s,!0)}),this.$menuInner.on("click","li a",function(e,t){var i=$(this),s=I.$element[0],n=I.isVirtual()?I.selectpicker.view.position0:0,o=I.selectpicker.current.data[i.parent().index()+n],n=o.element,l=x.call(I),r=s.selectedIndex,a=s.options[r],a=!!a&&I.selectpicker.main.data[a.liIndex],c=!0;if(I.multiple&&1!==I.options.maxOptions&&e.stopPropagation(),e.preventDefault(),!I.isDisabled()&&!i.parent().hasClass(T.DISABLED)){var e=o.option,i=$(e),d=e.selected,h=I.selectpicker.current.data.find(function(e){return e.optID===o.optID&&"optgroup-label"===e.type}),p=h?h.optgroup:void 0,h=p instanceof Element?N.fromOption:N.fromDataSource,u=p&&p.children,f=parseInt(I.options.maxOptions),h=p&&parseInt(h(p,"maxOptions"))||!1;if((t=n===I.activeElement?!0:t)||(I.prevActiveElement=I.activeElement,I.activeElement=void 0),I.multiple&&1!==f){if(I.setSelected(o,!d),I.focusedParent.focus(),!1!==f||!1!==h){var n=f<y.call(I).length,m=0;if(p&&p.children)for(var v=0;v<p.children.length;v++)p.children[v].selected&&m++;t=h<m;if(f&&n||h&&t)if(f&&1===f)s.selectedIndex=-1,I.setOptionStatus(!0);else if(h&&1===h){for(v=0;v<u.length;v++){var g=u[v];I.setSelected(I.selectpicker.current.data[g.liIndex],!1)}I.setSelected(o,!0)}else{var d="string"==typeof I.options.maxOptionsText?[I.options.maxOptionsText,I.options.maxOptionsText]:I.options.maxOptionsText,d="function"==typeof d?d(f,h):d,b=d[0].replace("{n}",f),w=d[1].replace("{n}",h),k=$('<div class="notify"></div>');d[2]&&(b=b.replace("{var}",d[2][1<f?0:1]),w=w.replace("{var}",d[2][1<h?0:1])),I.$menu.append(k),f&&n&&(k.append($("<div>"+b+"</div>")),c=!1,I.$element.trigger("maxReached"+A)),h&&t&&(k.append($("<div>"+w+"</div>")),c=!1,I.$element.trigger("maxReachedGrp"+A)),setTimeout(function(){I.setSelected(o,!1)},10),k[0].classList.add("fadeOut"),setTimeout(function(){k.remove()},1050)}}}else a&&I.setSelected(a,!1),I.setSelected(o,!0);I.options.source.data&&I.$element[0].appendChild(I.selectpicker.main.optionQueue),!I.multiple||I.multiple&&1===I.options.maxOptions?I.$button.trigger("focus"):I.options.liveSearch&&I.$searchbox.trigger("focus"),!c||!I.multiple&&r===s.selectedIndex||(E=[e.index,i.prop("selected"),l],I.$element.triggerNative("change"))}}),this.$menu.on("click","li."+T.DISABLED+" a, ."+T.POPOVERHEADER+", ."+T.POPOVERHEADER+" :not(.close)",function(e){e.currentTarget==this&&(e.preventDefault(),e.stopPropagation(),(I.options.liveSearch&&!$(e.target).hasClass("close")?I.$searchbox:I.$button).trigger("focus"))}),this.$menuInner.on("click",".divider, .dropdown-header",function(e){e.preventDefault(),e.stopPropagation(),(I.options.liveSearch?I.$searchbox:I.$button).trigger("focus")}),this.$menu.on("click","."+T.POPOVERHEADER+" .close",function(){I.$button.trigger("click")}),this.$searchbox.on("click",function(e){e.stopPropagation()}),this.$menu.on("click",".actions-btn",function(e){(I.options.liveSearch?I.$searchbox:I.$button).trigger("focus"),e.preventDefault(),e.stopPropagation(),$(this).hasClass("bs-select-all")?I.selectAll():I.deselectAll()}),this.$button.on("focus"+A,function(e){var t=I.$element[0].getAttribute("tabindex");void 0!==t&&e.originalEvent&&e.originalEvent.isTrusted&&(this.setAttribute("tabindex",t),I.$element[0].setAttribute("tabindex",-1),I.selectpicker.view.tabindex=t)}).on("blur"+A,function(e){void 0!==I.selectpicker.view.tabindex&&e.originalEvent&&e.originalEvent.isTrusted&&(I.$element[0].setAttribute("tabindex",I.selectpicker.view.tabindex),this.setAttribute("tabindex",-1),I.selectpicker.view.tabindex=void 0)}),this.$element.on("change"+A,function(){I.render(),I.$element.trigger("changed"+A,E),E=null}).on("focus"+A,function(){I.options.mobile||I.$button[0].focus()})},liveSearchListener:function(){var p=this;this.$button.on("click.bs.dropdown.data-api",function(){p.$searchbox.val()&&(p.$searchbox.val(""),p.selectpicker.search.previousValue=void 0)}),this.$searchbox.on("click.bs.dropdown.data-api focus.bs.dropdown.data-api touchend.bs.dropdown.data-api",function(e){e.stopPropagation()}),this.$searchbox.on("input propertychange",function(){var t=p.$searchbox[0].value;if(p.selectpicker.search.elements=[],p.selectpicker.search.data=[],t)if(p.selectpicker.search.previousValue=t,p.options.source.search)p.fetchData(function(e){p.render(),p.buildList(void 0,!0),p.noScroll=!0,p.$menuInner.scrollTop(0),p.createView(!0),se.call(p,e,t)},"search",0,t);else{var e=[],i=t.toUpperCase(),s={},n=[],o=p._searchStyle(),l=p.options.liveSearchNormalize;l&&(i=u(i));for(var r=0;r<p.selectpicker.main.data.length;r++){var a=p.selectpicker.main.data[r];s[r]||(s[r]=b(a,i,o,l)),s[r]&&void 0!==a.headerIndex&&-1===n.indexOf(a.headerIndex)&&(0<a.headerIndex&&(s[a.headerIndex-1]=!0,n.push(a.headerIndex-1)),s[a.headerIndex]=!0,n.push(a.headerIndex),s[a.lastIndex+1]=!0),s[r]&&"optgroup-label"!==a.type&&n.push(r)}for(var r=0,c=n.length;r<c;r++){var d=n[r],h=n[r-1],a=p.selectpicker.main.data[d],h=p.selectpicker.main.data[h];("divider"!==a.type||"divider"===a.type&&h&&"divider"!==h.type&&c-1!==r)&&(p.selectpicker.search.data.push(a),e.push(p.selectpicker.main.elements[d]))}p.activeElement=void 0,p.noScroll=!0,p.$menuInner.scrollTop(0),p.selectpicker.search.elements=e,p.createView(!0),se.call(p,e,t)}else p.selectpicker.search.previousValue&&(p.$menuInner.scrollTop(0),p.createView(!1))})},_searchStyle:function(){return this.options.liveSearchStyle||"contains"},val:function(t){var e=this.$element[0];if(void 0===t)return this.$element.val();var i=y.call(this),s=x.call(this,i);E=[null,null,s],(t=Array.isArray(t)?t:[t]).map(String);for(var n=0;n<i.length;n++){var o=i[n];o&&-1===t.indexOf(String(o.value))&&this.setSelected(o,!1)}return this.selectpicker.main.data.filter(function(e){return-1!==t.indexOf(String(e.value))&&(this.setSelected(e,!0),!0)},this),this.options.source.data&&e.appendChild(this.selectpicker.main.optionQueue),this.$element.trigger("changed"+A,E),this.$newElement.hasClass(T.SHOW)&&(this.multiple?this.setOptionStatus(!0):"number"==typeof(s=(e.options[e.selectedIndex]||{}).liIndex)&&this.setSelected(this.selectpicker.current.data[s],!0)),this.render(),E=null,this.$element},changeAll:function(e){if(this.multiple){void 0===e&&(e=!0);var t=this.$element[0],i=0,s=0,n=x.call(this);t.classList.add("bs-select-hidden");for(var o=0,l=this.selectpicker.current.data,r=l.length;o<r;o++){var a=l[o],c=a.option;c&&!a.disabled&&"divider"!==a.type&&(a.selected&&i++,c.selected=e,!0===(a.selected=e)&&s++)}t.classList.remove("bs-select-hidden"),i!==s&&(this.setOptionStatus(),E=[null,null,n],this.$element.triggerNative("change"))}},selectAll:function(){return this.changeAll(!0)},deselectAll:function(){return this.changeAll(!1)},toggle:function(e,t){var i=void 0===t;(e=e||window.event)&&e.stopPropagation(),!1===i&&(e=this.$newElement[0].classList.contains(T.SHOW),i=!0===t&&!1===e||!1===t&&!0===e),i&&this.$button.trigger("click.bs.dropdown.data-api")},open:function(e){this.toggle(e,!0)},close:function(e){this.toggle(e,!1)},keydown:function(e){var t,i,s,n,o=$(this),l=o.hasClass("dropdown-toggle"),r=(l?o.closest(".dropdown"):o.closest(z.MENU)).data("this"),a=r.findLis(),c=!1,l=e.which===I&&!l&&!r.options.selectOnTab,d=te.test(e.which)||l,h=r.$menuInner[0].scrollTop,p=!0===r.isVirtual()?r.selectpicker.view.position0:0;if(!(112<=e.which&&e.which<=123))if(!(t=r.$menu.hasClass(T.SHOW))&&(d||48<=e.which&&e.which<=57||96<=e.which&&e.which<=105||65<=e.which&&e.which<=90)&&(r.$button.trigger("click.bs.dropdown.data-api"),r.options.liveSearch))r.$searchbox.trigger("focus");else{if(e.which===Z&&t&&(e.preventDefault(),r.$button.trigger("click.bs.dropdown.data-api").trigger("focus")),d){if(!a.length)return;-1!==(d=(i=r.activeElement)?Array.prototype.indexOf.call(i.parentElement.children,i):-1)&&r.defocusItem(i),e.which===C?(-1!==d&&d--,d+p<0&&(d+=a.length),r.selectpicker.view.canHighlight[d+p]||-1===(d=r.selectpicker.view.canHighlight.slice(0,d+p).lastIndexOf(!0)-p)&&(d=a.length-1)):e.which!==O&&!l||(++d+p>=r.selectpicker.view.canHighlight.length&&(d=r.selectpicker.view.firstHighlightIndex),r.selectpicker.view.canHighlight[d+p]||(d=d+1+r.selectpicker.view.canHighlight.slice(d+p+1).indexOf(!0))),e.preventDefault();var u=p+d;e.which===C?0===p&&d===a.length-1?(r.$menuInner[0].scrollTop=r.$menuInner[0].scrollHeight,u=r.selectpicker.current.elements.length-1):(s=r.selectpicker.current.data[u])&&(c=(n=s.position-s.height)<h):e.which!==O&&!l||(d===r.selectpicker.view.firstHighlightIndex?(r.$menuInner[0].scrollTop=0,u=r.selectpicker.view.firstHighlightIndex):(s=r.selectpicker.current.data[u])&&(c=h<(n=s.position-r.sizeInfo.menuInnerHeight))),i=r.selectpicker.current.elements[u],r.activeElement=(r.selectpicker.current.data[u]||{}).element,r.focusItem(i),r.selectpicker.view.currentActive=i,c&&(r.$menuInner[0].scrollTop=n),(r.options.liveSearch?r.$searchbox:o).trigger("focus")}else if(!o.is("input")&&!ie.test(e.which)||e.which===w&&r.selectpicker.keydown.keyHistory){var f,m=[];e.preventDefault(),r.selectpicker.keydown.keyHistory+=Y[e.which],r.selectpicker.keydown.resetKeyHistory.cancel&&clearTimeout(r.selectpicker.keydown.resetKeyHistory.cancel),r.selectpicker.keydown.resetKeyHistory.cancel=r.selectpicker.keydown.resetKeyHistory.start(),f=r.selectpicker.keydown.keyHistory,/^(.)\1+$/.test(f)&&(f=f.charAt(0));for(var v=0;v<r.selectpicker.current.data.length;v++){var g=r.selectpicker.current.data[v];b(g,f,"startsWith",!0)&&r.selectpicker.view.canHighlight[v]&&m.push(g.element)}m.length&&(p=0,a.removeClass("active").find("a").removeClass("active"),1===f.length&&(-1===(p=m.indexOf(r.activeElement))||p===m.length-1?p=0:p++),l=m[p],c=0<h-(s=r.selectpicker.main.data[l]).position?(n=s.position-s.height,!0):(n=s.position-r.sizeInfo.menuInnerHeight,s.position>h+r.sizeInfo.menuInnerHeight),i=r.selectpicker.main.elements[l],r.activeElement=i,r.focusItem(i),i&&i.firstChild.focus(),c&&(r.$menuInner[0].scrollTop=n),o.trigger("focus"))}t&&(e.which===w&&!r.selectpicker.keydown.keyHistory||e.which===J||e.which===I&&r.options.selectOnTab)&&(e.which!==w&&e.preventDefault(),r.options.liveSearch&&e.which===w||(r.$menuInner.find(".active a").trigger("click",!0),o.trigger("focus"),r.options.liveSearch||(e.preventDefault(),$(document).data("spaceSelect",!0))))}},mobile:function(){this.options.mobile=!0,this.$element[0].classList.add("mobile-device")},refresh:function(){var e=this,t=$.extend({},this.options,d(this.$element),this.$element.data());this.options=t,this.options.source.data?(this.render(),this.buildList()):this.fetchData(function(){e.render(),e.buildList()}),this.checkDisabled(),this.setStyle(),this.setWidth(),this.setSize(!0),this.$element.trigger("refreshed"+A)},hide:function(){this.$newElement.hide()},show:function(){this.$newElement.show()},remove:function(){this.$newElement.remove(),this.$element.remove()},destroy:function(){this.$newElement.before(this.$element).remove(),(this.$bsContainer||this.$menu).remove(),this.selectpicker.view.titleOption&&this.selectpicker.view.titleOption.parentNode&&this.selectpicker.view.titleOption.parentNode.removeChild(this.selectpicker.view.titleOption),this.$element.off(A).removeData("selectpicker").removeClass("bs-select-hidden selectpicker mobile-device"),$(window).off(A+"."+this.selectId)}};var le=$.fn.selectpicker;function re(){return g.major<5?$.fn.dropdown?($.fn.dropdown.Constructor._dataApiKeydownHandler||$.fn.dropdown.Constructor.prototype.keydown).apply(this,arguments):void 0:m.dataApiKeydownHandler}$.fn.selectpicker=oe,$.fn.selectpicker.Constructor=H,$.fn.selectpicker.noConflict=function(){return $.fn.selectpicker=le,this},$(document).off("keydown.bs.dropdown.data-api").on("keydown.bs.dropdown.data-api",":not(.bootstrap-select) > ["+z.DATA_TOGGLE+"]",re).on("keydown.bs.dropdown.data-api",":not(.bootstrap-select) > .dropdown-menu",re).on("keydown"+A,".bootstrap-select ["+z.DATA_TOGGLE+'], .bootstrap-select [role="listbox"], .bootstrap-select .bs-searchbox input',H.prototype.keydown).on("focusin.modal",".bootstrap-select ["+z.DATA_TOGGLE+'], .bootstrap-select [role="listbox"], .bootstrap-select .bs-searchbox input',function(e){e.stopPropagation()}),document.addEventListener("DOMContentLoaded",function(){$(".selectpicker").each(function(){var e=$(this);oe.call(e,e.data())})})}(e)}); +//# sourceMappingURL=bootstrap-select.min.js.map \ No newline at end of file diff --git a/libs/ckeditor5-31.0.0-k356w86hp13l.zip b/libs/ckeditor5-31.0.0-k356w86hp13l.zip deleted file mode 100644 index 71523482a2bdfa7c0a9776eeed7aa7be010e053b..0000000000000000000000000000000000000000 Binary files a/libs/ckeditor5-31.0.0-k356w86hp13l.zip and /dev/null differ diff --git a/libs/ckeditor5-build-custom.zip b/libs/ckeditor5-build-custom.zip new file mode 100644 index 0000000000000000000000000000000000000000..cb06441337f97369c1bc83e2b627bafa7b584b6f Binary files /dev/null and b/libs/ckeditor5-build-custom.zip differ diff --git a/misc/entity_state_test_data.py b/misc/entity_state_test_data.py index 400b73749011834fb5390be2e00eebdde1a91062..ff06452cb33045e876e1f17a94862737e77a8207 100755 --- a/misc/entity_state_test_data.py +++ b/misc/entity_state_test_data.py @@ -3,7 +3,7 @@ import sys import caosdb as db -_PASSWORD = "password1A!" +_PASSWORD = "Password1!" def teardown(): @@ -49,6 +49,8 @@ def setup_users(): "Grant", "STATE:TRANSITION:Edit"), db.administration.PermissionRule( "Grant", "STATE:TRANSITION:Start Review"), + db.administration.PermissionRule( + "Grant", "STATE:ASSIGN:Publish Life-cycle"), ]) db.administration._set_permissions( @@ -180,9 +182,17 @@ def setup_test_data(): # any record of this type will have the unpublished state rt = db.RecordType("TestRT") rt.state = db.State(model="Publish Life-cycle", name="Unpublished") + rt.acl = db.ACL() + rt.acl.grant(role="normal", permission="RETRIEVE:ENTITY") + rt.acl.grant(role="normal", permission="USE:AS_PARENT") rt.insert() - db.Property("TestProperty", datatype=db.TEXT).insert() + prop = db.Property("TestProperty", datatype=db.TEXT) + prop.acl = db.ACL() + prop.acl.grant(role="normal", permission="RETRIEVE:ENTITY") + prop.acl.grant(role="normal", permission="USE:AS_PROPERTY") + prop.insert() + rec = db.Record().add_parent("TestRT") rec.description = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur." rec.add_property("TestProperty", "TestValue") diff --git a/misc/map_test_data.py b/misc/map_test_data.py index addc0e8c7f52cc60d228e079e5b76f5893be74d4..eda56c0ef06fc73b42d062e4fe7536aaf49b8286 100755 --- a/misc/map_test_data.py +++ b/misc/map_test_data.py @@ -18,6 +18,9 @@ datamodel.extend([ "MapObject" ).add_property("longitude", importance=caosdb.OBLIGATORY ).add_property("latitude", importance=caosdb.OBLIGATORY), + caosdb.RecordType( + "PathObject" + ).add_property("MapObject", datatype=caosdb.LIST("MapObject")), ]) datamodel.insert() @@ -25,19 +28,23 @@ datamodel.insert() # test data +testdata = caosdb.Container() +path = caosdb.Record() +path.add_parent("PathObject") +path.add_property("MapObject", datatype=caosdb.LIST("MapObject"), value=[]) +testdata.append(path) +for i in range(100): + loc = caosdb.Record( + "Object-{}".format(i) + ).add_parent("MapObject" + ).add_property("longitude", random.gauss(-42.0, 5) + ).add_property("latitude", random.gauss(77.0, 5)) + testdata.append(loc) + path.get_property("MapObject").value.append(loc) -testdata = caosdb.Container() -for i in range(100): - testdata.append( - caosdb.Record( - "Object-{}".format(i) - ).add_parent("MapObject" - ).add_property("longitude", random.gauss(-42.0, 5) - ).add_property("latitude", random.gauss(77.0, 5)) - ) testdata.insert(); diff --git a/src/core/css/webcaosdb.css b/src/core/css/webcaosdb.css index b76a5fe9ecc9f6d866dc159429ef7401ac3ddd5d..1c8f50be049d308efcac25e41d705b08d96d21e6 100644 --- a/src/core/css/webcaosdb.css +++ b/src/core/css/webcaosdb.css @@ -279,9 +279,17 @@ h5 { left: 0px; } +.caosdb-label-link { + text-decoration: none; +} + +.caosdb-label-id { + margin-right: 0.3em; + display: none; +} + .caosdb-label-name { font-weight: bold; - text-decoration: none; } /* lists of values */ @@ -342,12 +350,10 @@ h5 { border-bottom-right-radius: 3px; } -.coasdb-entity-version-attr, .caosdb-entity-heading-attr { overflow-x: auto; } -.coasdb-entity-version-attr-name, .caosdb-entity-heading-attr-name { color: #6c6c6c; font-size: 90%; @@ -559,10 +565,23 @@ input[type="file"] { min-height: 22px; } +.caosdb-v-property-value-inputs { + max-height: 300px; + overflow: auto; +} + .caosdb-v-property-value-inputs > textarea { width: 100%; } +.caosdb-v-property-value-inputs .caosdb-preview-waiting-notification { + color: #aaa; +} + +.caosdb-v-property-value-inputs .caosdb-preview-waiting-notification:after { + content: "..."; +} + .caosdb-v-property-value-inputs li > textarea { width: calc(100% - 40px); } @@ -612,7 +631,16 @@ input[type="file"] { margin: 0px; } -.caosdb-v-property-value-inputs .caosdb-v-edit-value-list-buttons > button { +.caosdb-v-property-value-inputs div.dropdown.bootstrap-select { + margin-bottom: -1px; + border: 1px solid #aaa; +} + +.caosdb-v-property-value-inputs div.dropdown.bootstrap-select:last-child { + margin-bottom: 0px; +} + +.caosdb-v-property-value-inputs button.caosdb-f-list-item-button { padding: 1px; } @@ -771,3 +799,15 @@ details p { font-size: 0.875rem; color: #5E6762; } + +.caosdb-f-form-wrapper .caosdb-f-form-required-marker { + font-size: 10px; + color: red; + margin-right: 4px; + font-weight: 100; +} + +.caosdb-f-form-elements-footer .caosdb-f-form-required-label { + margin-right: 4px; + font-size: 11px; +} diff --git a/src/core/js/caosdb.js b/src/core/js/caosdb.js index 7c3c4300f971eae16d30748e6787843334708baa..39109d444ff4bc8bbd0f2b2811004967565a3361 100644 --- a/src/core/js/caosdb.js +++ b/src/core/js/caosdb.js @@ -5,8 +5,8 @@ * Copyright (C) 2018-2020 Alexander Schlemmer * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen - * Copyright (C) 2019-2020 IndiScale GmbH (info@indiscale.com) - * Copyright (C) 2019-2020 Timm Fitschen (t.fitschen@indiscale.com) + * Copyright (C) 2019-2022 IndiScale GmbH (info@indiscale.com) + * Copyright (C) 2019-2022 Timm Fitschen (t.fitschen@indiscale.com) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -550,10 +550,14 @@ function getPropertyFromElement(propertyelement, names = undefined) { let dtel = propertyelement.getElementsByClassName("caosdb-property-datatype")[0]; let idel = propertyelement.getElementsByClassName("caosdb-property-id")[0]; let unitel = valel.getElementsByClassName("caosdb-unit")[0]; + const descel = propertyelement.getElementsByClassName("caosdb-property-description")[0]; property.html = propertyelement; // name property.name = getPropertyName(propertyelement); + // description + property.description = descel ? descel.textContent : undefined; + // id if (idel === undefined) { @@ -600,6 +604,7 @@ function getPropertyFromElement(propertyelement, names = undefined) { if (valel && valel.textContent.length > 0) { value_string = valel.textContent; } else if (valel && valel.value && valel.value.length > 0) { + // this is the case when the valel is an input coming from edit_mode. value_string = valel.value; } @@ -623,20 +628,11 @@ function getPropertyFromElement(propertyelement, names = undefined) { if (property.list) { // list datatypes let listel; - if (property.reference) { - // list of referernces - listel = findElementByConditions(valel, x => x.classList.contains("caosdb-f-reference-value"), - x => x.classList.contains("caosdb-preview-container")); - for (var j = 0; j < listel.length; j++) { - property.value.push(getIDfromHREF(listel[j])); - } - } else { - // list of anything but references - listel = findElementByConditions(valel, x => x.classList.contains("list-inline-item"), - x => x.classList.contains("caosdb-preview-container")); - for (var j = 0; j < listel.length; j++) { - property.value.push(listel[j].textContent); - } + // list of anything but references + listel = findElementByConditions(valel, x => x.classList.contains("caosdb-f-property-single-raw-value"), + x => x.classList.contains("caosdb-preview-container")); + for (var j = 0; j < listel.length; j++) { + property.value.push(listel[j].textContent); } } else if (property.reference && valel.getElementsByTagName("a")[0]) { // reference datatypes @@ -647,7 +643,6 @@ function getPropertyFromElement(propertyelement, names = undefined) { } } - return property; } @@ -739,6 +734,16 @@ var _constructXpaths = function (selectors) { * * `getPropertyValues(entities, [["Geo Location", "latitude"], ["Geo Location", "longitude"]])` * + * When the entitieshave normal non-list references to the "Geo Location" the + * result looks like this: + * + * `[[ "50", "-39"], ...]` + * + * When the entities have a LIST of thre Geo Locations the result looks like + * this: + * + * `[[[ "50", "51", "52"], [ "-39", "-38", "-37" ]], ...]`. + * * Use empty strings for selector elements when the property name is irrelevant: * * `getPropertyValues(entities, [["", "latitude"], ["", "longitude"]])` @@ -753,11 +758,14 @@ var _constructXpaths = function (selectors) { * special cases ("name", "description", "unit", etc.) are to be added when * needed. * - * @param {XMLElement[]) entities + * @param {XMLElement[]} entities * @param {String[][]} selectors - * @return {String[][]} A table of the property values for each entity. + * @return {String[][]} A table of the property values for each entity (index + * order is `[row][column]`). Each row is an entity, each column is a value + * (or an array of values, when the entity has list properties). */ var getPropertyValues = function (entities, selectors) { + // @review Florian Spreckelsen 2022-05-06 const entity_iter = entities.evaluate("/Response/Record", entities); const table = []; @@ -767,9 +775,20 @@ var getPropertyValues = function (entities, selectors) { while (current_entity) { const row = []; for (let expr of xpaths) { - const property = entities.evaluate(expr, current_entity).iterateNext(); - if (typeof property != "undefined" && property != null) { - row.push(property.textContent.trim()); + const property_iter = entities.evaluate(expr, current_entity); + var property = property_iter.iterateNext(); + if (typeof property !== "undefined" && property !== null) { + // handle lists and single values + var values = []; + while (property !== null) { + values.push(property.textContent.trim()); + property = property_iter.iterateNext(); + } + if(values.length < 2) { + // wasn't a list + values = values[0]; + } + row.push(values); } else { row.push(undefined) } @@ -888,13 +907,16 @@ function getProperty(element, property_name, case_sensitive = true) { * @param parentElement The element which is to recieve the attributes. * @param parent The object possibly containing an id and or a name. */ -function setNameID(parentElement, parent) { +function _setDescriptionNameID(parentElement, parent) { if (typeof parent.id !== 'undefined' && parent.id !== '') { parentElement.setAttribute("id", parent.id); } if (typeof parent.name !== 'undefined' && parent.name !== '') { parentElement.setAttribute("name", parent.name); } + if (typeof parent.description !== 'undefined' && parent.description !== '') { + parentElement.setAttribute("description", parent.description); + } } /** @@ -906,7 +928,7 @@ function setNameID(parentElement, parent) { */ function appendParent(doc, element, parent) { var parentElement = document.createElementNS(undefined, "Parent"); - setNameID(parentElement, parent); + _setDescriptionNameID(parentElement, parent); element.appendChild(parentElement); } @@ -933,7 +955,7 @@ function appendValueNode(doc, element, name, value) { */ function appendProperty(doc, element, property, append_datatype = false) { var propertyElement = document.createElementNS(undefined, "Property"); - setNameID(propertyElement, property); + _setDescriptionNameID(propertyElement, property); if (append_datatype && typeof property.datatype !== "undefined") { propertyElement.setAttribute("datatype", property.datatype); } @@ -995,19 +1017,16 @@ function createEntityXML(role, name, id, properties, parents, var doc = _createDocument(role); var nelnode = doc.children[0]; - setNameID(nelnode, { + _setDescriptionNameID(nelnode, { name: name, - id: id + id: id, + description: description, }); if (typeof datatype !== 'undefined' && datatype.length > 0) { $(nelnode).attr("datatype", datatype); } - if (typeof description !== 'undefined' && description.length > 0) { - $(nelnode).attr("description", description); - } - if (typeof unit !== 'undefined' && unit.length > 0) { $(nelnode).attr("unit", unit); } diff --git a/src/core/js/edit_mode.js b/src/core/js/edit_mode.js index 87e588aab11a681e49ff4e6596a81f4037628316..8b7e5fabb075a87e1ddf92d5d568abea589e6ddc 100644 --- a/src/core/js/edit_mode.js +++ b/src/core/js/edit_mode.js @@ -726,6 +726,10 @@ var edit_mode = new function () { $(entities).find(".caosdb-entity-actions-panel").show(); } + this._init_select = function (select) { + $(select).selectpicker(); + } + this.make_header_editable = function (entity) { var header = $(entity).find('.caosdb-entity-panel-heading'); var roleElem = $(header).find('.caosdb-f-entity-role'); @@ -772,6 +776,9 @@ var edit_mode = new function () { header.children().remove(); const form = $('<form class="form-horizontal"></form>').append(inputs); header.append(form); + // selectpicker is based on bootstrap-select and must be initializes + // AFTER it had been added to the dom tree. + edit_mode._init_select(form.find(".selectpicker")); edit_mode.add_parent_dropzone(entity); edit_mode.make_datatype_input_logic(form[0]); @@ -887,7 +894,7 @@ var edit_mode = new function () { // generate select input for the atomic_datatype - const select = $('<select name="atomic_datatype" class="form-control"></select>'); + const select = $('<select name="atomic_datatype" class="selectpicker form-control"></select>'); for (const dt of edit_mode._known_atomic_datatypes) { select.append(`<option value="${dt}">${dt}</option>`); } @@ -980,13 +987,29 @@ var edit_mode = new function () { } else if (property.datatype == "INTEGER") { result = "<input type='number' value='" + property.value + "'></input>"; } else if (property.datatype == "BOOLEAN") { - result = $('<select style="width:80%;display:inline;" class="form-control caosdb-list-' + property.datatype + '"><option value=""></option><option value="FALSE">FALSE</option><option value="TRUE">TRUE</option></select>'); - result.val(property.value); - } else if (property.reference || property.datatype == "FILE") { - result = $('<select style="width:80%;display:inline;" class="form-control caosdb-list-' + property.datatype + '" data-resolved="false"><option selected class="caosdb-f-option-default" value="' + property.value + '">' + property.value + '</option></select>'); - if (typeof options !== "undefined") { - edit_mode.fill_reference_drop_down(result[0], options); + result = $(`<select class="selectpicker form-control caosdb-list-${property.datatype}"><option value=""></option><option>FALSE</option><option>TRUE</option></select>`); + if(property.value) { + result.val(property.value); } + } else if (property.reference || property.datatype == "FILE") { + result = $('<div/>'); + var css = { + "min-height": "38px", + "width": "80%", + "display": "inline-block", + }; + result.css(css); + result.append(`<select style="display: none"><option selected>${property.value}</option></select>`); + result.append(createWaitingNotification(property.value)); + + const select = $('<select data-container="body" data-virtual-scroll="100" data-window-padding="15" data-live-search="true" class="selectpicker form-control caosdb-list-' + property.datatype + '" data-resolved="false"><option value=""></option></select>'); + options.then((_options) => { + edit_mode.fill_reference_drop_down(select[0], _options, property.value); + result.empty(); + result.append(select); + edit_mode._init_select(select); + }); + } else { throw ("Unsupported data type: `" + property.datatype + "`. Please issue a feature request."); } @@ -1199,6 +1222,9 @@ var edit_mode = new function () { var editfield = $(element).find(".caosdb-f-property-value"); editfield.children().remove(); editfield.append(inputs); + // selectpicker is based on bootstrap-select and must be initializes + // AFTER it had been added to the dom tree. + edit_mode._init_select(editfield.find(".selectpicker")); edit_mode.change_property_data_type(element, property.datatype); @@ -1301,6 +1327,9 @@ var edit_mode = new function () { var inputs = edit_mode.create_value_inputs(property); editfield.children().remove(); editfield.append(inputs); + // selectpicker is based on bootstrap-select and must be initializes + // AFTER it had been added to the dom tree. + edit_mode._init_select(editfield.find(".selectpicker")); // CHECKBOX `List [ ]` edit_mode.add_toggle_list_checkbox(element, property.list, property.datatype); @@ -1772,22 +1801,14 @@ var edit_mode = new function () { * @param {HTMLElement[]} options - array of option elements, ready for * being appended to a SELECT element. This parameter might as well be * a Promise for such an array. + * @param {String} [current_value] - The value which is to be selected. */ - this.fill_reference_drop_down = async function (drop_down, options) { - var resolved_options = await options; - var old = $(drop_down).find("option.caosdb-f-option-default"); - if (old.length == 0) { - // no option selected, append all - $(drop_down).append($(resolved_options).clone()); - return; - } - var old_value = old.attr("value"); - for (let opt of resolved_options) { - if (opt.value === old_value) { - old.text($(opt).text()); - } else { - $(drop_down).append($(opt).clone()); - } + this.fill_reference_drop_down = function (drop_down, options, current_value) { + drop_down = $(drop_down); + + drop_down.append($(options).clone()); + if (current_value) { + drop_down.val(current_value); } } @@ -1805,13 +1826,15 @@ var edit_mode = new function () { */ this.retrieve_datatype_list = async function (datatype) { var find_entity = ["FILE", "REFERENCE"].includes(datatype) ? "" : `"${datatype}"`; - var entities = datatype !== "FILE" ? await edit_mode.query(`FIND Record ${find_entity}`) : []; - var files = await edit_mode.query(`FIND File ${find_entity}`); + var entities = datatype !== "FILE" ? edit_mode.query(`FIND Record ${find_entity}`, true) : []; + var files = edit_mode.query(`FIND File ${find_entity}`, true); + + await Promise.all([entities, files]) var options = edit_mode - ._create_reference_options(entities) + ._create_reference_options(await entities) .concat(edit_mode - ._create_reference_options(files)); + ._create_reference_options(await files)); return options; @@ -2052,8 +2075,17 @@ var edit_mode = new function () { $(entity).find(".caosdb-f-entity-delete-button").remove(); } - this.query = (str) => { - return query(str); + const query_cache = {}; + + this.query = (str, use_cache) => { + if (use_cache && query_cache[str]) { + return query_cache[str]; + } + const result = query(str); + if (use_cache) { + query_cache[str] = result; + } + return result; } }() diff --git a/src/core/js/ext_autocomplete.js b/src/core/js/ext_autocomplete.js index 2f6fa0dde729907f7248bd05367f26633872f39e..932a9466a83f17594894ad389f6535bcd6caffa2 100644 --- a/src/core/js/ext_autocomplete.js +++ b/src/core/js/ext_autocomplete.js @@ -159,7 +159,7 @@ var ext_autocomplete = new function () { var end = origJQElement[0].value.slice(cursorpos); var result = resultsFromServer.map(x => { var x_quoted = x; - if (x.indexOf(" ") > -1) { + if (ext_autocomplete.CQL_WORDS.indexOf(x) == -1 && x.indexOf(" ") > -1) { if(x.indexOf("\"") > -1) { x_quoted = `'${x}'`; } else { diff --git a/src/core/js/ext_editmode_wysiwyg_text.js b/src/core/js/ext_editmode_wysiwyg_text.js index f784dbd5d998ffeb95b4d594c907ff725441eadd..d5cd88a85084c76fd92c8710d9845bd80f09fb95 100644 --- a/src/core/js/ext_editmode_wysiwyg_text.js +++ b/src/core/js/ext_editmode_wysiwyg_text.js @@ -39,6 +39,8 @@ */ var ext_editmode_wysiwyg_text = function ($, logger, ClassicEditor, edit_mode, getPropertyElements, getPropertyDatatype, getPropertyName) { + var _callOnSave = []; + var insertEditorInProperty = async function (prop) { if (!(getPropertyDatatype(prop) === 'TEXT')) { // Ignore anything that isn't a list property, even LIST<TEXT> @@ -62,7 +64,7 @@ var ext_editmode_wysiwyg_text = function ($, logger, ClassicEditor, edit_mode, g logger.debug('Initialized editor for ' + getPropertyName(prop)); // Manually implement saving the data since edit mode is not // a form to be submitted. - editor.model.document.on("change:data", (e) => { + _callOnSave.push(() => { editor.updateSourceElement(); }); } catch (error) { @@ -70,7 +72,23 @@ var ext_editmode_wysiwyg_text = function ($, logger, ClassicEditor, edit_mode, g } } + const proxySaveMethod = function (original) { + const result = function (entity) { + _callOnSave.forEach(cb => { + cb(); + }); + if (typeof original === "function") { + return original(entity); + } + return undefined; + } + return result; + } + var replaceTextAreas = function (entity) { + // on save, call callbacks + edit_mode.app.onBeforeInsert = proxySaveMethod(edit_mode.app.onBeforeInsert); + edit_mode.app.onBeforeUpdate = proxySaveMethod(edit_mode.app.onBeforeUpdate); const properties = getPropertyElements(entity); for (let prop of properties) { // TODO(fspreck): This will be replaced by a whitelist of properties @@ -107,6 +125,12 @@ var ext_editmode_wysiwyg_text = function ($, logger, ClassicEditor, edit_mode, g logger.debug('Re-rendering ' + getPropertyName(e.target)); ext_editmode_wysiwyg_text.insertEditorInProperty(e.target); }, true); + + // Clear list of saving callbacks when leaving the edit mode (regardless + // of saving or cancelling) + document.body.addEventListener(edit_mode.end_edit.type, (e) => { + this._callOnSave = []; + }, true); }; return { diff --git a/src/core/js/ext_entity_acl.js b/src/core/js/ext_entity_acl.js new file mode 100644 index 0000000000000000000000000000000000000000..ce6d4160afefffa11a346804a565f832a0aca84e --- /dev/null +++ b/src/core/js/ext_entity_acl.js @@ -0,0 +1,104 @@ +/* + * This file is a part of the CaosDB Project. + * + * Copyright (C) 2021 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +"use strict"; + +/** + * Adds button to each entity which links to the entity's ACL. + * + * Please enable via build property: + * + * BUILD_MODULE_EXT_ENTITY_ACL=ENABLED + * + * Set the base uri of the EntityACL view (optional, defaults to + * `${connection.getBasePath()}webinterface/acm/entityacl/`): + * + * BUILD_MODULE_EXT_ENTITY_ACL_URI_ROOT=[scheme://host:port]/what/evs + * + * + * @author Timm Fitschen + */ +var ext_entity_acl = function ($, connection, getEntityVersion, getEntityID, logger) { + + const BUILD_MODULE_EXT_ENTITY_ACL_URI_ROOT = connection.getBasePath() + "webinterface/acm/entityacl/"; + const _buttons_list_class = "caosdb-v-entity-header-buttons-list"; + const _entity_acl_link_class = "caosdb-f-entity-entity_acl-button"; + const _entity_acl_canvas_container = "caosdb-f-entity-entity_acl"; + const _entity_acl_link_container = "caosdb-f-entity-entity_acl-link"; + const _entity_acl_icon = `<i class="bi bi-key"></i>`; + + /** + * Create a link to the Entity ACL. + * + * @param {string} entity_id + * @return {HTMLElement} the newly created link. + */ + var create_entity_acl_link = function (entity_id) { + const button = $(`<a href="${BUILD_MODULE_EXT_ENTITY_ACL_URI_ROOT}${entity_id}" title="Open Entity ACL" class="${_entity_acl_link_class} caosdb-v-entity-entity_acl-button btn">${_entity_acl_icon}</a>`); + return button[0]; + } + + /** + * Add a entity_acl button to a given entity. + * @param {HTMLElement} entity + */ + var add_entity_acl_to_entity = function (entity) { + const entity_id = getEntityID(entity); + + $(entity).find(`.${_buttons_list_class}`).append(create_entity_acl_link(entity_id)); + } + + var remove_entity_acl_link = function (entity) { + $(entity).find(`.${_buttons_list_class} .${_entity_acl_link_class}`).remove(); + } + + var _init = function () { + for (let entity of $(".caosdb-entity-panel")) { + remove_entity_acl_link(entity); + add_entity_acl_to_entity(entity); + } + } + + /** + * Initialize this module and append a QR Code button to all entities panels on the page. + * + * Removes all respective buttons if present before adding a new one. + */ + var init = function () { + _init(); + + // edit-mode-listener + document.body.addEventListener(edit_mode.end_edit.type, _init, true); + }; + + return { + add_entity_acl_to_entity: add_entity_acl_to_entity, + remove_entity_acl_link: remove_entity_acl_link, + create_entity_acl_link: create_entity_acl_link, + init: init + }; + +}($, connection, getEntityVersion, getEntityID, log.getLogger("ext_entity_acl")); + +$(document).ready(function () { + if ("${BUILD_MODULE_EXT_ENTITY_ACL}" == "ENABLED") { + caosdb_modules.register(ext_entity_acl); + } +}); diff --git a/src/core/js/ext_map.js b/src/core/js/ext_map.js index c20cdefc2e9de73a21db81e3a7d5ebacebe73416..39e6d510f9bbdf8e18426dc9b56577a235850a8d 100644 --- a/src/core/js/ext_map.js +++ b/src/core/js/ext_map.js @@ -25,7 +25,7 @@ /** * @module caosdb_map - * @version 0.4 + * @version 0.4.1 * * For displaying a geographical map which shows entities at their associated * geolocation. @@ -34,7 +34,7 @@ * `conf/ext/json/ext_map.json` and comply with the {@link MapConfig} type * which is described below. * - * The current version is 0.4. It is not considered to be stable because + * The current version is 0.4.1. It is not considered to be stable because * implementation of the graticule still is not satisfactory. * * Apart from that the specification of the configuration and the @@ -43,7 +43,7 @@ var caosdb_map = new function () { var logger = log.getLogger("caosdb_map"); - this.version = "0.4"; + this.version = "0.4.1"; this.dependencies = ["log", { "L": ["latlngGraticule", "Proj"] }, "navbar", "caosdb_utils"]; @@ -63,6 +63,9 @@ var caosdb_map = new function () { * integrated query generator. * * @typedef {object} MapConfig + * @property {boolean} [show=false] - show the map immediately when it is + * ready. This configuration option is being stored locally for keeping + * the map shown/hidden accross page reloads. * @property {string} version - the version of the map * @property {string} default_view - the view which is shown when the user * opens the map and has no stored view from prior visits to the map. @@ -336,6 +339,7 @@ var caosdb_map = new function () { */ this._default_config = { "version": this.version, + "show": false, "datamodel": { "lat": "latitude", "lng": "longitude", @@ -372,6 +376,8 @@ var caosdb_map = new function () { * @callback {mapEntityPopupGenerator} * @param {HTMLElement} entity - in HTML representation * @param {DataModelConfig} datamodel + * @param {Number} lat - latitude + * @param {Number} lng - longitude * @return {HTMLElement} a popup element. */ @@ -396,7 +402,7 @@ var caosdb_map = new function () { this._get_with_POV = function (props) { var pov = "" for (let p of props) { - pov = pov + ` WITH ${p} `; + pov = pov + ` WITH "${p}" `; } return pov; } @@ -440,12 +446,12 @@ var caosdb_map = new function () { var pov = undefined; if (typeof ids === "undefined") { pov = (caosdb_map._get_with_POV(props) + - ` WITH ${datamodel.lat} AND ${datamodel.lng}`); + ` WITH ( "${datamodel.lat}" AND "${datamodel.lng}" )`); } else { pov = caosdb_map._get_id_POV(ids); } - return `SELECT parent,${selector}${datamodel.lat},${selector}${datamodel.lng} FROM ENTITY ${recordtype} ${pov} `; + return `SELECT parent,${selector}${datamodel.lat},${selector}${datamodel.lng} FROM ENTITY "${recordtype}" ${pov} `; } @@ -537,12 +543,25 @@ var caosdb_map = new function () { * recordtype) */ this._set_subprops_at_top = function (entities, depth, datamodel) { + // @review Florian Spreckelsen 2022-05-06 var latlong = caosdb_map._get_leaf_prop(entities, depth, datamodel); for (let rec_id in latlong) { + const is_list_lat = Array.isArray(latlong[rec_id][0]); + const is_list_lng = Array.isArray(latlong[rec_id][0]); + var lat, lng; + if (is_list_lat) { + var lat_val = `<Value>${latlong[rec_id][0].join("</Value><Value>")}</Value>`; + lat = `<Property name="${datamodel.lat}" datatype="LIST<lat>">${lat_val}</Property>`; + var lng_val = `<Value>${latlong[rec_id][1].join("</Value><Value>")}</Value>`; + lng = `<Property name="${datamodel.lng}" datatype="LIST<lng>">${lng_val}</Property>`; + } else { + lat = `<Property name="${datamodel.lat}">${latlong[rec_id][0]}</Property>`; + lng = `<Property name="${datamodel.lng}">${latlong[rec_id][1]}</Property>`; + } let tmp_rec = caosdb_map._get_toplvl_rec_with_id(entities, rec_id); - tmp_rec.append(str2xml(`<Property name="${datamodel.lat}">${latlong[rec_id][0]}</Property>`).firstElementChild); - tmp_rec.append(str2xml(`<Property name="${datamodel.lng}">${latlong[rec_id][1]}</Property>`).firstElementChild); + tmp_rec.append(str2xml(lat).firstElementChild); + tmp_rec.append(str2xml(lng).firstElementChild); } } @@ -565,7 +584,7 @@ var caosdb_map = new function () { results = await transformation.transformEntities(entities); } else { results = await caosdb_map.query( - `FIND ENTITY WITH ${datamodel.lat} AND ${datamodel.lng}`); + `FIND ENTITY WITH ( "${datamodel.lat}" AND "${datamodel.lng}" )`); } const container = $('<div>').append(results)[0]; @@ -582,11 +601,12 @@ var caosdb_map = new function () { * @param {HTMLElement} entity - an entity in HTML representation. * @param {DataModelConfig} datamodel - configuration of the properties * used for the coordinates. + * @param {Number} lat - latitude + * @param {Number} lng - longitude * @returns {HTMLElement} a popup element. */ - this._make_map_popup = function (entity, datamodel) { - const lat = getProperty(entity, datamodel.lat); - const lng = getProperty(entity, datamodel.lng); + this._make_map_popup = function (entity, datamodel, lat, lng) { + // @review Florian Spreckelsen 2022-05-06 const role_label = $(entity).find( ".label.caosdb-f-entity-role").first().clone(); const parent_list = caosdb_map.make_parent_labels(entity); @@ -599,8 +619,7 @@ var caosdb_map = new function () { extra_loc_hint = `<div>Location of related ${path[path.length-1]}<div>`; } const loc = $(`<div class="small text-muted">${extra_loc_hint} - Lat: ${dms_lat} Lng: ${dms_lng} - </div>`); + Lat: ${dms_lat} Lng: ${dms_lng}</div>`); const ret = $('<div/>') .append(role_label) .append(parent_list) @@ -843,8 +862,10 @@ var caosdb_map = new function () { * * The map panel is the HTMLElement which contains the map. Is is * hidden or shown when the user clicks on the toggle map button. + * + * @param {boolean} show - show this panel immediately. */ - this.init_map_panel = function () { + this.init_map_panel = function (show) { logger.trace("enter init_map_panel"); // remove old @@ -852,7 +873,9 @@ var caosdb_map = new function () { let panel = this.create_map_panel(); - $(panel).hide(); + if (!show) { + $(panel).hide(); + } $('nav').first().after(panel); logger.trace("leave init_map_panel"); @@ -865,6 +888,7 @@ var caosdb_map = new function () { */ this.toggle_map = function () { logger.trace("enter toggle_map"); + sessionStorage["caosdb_map.show"] = !$(".caosdb-f-map-panel").is(":visible"); $(".caosdb-f-map-panel").toggle(900, () => this ._toggle_cb()); } @@ -872,6 +896,7 @@ var caosdb_map = new function () { this.show_map = function () { logger.trace("enter show_map"); + sessionStorage["caosdb_map.show"] = "true"; $(".caosdb-f-map-panel").show(900, () => this ._toggle_cb()); } @@ -983,14 +1008,18 @@ var caosdb_map = new function () { return; } this.config = config; + var show_map = config.show; + if (sessionStorage["caosdb_map.show"]) { + show_map = JSON.parse(sessionStorage["caosdb_map.show"]); + } this.init_select_handler(); this.init_view_change_handler(); - panel = this.init_map_panel(); + panel = this.init_map_panel(show_map); // TODO split in smaller pieces and move callback to separate function this.change_map_view = (view) => { if (this._map) { - this._map._container.remove(); + this._map._container.remove(); this._map.remove(); } @@ -1081,8 +1110,8 @@ var caosdb_map = new function () { // indicate that the map is ready: map button is present and // map is hidden or shown but initialized in either case. - this._map.whenReady(()=>{ - document.body.dispatchEvent(caosdb_map.map_ready); + this._map.whenReady(() => { + document.body.dispatchEvent(caosdb_map.map_ready); }); } catch (err) { logger.error("Could not initialize the map.", @@ -1365,6 +1394,11 @@ var caosdb_map = new function () { ); result.views = this._unconfigured_views; } + try { + this.check_config(result); + } catch (error) { + logger.error(error.message); + } logger.trace("leave load_config", result); return result; } @@ -1499,6 +1533,34 @@ var caosdb_map = new function () { */ this.query = query; + /* + * Create a single map marker for the given entity. + * + * @param {HTMLElement} map_entity - the entity. + * @param {DataModelConfig} datamodel - specifies the properties for + * coordinates. + * @param {number} lat - latitude + * @param {number} lng - longitude + * @param {DivIcon_options} icon_options + * @param {number} zIndexOffset - zIndexOffset of the marker. + * @param {mapEntityPopupGenerator} [make_popup] - creates popup content. + * @returns {L.Marker} the marker for the map. + */ + var _create_single_entity_marker = function (map_entity, datamodel, lat, + lng, icon_options, zIndexOffset, make_popup) { + // @review Florian Spreckelsen 2022-05-06 + var marker = L.marker([lat, lng], { + icon: L.divIcon(icon_options) + }); + + if (zIndexOffset) { + marker.setZIndexOffset(zIndexOffset); + } + if (make_popup) { + marker.bindPopup(make_popup(map_entity, datamodel, lat, lng)); + } + return marker; + } /** * Create markers for the map for an array of entities. @@ -1512,32 +1574,82 @@ var caosdb_map = new function () { * @param {DivIcon_options} icon_options * @returns {L.Marker[]} an array of markers for the map. */ - this.create_entity_markers = function (entities, datamodel, make_popup, zIndexOffset, icon_options) { - logger.trace("enter create_entity_markers", entities, datamodel, zIndexOffset, icon_options); + this.create_entity_markers = function (entities, datamodel, make_popup, + zIndexOffset, icon_options) { + // @review Florian Spreckelsen 2022-05-06 + logger.trace("enter create_entity_markers", entities, datamodel, + zIndexOffset, icon_options); var ret = [] for (const map_entity of entities) { - var lat = getProperty(map_entity, datamodel.lat); - var lng = getProperty(map_entity, datamodel.lng); + var lat_vals = getProperty(map_entity, datamodel.lat); + var lng_vals = getProperty(map_entity, datamodel.lng); - if (lat && lng) { - logger.debug(`create entity marker at [${lat}, ${lng}] for`, + if (!lng_vals || !lng_vals) { + logger.debug("undefined latitude or longitude", + map_entity, lat_vals, lng_vals); + continue; + } + + // we need lat_vals and lng_lavs to be arrays so we make them + // be one + var is_list_lat = true; + if (!Array.isArray(lat_vals)) { + lat_vals = [lat_vals]; + is_list_lat = false; + } + var is_list_lng = true; + if (!Array.isArray(lng_vals)) { + lng_vals = [lng_vals]; + is_list_lng = false; + } + + // both array's length must match + if (is_list_lng !== is_list_lat || + (is_list_lat && is_list_lng && + lat_vals.length !== lng_vals.length)) { + logger.error("Cannot show this entity on the map. " + + "Its lat/long properties have different lenghts: ", map_entity); - var marker = L.marker([lat, lng], { - icon: L.divIcon(icon_options) - }); + continue; + } - if (zIndexOffset) { - marker.setZIndexOffset(zIndexOffset); - } - if (make_popup) { - marker.bindPopup(make_popup(map_entity, datamodel)); - } + + // zip both arrays + // [lat1, lat2, ... latN] + // [lng1, lng2, ... lngN] + // into one + // [[lat1,lng1],[lat2,lng2],... [latN,lngN]] + var latlngs = lat_vals.map(function (e, i) { + return [e, lng_vals[i]]; + }); + logger.debug(`create point marker(s) at ${latlngs} for`, + map_entity); + for (let latlng of latlngs) { + var marker = _create_single_entity_marker(map_entity, + datamodel, latlng[0], latlng[1], + icon_options, zIndexOffset, make_popup); ret.push(marker); - } else { - logger.debug("undefined latitude or longitude", - map_entity, lat, lng); } + + /* Code for showing a PATH on the map. + * Maybe we re-use it later + * + logger.debug(`create path line at ${latlngs} for`, + map_entity); + + var opts = {color:'red', smoothFactor: 10.0, weight: 1.5, opacity: 0.5}; + var opts_2 = {color:'green', smoothFactor: 10.0, weight: 3, opacity: 0.5}; + var path = L.polyline(latlngs, opts); + if (make_popup) { + path.bindPopup(make_popup(map_entity, datamodel, lat, lng)); + } + path.on("mouseover",()=>path.setStyle(opts_2)); + path.on("mouseout",()=>path.setStyle(opts)); + ret.push(path); + * + * + */ } return ret; } diff --git a/src/core/js/ext_references.js b/src/core/js/ext_references.js index 0853d37db4f34e012666c8fd5b438afadc00528d..cee574e30c305f719dd041cc05913fa704d2e6d4 100644 --- a/src/core/js/ext_references.js +++ b/src/core/js/ext_references.js @@ -311,8 +311,8 @@ var resolve_references = new function () { * resolved as a string and returns a `reference_info` object with * the resolved custom reference as a `text` property. * - * See caosdb-webui/src/ext/js/person_reference_resolver.js for an - * example. + * See caosdb-webui/src/core/js/reference_resolver/caosdb_default_person.js + * for an example. * * TODO refactor to be configurable. @async @param {string} id - the id of * the entity which is to be resolved. @return {reference_info} diff --git a/src/core/js/form_elements.js b/src/core/js/form_elements.js index 193235a2f8a799c07ccc893742d5df9a7d0fa7d1..6815acd791213c6b239a693c3c64667965c369ed 100644 --- a/src/core/js/form_elements.js +++ b/src/core/js/form_elements.js @@ -1232,7 +1232,7 @@ var form_elements = new function () { .css({ "margin": "20px", }).append(this.make_required_marker()) - .append('<span style="margin-right: 4px; font-size: 11px">required field</span>')[0]; + .append('<span class="caosdb-f-form-required-label">required field</span>')[0]; } this.make_error_message = function (message) { @@ -1502,14 +1502,7 @@ var form_elements = new function () { * @returns {HTMLElement} span element. */ this.make_required_marker = function () { - // TODO create class and move to css file - return $('<span>*</span>') - .css({ - "font-size": "10px", - "color": "red", - "margin-right": "4px", - "font-weight": "100", - })[0]; + return $('<span class="caosdb-f-form-required-marker">*</span>')[0]; } diff --git a/src/ext/js/person_reference_resover.js b/src/core/js/reference_resolver/caosdb_default_person.js similarity index 59% rename from src/ext/js/person_reference_resover.js rename to src/core/js/reference_resolver/caosdb_default_person.js index 393557354904787f04472585bca0883d64200d86..dc750865c9ebf7aa01385d8f471d8d051c335fae 100644 --- a/src/ext/js/person_reference_resover.js +++ b/src/core/js/reference_resolver/caosdb_default_person.js @@ -22,17 +22,18 @@ */ /** - * @module person_reference + * @module caosdb_default_person_reference + * @version 0.1 * - * Replace the reference to a Person Record by the values of that + * @description Replace the reference to a Person Record by the values of that * Record's firstname and lastname properties. * * TODO: Make name(s) of person RecordType(s) and names of firstname * and lastname properties configurable. */ -var person_reference = new function () { +var caosdb_default_person_reference = new function () { - var logger = log.getLogger("person_reference"); + var logger = log.getLogger("caosdb_default_person_reference"); const lastname_prop_name = "lastname" const firstname_prop_name = "firstname" @@ -42,24 +43,26 @@ var person_reference = new function () { * Return the name of a person as firstname + lastname */ this.get_person_str = function (el) { - var valpr = getProperties(el); - if (valpr == undefined) { - return; - } - return valpr.filter(valprel => - valprel.name.toLowerCase().trim() == - firstname_prop_name.toLowerCase())[0].value + - " " + - valpr.filter(valprel => valprel.name.toLowerCase().trim() == - lastname_prop_name.toLowerCase())[0].value; + var valpr = getProperties(el); + if (valpr == undefined) { + return; + } + return valpr.filter(valprel => + valprel.name.toLowerCase().trim() == + firstname_prop_name.toLowerCase())[0].value + + " " + + valpr.filter(valprel => valprel.name.toLowerCase().trim() == + lastname_prop_name.toLowerCase())[0].value; } this.resolve = async function (id) { - const entity = (await resolve_references.retrieve(id))[0]; + const entity = (await resolve_references.retrieve(id))[0]; - if (resolve_references.is_child(entity, person_rt_name)) { - return {"text": person_reference.get_person_str(entity)}; - } + if (resolve_references.is_child(entity, person_rt_name)) { + return { + "text": caosdb_default_person_reference.get_person_str(entity) + }; + } } } diff --git a/src/core/js/webcaosdb.js b/src/core/js/webcaosdb.js index 03cc7bfbb399e898a88cb99dc0a05cc90289f96a..5d8ea6599316c1f202bf49a611df748cf29d014b 100644 --- a/src/core/js/webcaosdb.js +++ b/src/core/js/webcaosdb.js @@ -4,8 +4,10 @@ * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen - * Copyright (C) 2019 IndiScale GmbH (info@indiscale.com) - * Copyright (C) 2019 Timm Fitschen (t.fitschen@indiscale.com) + * Copyright (C) 2022 IndiScale GmbH <info@indiscale.com> + * Copyright (C) 2019 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2022 Florian Spreckelsen <f.spreckelsen@indiscale.com> + * Copyright (C) 2022 Daniel Hornung <d.hornung@indiscale.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -990,6 +992,11 @@ var version_history = new function () { const logger = log.getLogger("version_history"); this.logger = logger; + this._has_version_fragment = function () { + const fragment = window.location.hash.substr(1); + return fragment === 'version_history'; + } + this._get = connection.get; /** * Retrieve the version history of an entity and return a table with the @@ -1161,6 +1168,18 @@ var version_history = new function () { this.init_load_history_buttons(); this.init_export_history_buttons(); this.init_restore_version_buttons(); + + // check for the version_history fragment and open the modal if present. + if (this._has_version_fragment()) { + const first_entity = $(".caosdb-entity-panel")[0]; + if (first_entity && hasEntityPermission(first_entity, "RETRIEVE:HISTORY")) { + logger.debug("Showing full version modal for first entity"); + const version_button = $(first_entity).find(".caosdb-f-entity-version-button"); + version_button.click(); + const full_version_history_button = $(first_entity).find(".caosdb-f-entity-version-load-history-btn"); + full_version_history_button.click(); + } + } } } @@ -1391,7 +1410,11 @@ var queryForm = new function () { return; } if (!(value.startsWith("FIND") || value.startsWith("COUNT") || value.startsWith("SELECT"))) { - queryField.value = "FIND ENTITY WHICH HAS A PROPERTY LIKE '*" + queryField.value + "*'"; + // split words in query field at space and create query fragments + var words = queryField.value.split(" ").map(word => `A PROPERTY LIKE '*${word}*'`); + var query_string = "FIND ENTITY WHICH HAS "; + // send a query that combines all fragments with an AND + queryField.value = query_string + words.join(" AND "); } setter(queryField.value); @@ -1942,6 +1965,16 @@ function initOnDocumentReady() { if ("${BUILD_MODULE_USER_MANAGEMENT}" == "ENABLED") { caosdb_modules.register(user_management); } + + if ("${BUILD_MODULE_SHOW_ID_IN_LABEL}" == "ENABLED") { + // Remove the "display: none;" rule from the caosdb-label-id class + [...document.styleSheets] + .map(s => { + return [...s.cssRules].find(x => x.selectorText == '.caosdb-label-id') + }) + .filter(Boolean) + .forEach(rule => rule.style.removeProperty("display")); + } } @@ -2000,4 +2033,4 @@ class _CaosDBModules { var caosdb_modules = new _CaosDBModules() -$(document).ready(initOnDocumentReady); \ No newline at end of file +$(document).ready(initOnDocumentReady); diff --git a/src/core/xsl/entity.xsl b/src/core/xsl/entity.xsl index d8ba00a9810e9861bfba741055ea113c7f2b88bc..82de9e416c2e28f21cd3f386cc6e04419284dc5f 100644 --- a/src/core/xsl/entity.xsl +++ b/src/core/xsl/entity.xsl @@ -5,6 +5,10 @@ * * Copyright (C) 2018 Research Group Biomedical Physics, * Max-Planck-Institute for Dynamics and Self-Organization Göttingen + * Copyright (C) 2022 Indiscale GmbH <info@indiscale.com> + * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2022 Florian Spreckelsen <f.spreckelsen@indiscale.com> + * Copyright (C) 2022 Daniel Hornung <d.hornung@indiscale.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -146,11 +150,14 @@ </xsl:for-each> </xsl:if> </span> - <a class="caosdb-label-name" title="Open this Entity separately."> + <a class="caosdb-label-link" title="Open this Entity separately."> <xsl:attribute name="href"> <xsl:value-of select="concat($entitypath, @id)"/> </xsl:attribute> - <xsl:value-of select="@name"/> + <span class="caosdb-label-id"><xsl:value-of select="@id"/></span> + <span class="caosdb-label-name"> + <xsl:value-of select="@name"/> + </span> </a> <div class="caosdb-v-entity-header-buttons-list ms-auto"> <xsl:apply-templates mode="entity-heading-attributes-state" select="State"> @@ -266,7 +273,7 @@ </xsl:attribute> <hr class="caosdb-subproperty-divider"/> <dl class="row caosdb-v-entity-property-attributes"> - <xsl:apply-templates mode="property-attributes" select="@description"/> + <xsl:apply-templates mode="property-attributes-desc" select="@description"/> <xsl:apply-templates mode="property-attributes-id" select="@id"/> <xsl:apply-templates mode="property-attributes-type" select="@datatype"/> <xsl:apply-templates mode="property-attributes" select="@*[not(contains('+cuid+id+name+description+datatype+',concat('+',name(),'+')))]"/> @@ -470,6 +477,12 @@ <xsl:value-of select="."/> </dd> </xsl:template> + <xsl:template match="@description" mode="property-attributes-desc"> + <dt class="col-6 col-md-4 mb-0">description</dt> + <dd class="col-6 col-md-8 mb-0 caosdb-property-description"> + <xsl:value-of select="."/> + </dd> + </xsl:template> <xsl:template match="@id" mode="property-attributes-id"> <dt class="col-6 col-md-4 mb-0">id</dt> <dd class="col-6 col-md-8 mb-0"> diff --git a/src/core/xsl/navbar.xsl b/src/core/xsl/navbar.xsl index 529945d7c37a95a0687473dce34f0269e2942c92..8c53b3bc09dbd9c5f9dd120c7b3b9fb3e483b99f 100644 --- a/src/core/xsl/navbar.xsl +++ b/src/core/xsl/navbar.xsl @@ -48,10 +48,13 @@ <xsl:with-param name="username"><xsl:value-of select="/Response/@username"/></xsl:with-param> </xsl:call-template> </xsl:if> - <nav class="navbar navbar-expand-lg navbar-light bg-light sticky-top mb-2 flex-column"> + <nav class="navbar navbar-expand-lg navbar-light bg-light sticky-top mb-2 flex-column" id="caosdb-navbar-full"> <noscript>Please enable JavaScript!</noscript> <div class="container-fluid"> - <a class="navbar-brand" href="/"> + <a class="navbar-brand"> + <xsl:attribute name="href"> + <xsl:value-of select="$basepath"/> + </xsl:attribute> <xsl:element name="img"> <xsl:if test="'${BUILD_NAVBAR_BRAND_NAME}' != ''"> <xsl:attribute name="class">caosdb-logo</xsl:attribute> diff --git a/src/doc/conf.py b/src/doc/conf.py index 8e627dd0c26f9d760c549abfab4cbc824baa9912..e01e6786b8d890b6d02fc39bd1444ba2c52368d0 100644 --- a/src/doc/conf.py +++ b/src/doc/conf.py @@ -22,13 +22,13 @@ import sphinx_rtd_theme project = 'caosdb-webui' -copyright = '2020, IndiScale GmbH' +copyright = '2022, IndiScale GmbH' author = 'Daniel Hornung' # The short X.Y version -version = '0.X.Y' +version = '0.9.0' # The full version, including alpha/beta/rc tags -release = '0.x.y-beta-rc2' +release = '0.9.0-SNAPSHOT' # -- General configuration --------------------------------------------------- diff --git a/src/doc/extension/references.rst b/src/doc/extension/references.rst index 63c551612e5e9d807846595b6c5e458bc5096615..c41d907de57b658194e062abcd734b41ef88ab9b 100644 --- a/src/doc/extension/references.rst +++ b/src/doc/extension/references.rst @@ -23,16 +23,15 @@ the basic structure of the module should look like // Has to be called ``resolve`` and has to take exactly one // string parameter: the id of the referenced entity. this.resolve = async function (id) { - /* - * find the string that the reference should be resolved to, - * e.g., from the value of the entity's properties. - */ - return {"text": new_reference_text} + /* + * find the string that the reference should be resolved to, + * e.g., from the value of the entity's properties. + */ + return {"text": new_reference_text} } } An example is located in -``caosdb-webui/src/ext/js/person_reference_resolver.js``. It resolves -any reference to a ``Person`` Record to the value of its ``firstname`` -and ``lastname`` properties separated by a space and is active by -default. +``caosdb-webui/src/core/js/reference_resolver/caosdb_default_person.js``. It +resolves any reference to a ``Person`` Record to the value of its ``firstname`` +and ``lastname`` properties separated by a space and is active by default. diff --git a/test/core/js/modules/caosdb.js.js b/test/core/js/modules/caosdb.js.js index 8df5e2f9c2b933cd7b678286295961f2c73d7113..b9925f28aa8eee3a828b17ea2f0dcd6b050f2711 100644 --- a/test/core/js/modules/caosdb.js.js +++ b/test/core/js/modules/caosdb.js.js @@ -19,7 +19,15 @@ QUnit.module("caosdb.js", { }, before: function(assert) { - var done = assert.async(3); + var done = assert.async(4); + + // load entity.xsl + var qunit_obj = this; + _retrieveEntityXSL().then(function(xsl) { + qunit_obj.entityXSL = xsl + done(); + }); + this.setTestDocument("x", done, ` <Response> <Record name="nameofrecord"> @@ -82,7 +90,7 @@ QUnit.module("caosdb.js", { </Record> </Response>`); - + // Test document for unset references this.setTestDocument("unsetReferencesTest", done, ` <Response> @@ -483,59 +491,37 @@ QUnit.test("_constructXpaths", function (assert) { QUnit.test("getPropertyValues", function (assert) { const test_response = str2xml(` -<Response srid="851d063d-121b-4b67-98d4-6e5ad4d31e72" timestamp="1606214042274" baseuri="https://localhost:10443" count="8"> - <Query string="select Campaign.responsible.firstname from icecore" results="8"> - <ParseTree>(cq select (prop_sel (prop_subsel (selector_txt C a m p a i g n) . (prop_subsel (selector_txt r e s p o n s i b l e) . (prop_subsel (selector_txt f i r s t n a m e ))))) from (entity icecore) <EOF>)</ParseTree> - <Role/> - <Entity>icecore</Entity> - <Selection> - <Selector name="Campaign.responsible.firstname"/> - </Selection> - </Query> +<Response> <Record id="6525" name="Test_IceCore_1"> - <Permissions/> <Property datatype="Campaign" id="6430" name="Campaign"> <Record id="6516" name="Test-2020_Camp1"> - <Permissions/> <Property datatype="REFERENCE" id="168" name="responsible"> <Record id="6515" name="Test_Scientist"> - <Permissions/> <Property datatype="DOUBLE" id="151" name="latitude" importance="FIX"> 1.34 - <Permissions/> </Property> <Property datatype="DOUBLE" id="151" name="longitude" importance="FIX"> 2 - <Permissions/> </Property> </Record> - <Permissions/> </Property> </Record> - <Permissions/> </Property> </Record> <Record id="6526" name="Test_IceCore_2"> - <Permissions/> <Property datatype="Campaign" id="6430" name="Campaign"> <Record id="6516" name="Test-2020_Camp1"> - <Permissions/> <Property datatype="REFERENCE" id="168" name="responsible"> <Record id="6515" name="Test_Scientist"> - <Permissions/> <Property datatype="DOUBLE" id="151" name="latitude" importance="FIX"> 3 - <Permissions/> </Property> <Property datatype="DOUBLE" id="151" name="longitude" importance="FIX"> 4.8345 - <Permissions/> </Property> </Record> - <Permissions/> </Property> </Record> - <Permissions/> </Property> </Record> </Response>`); @@ -545,6 +531,57 @@ QUnit.test("getPropertyValues", function (assert) { [["6525" ,"1.34", "2"], ["6526", "3", "4.8345"]]); }); +QUnit.test("getPropertyValues - with list of references", function (assert) { + const test_response = str2xml(` +<Response> + <Record id="7393"> + <Version id="7f04ebc3a09d43f8711371a1d62905e5fc6af80f" head="true" /> + <Parent id="7392" name="PathObject" /> + <Property datatype="LIST<MapObject>" id="7391" name="MapObject"> + <Value> + <Record id="7394" name="Object-0"> + <Version id="4c3b4a7ef4abc4d3b6045968f3b5f028d82baab2" head="true" /> + <Property datatype="DOUBLE" id="7389" name="longitude" importance="FIX" unit="°"> + -44.840238182501864 + </Property> + <Property datatype="DOUBLE" id="7390" name="latitude" importance="FIX" unit="°"> + 83.98152416509532 + </Property> + </Record> + </Value> + <Value> + <Record id="7395" name="Object-1"> + <Version id="42fbe0c9be68c356f81f590cddbdd3d5fc17cba4" head="true" /> + <Property datatype="DOUBLE" id="7389" name="longitude" importance="FIX" unit="°"> + -35.60247552143245 + </Property> + <Property datatype="DOUBLE" id="7390" name="latitude" importance="FIX" unit="°"> + 73.86388403927366 + </Property> + </Record> + </Value> + <Value> + <Record id="7396" name="Object-2"> + <Version id="45b71028261061e94ae198eaaa66af0612004173" head="true" /> + <Property datatype="DOUBLE" id="7389" name="longitude" importance="FIX" unit="°"> + -42.429495631197724 + </Property> + <Property datatype="DOUBLE" id="7390" name="latitude" importance="FIX" unit="°"> + 74.95382063506622 + </Property> + </Record> + </Value> + </Property> + </Record> +</Response>`); + + assert.propEqual( + getPropertyValues(test_response, [["id"], ["", "latitude"],["", + "longitude"]]), [["7393", ["83.98152416509532", "73.86388403927366", + "74.95382063506622"], ["-44.840238182501864", "-35.60247552143245", + "-42.429495631197724"]]]); +}); + // Test for bug 103 // If role is File when creating XML for entities, checksum, path and size must be given. QUnit.test("unset_file_attributes", function(assert) { @@ -566,3 +603,61 @@ QUnit.test("unset_file_attributes", function(assert) { undefined); assert.equal(xml2str(res3), "<File id=\"103\" name=\"test\" path=\"testfile.txt\" checksum=\"blablabla\" size=\"0\"/>"); }); + +QUnit.test("getPropertyFromElement", async function(assert) { + var data = await $.ajax({ + cache: true, + dataType: 'xml', + url: "xml/test_case_list_of_myrecordtype.xml", + }); + console.log(this.entityXSL); + var xsl = injectTemplate(this.entityXSL, '<xsl:template match="/"><ul><xsl:apply-templates select="Property" mode="entity-body"/></ul></xsl:template>'); + var params = { + entitypath: "/entitypath/" + }; + var ret = xslt(data, xsl, params); + assert.ok(ret); + assert.propEqual(getPropertyFromElement(ret.firstElementChild), { + "datatype": "LIST<MyRecordType>", + "description": undefined, + "html": {}, + "id": "149315", + "list": true, + "listDatatype": "MyRecordType", + "name": "MyRecordType", + "reference": true, + "unit": undefined, + "value": [ + "167510", + "", + "167546", + "167574", + "167625", + "167515", + "167441", + "167596", + "167249", + "167632", + "167593", + "167321", + "167536", + "167389", + "167612", + "167585", + "167228", + "167211", + "167414", + "167282", + "167409", + "167637", + "167487", + "167328", + "167572", + "167245", + "167615", + "167301", + "167466" + ] + }); + +}); diff --git a/test/core/js/modules/edit_mode.js.js b/test/core/js/modules/edit_mode.js.js index ae1a51d837348ba0ba9c31f48a28a69ef2c9ad7b..17b32023627cc557e204e4cab3300f7092d78fad 100644 --- a/test/core/js/modules/edit_mode.js.js +++ b/test/core/js/modules/edit_mode.js.js @@ -614,9 +614,20 @@ QUnit.test("createElementForProperty", async function (assert) { value: data[dt], } - var html = $(edit_mode.createElementForProperty(prop)); + var options = async () => { + return [$(`<option>${data[dt]}</option>`)[0]]; + } + var html = $(edit_mode.createElementForProperty(prop, options())); + + await sleep(100); + + if($(html).find("select").val()) { + assert.equal($(html).find("select").val(), data[dt], `${dt} input has correct value`); + + } else { + assert.equal($(html).val(), data[dt], `${dt} input has correct value`); + } - assert.equal($(html).val(), data[dt], `${dt} input has correct value`); } }); @@ -810,6 +821,8 @@ QUnit.test("_toggle_list_property", async function (assert) { var val = $(single_input).val(); if (dt == "DATETIME") { val = $(single_input).find("[type='date']").val() + "T" + $(single_input).find("[type='time']").val(); + } else if (["Person", "FILE", "REFERENCE"].indexOf(dt) > -1) { + val = $(single_input).find("select").val(); } assert.equal(val, data[`LIST<${dt}>`], `single ${dt} input has correct value`); } @@ -846,49 +859,49 @@ QUnit.test("Bug #95", async function (assert) { query_done = assert.async(1); // only called with file await edit_mode.retrieve_datatype_list("FILE"); - // old option not deleted when options are empty - var resolve_function; - var empty_options = new Promise(function (res, err) { - resolve_function = res; - }); + var options = async () => { await sleep(100); return [$("<option>1234</option>")[0]]; }; var property = $("<div/>") .append(edit_mode.createElementForProperty({ reference: true, datatype: "REFERENCE", list: false, value: "1234" - }, empty_options)); + }, options())); + assert.equal($(property).find("select").val(), "1234", "old value before"); assert.equal($(property).find("option").length, 1, "one option before"); assert.equal($(property).find(":selected").text(), "1234", "old text before"); - // mimic retrieve_datatype_list with empty result - resolve_function([]); + await sleep(200); assert.equal($(property).find("select").val(), "1234", "old value after"); - assert.equal($(property).find("option").length, 1, "one option after"); + assert.equal($(property).find("option").length, 2, "two options after (one empty)"); assert.equal($(property).find(":selected").text(), "1234", "old text after"); // now an integration test. A list of options is passed to - var options = edit_mode._create_reference_options(await transformation - .transformEntities(str2xml(` - <Response> - <Record name="RName1" id="RID1"/> - <Record name="RName2" id="RID2"/> - <Record name="RName1234" id="1234"/> - </Response> - `))); + options = async () => { + return edit_mode._create_reference_options(await transformation + .transformEntities(str2xml(` + <Response> + <Record name="RName1" id="RID1"/> + <Record name="RName2" id="RID2"/> + <Record name="RName1234" id="1234"/> + </Response> + `)) + ); + } - assert.equal(options.length, 3, "3 entities returned"); var fill_method_done = assert.async(); var proxied = edit_mode.fill_reference_drop_down; - edit_mode.fill_reference_drop_down = async function (arg1, arg2) { - await proxied(arg1, arg2); + edit_mode.fill_reference_drop_down = function (arg1, arg2, arg3) { + proxied(arg1, arg2, arg3); + + assert.equal($(arg1).val(), "1234", "still old value after"); + assert.equal($(arg1).find("option").length, 4, "4 options after"); + assert.equal($(arg1).find(":selected").text(), "Name: RName1234, CaosDB ID: 1234", "new text after"); - assert.equal($(property).find("select").val(), "1234", "still old value after"); - assert.equal($(property).find("option").length, 3, "3 options after"); - assert.equal($(property).find(":selected").text(), "Name: RName1234, CaosDB ID: 1234", "new text after"); + edit_mode.fill_reference_drop_down = proxied; fill_method_done(); } @@ -898,10 +911,7 @@ QUnit.test("Bug #95", async function (assert) { datatype: "REFERENCE", list: false, value: "1234" - }, options)); - - - edit_mode.fill_reference_drop_down = proxied; + }, options())); }); @@ -918,12 +928,9 @@ QUnit.test("fill_reference_drop_down", async function (assert) { assert.equal(options.length, 3, "3 entities returned"); assert.ok($(options).is("option"), "options contains options"); - var select = $('<select><option class="caosdb-f-option-default" selected value="1234">1234</option></select>'); - assert.equal(select.find("option").length, 1, "one option before"); - assert.equal(select.val(), "1234", "old value before"); - assert.equal(select.find(":selected").text(), "1234", "oldtext before"); - await edit_mode.fill_reference_drop_down(select[0], options); + var select = $('<select></select>'); + edit_mode.fill_reference_drop_down(select[0], options, "1234"); assert.equal(select.find("option").length, 3, "3 options after"); assert.equal(select.val(), "1234", "old value after"); diff --git a/test/core/js/modules/entity.xsl.js b/test/core/js/modules/entity.xsl.js index e5ff1e8700b8349cddbdda0a0b52ffef47e4e75f..04ec35a6fc14cbad81c731908e28f788999c3563 100644 --- a/test/core/js/modules/entity.xsl.js +++ b/test/core/js/modules/entity.xsl.js @@ -128,12 +128,13 @@ QUnit.test("Entities have a caosdb-annotation-section", function(assert) { QUnit.test("LIST Property", function(assert) { var done = assert.async(); var entityXSL = this.entityXSL; - assert.expect(2); + assert.expect(4); $.ajax({ cache: true, dataType: 'xml', url: "xml/test_case_list_of_myrecordtype.xml", }).done(function(data, textStatus, jdXHR) { + console.log(entityXSL); var xsl = injectTemplate(entityXSL, '<xsl:template match="/"><xsl:apply-templates select="Property" mode="property-value"/></xsl:template>'); var params = { entitypath: "/entitypath/" @@ -141,6 +142,8 @@ QUnit.test("LIST Property", function(assert) { var ret = xslt(data, xsl, params); assert.ok(ret); assert.equal(ret.firstChild.className, "caosdb-value-list", "property value contains a list.") + assert.equal($(ret.firstChild).find(".caosdb-f-property-single-raw-value").length, 29, "29 values in the list"); + assert.equal($(ret.firstChild).find(".caosdb-f-reference-value").length, 28, "28 reference values in the list"); }).always(function() { done(); }); @@ -292,7 +295,7 @@ QUnit.test("version full history", function (assert) { }); QUnit.test("Transforming abstract properties", function (assert) { - var xmlstr = `<Property id="3842" name="reftotestrt" datatype="TestRT"> + var xmlstr = `<Property id="3842" description="bla" name="reftotestrt" datatype="TestRT"> <Version id="04ad505da057603a9177a1fcf6c9efd5f3690fe4" date="2020-11-23T10:38:02.936+0100" /> </Property>`; var xml = str2xml(xmlstr); @@ -302,6 +305,7 @@ QUnit.test("Transforming abstract properties", function (assert) { "datatype": "TestRT", "html": {}, "id": "3842", + "description": "bla", "list": false, "name": "reftotestrt", "reference": true, diff --git a/test/core/js/modules/ext_autocomplete.js.js b/test/core/js/modules/ext_autocomplete.js.js index 96cab766fb848b74b04677f9b3312b574b9a3844..aaefd228705e12a0c47bf47f1a4e1ce7936d58f6 100644 --- a/test/core/js/modules/ext_autocomplete.js.js +++ b/test/core/js/modules/ext_autocomplete.js.js @@ -88,6 +88,26 @@ QUnit.test("searchPost", async function(assert) { assert.propEqual(result, expected); }); +QUnit.test("searchPost webui#170", async function(assert) { + // https://gitlab.com/caosdb/caosdb-webui/-/issues/170 + // Autocompletion for "IS REFERENCED BY" leads to query syntax error + const resultsFromServer = ["REFERENCED BY"]; + const origJQElement = [{ + selectionEnd: 24, + value: "FIND Event WHICH IS REFE", + }]; + + const expected = [ + { + "html": "REFERENCED BY", + "text": "FIND Event WHICH IS REFERENCED BY" + }, + ]; + + const result = ext_autocomplete.searchPost(resultsFromServer, origJQElement); + assert.propEqual(result, expected); +}); + QUnit.test("class", function(assert) { assert.ok(ext_autocomplete.switch_on_completion , "toggle available"); assert.ok(ext_autocomplete.switch_on_completion() , "toggle runs"); diff --git a/test/core/js/modules/ext_map.js.js b/test/core/js/modules/ext_map.js.js index 9b6b01022d8106153d50eaa906a0ce33803a8dc3..3e55506b9abc8742ae59bf958febd9f63f968ba9 100644 --- a/test/core/js/modules/ext_map.js.js +++ b/test/core/js/modules/ext_map.js.js @@ -43,6 +43,31 @@ QUnit.module("ext_map.js", { lng + `</div> <div class="caosdb-f-property-value">5.23</div> </div> +</div>`; + // This one has lists of lat/lng. + this.test_map_entity_2 = ` +<div class="caosdb-entity-panel caosdb-properties"> + <div class="caosdb-id">1234</div> + <div class="list-group-item caosdb-f-entity-property"> + <div class="caosdb-property-datatype">LIST</div> + <div class="caosdb-property-name">` + + lat + `</div> + <div class="caosdb-f-property-value"> + <div class="caosdb-value-list"><div + class="caosdb-f-property-single-raw-value">1.231</div> + <div class="caosdb-f-property-single-raw-value">1.232</div></div> + </div> + </div> + <div class="list-group-item caosdb-f-entity-property"> + <div class="caosdb-property-datatype">LIST</div> + <div class="caosdb-property-name">` + + lng + `</div> + <div class="caosdb-f-property-value"> + <div class="caosdb-value-list"><div + class="caosdb-f-property-single-raw-value">5.231</div> + <div class="caosdb-f-property-single-raw-value">5.232</div></div> + </div> + </div> </div>`; }, beforeEach: function (assert) { @@ -51,7 +76,7 @@ QUnit.module("ext_map.js", { }); QUnit.test("availability", function (assert) { - assert.equal(caosdb_map.version, "0.4", "test version"); + assert.equal(caosdb_map.version, "0.4.1", "test version"); assert.ok(caosdb_map.init, "init available"); }); @@ -125,6 +150,20 @@ QUnit.test("create_map_panel", function (assert) { assert.ok($(panel).hasClass("container"), "has class container"); }); +QUnit.test("init_map_panel", function (assert) { + assert.ok(caosdb_map.init_map_panel, "available"); + const dummy_nav = $("<nav/>"); + $(document.body).append(dummy_nav); + let panel = caosdb_map.init_map_panel(true); + + assert.ok($(panel).is(":visible"), "Panel is visible"); + + panel = caosdb_map.create_map_panel(true); + assert.notOk($(panel).is(":visible"), "Panel is not visible"); + + dummy_nav.remove(); +}); + QUnit.test("create_map_view", function (assert) { var view_config = $.extend(true, {}, caosdb_map._unconfigured_views[0], { "select": true, @@ -163,7 +202,6 @@ QUnit.test("create_map_view", function (assert) { map = caosdb_map.create_map_view(map_panel[0], view_config); - console.log(map_panel[0]); assert.ok(map instanceof L.Map, "map instance created"); assert.equal($(map_panel).find(".leaflet-container").length, 1, "map_panel has .leaflet-container child"); assert.ok(map._crs instanceof L.Proj.CRS, "map has special crs"); @@ -194,8 +232,24 @@ QUnit.test("create_entity_markers", function (assert) { assert.notOk(markers[0].getPopup(), "no popup"); // with popup - var markers = caosdb_map.create_entity_markers(entities, datamodel, () => "popup"); + markers = caosdb_map.create_entity_markers(entities, datamodel, () => "popup"); assert.ok(markers[0].getPopup(), "has popup"); + + + // test with list of lat/lng + entities = $(this.test_map_entity_2).toArray(); + markers = caosdb_map.create_entity_markers(entities, datamodel); + assert.equal(markers.length, 2, "has two marker"); + assert.ok(markers[0] instanceof L.Marker, "is marker"); + assert.ok(markers[1] instanceof L.Marker, "is marker"); + + latlng = markers[0]._latlng; + assert.equal(latlng.lat, "1.231", "latitude set"); + assert.equal(latlng.lng, "5.231", "longitude set"); + + latlng = markers[1]._latlng; + assert.equal(latlng.lat, "1.232", "latitude set"); + assert.equal(latlng.lng, "5.232", "longitude set"); }); @@ -249,9 +303,9 @@ QUnit.test("_get_with_POV ", function (assert) { assert.equal(caosdb_map._get_with_POV( []), "", "no POV"); assert.equal(caosdb_map._get_with_POV( - ["lol"]), " WITH lol ", "single POV"); + ["lol"]), " WITH \"lol\" ", "single POV"); assert.equal(caosdb_map._get_with_POV( - ["lol", "hi"]), " WITH lol WITH hi ", "with two POV"); + ["lol", "hi"]), " WITH \"lol\" WITH \"hi\" ", "with two POV"); }); @@ -260,71 +314,49 @@ QUnit.test("_get_select_with_path ", function (assert) { assert.throws(() => caosdb_map._get_select_with_path(this.datamodel, []), /Supply at least a RecordType./, "missing value"); assert.equal(caosdb_map._get_select_with_path( this.datamodel, - ["RealRT"]), "SELECT parent,latitude,longitude FROM ENTITY RealRT WITH latitude AND longitude ", "RT only"); + ["RealRT"]), "SELECT parent,latitude,longitude FROM ENTITY \"RealRT\" WITH ( \"latitude\" AND \"longitude\" ) ", "RT only"); assert.equal(caosdb_map._get_select_with_path( this.datamodel, - ["RealRT", "prop1"]), "SELECT parent,prop1.latitude,prop1.longitude FROM ENTITY RealRT WITH prop1 WITH latitude AND longitude ", "RT with one prop"); + ["RealRT", "prop1"]), "SELECT parent,prop1.latitude,prop1.longitude FROM ENTITY \"RealRT\" WITH \"prop1\" WITH ( \"latitude\" AND \"longitude\" ) ", "RT with one prop"); assert.equal(caosdb_map._get_select_with_path( this.datamodel, - ["RealRT", "prop1", "prop2"]), "SELECT parent,prop1.prop2.latitude,prop1.prop2.longitude FROM ENTITY RealRT WITH prop1 WITH prop2 WITH latitude AND longitude ", "RT with two props"); + ["RealRT", "prop1", "prop2"]), "SELECT parent,prop1.prop2.latitude,prop1.prop2.longitude FROM ENTITY \"RealRT\" WITH \"prop1\" WITH \"prop2\" WITH ( \"latitude\" AND \"longitude\" ) ", "RT with two props"); }); QUnit.test("_get_leaf_prop", async function (assert) { const test_response = str2xml(` -<Response srid="851d063d-121b-4b67-98d4-6e5ad4d31e72" timestamp="1606214042274" baseuri="https://localhost:10443" count="8"> - <Query string="select Campaign.responsible.firstname from icecore" results="8"> - <ParseTree>(cq select (prop_sel (prop_subsel (selector_txt C a m p a i g n) . (prop_subsel (selector_txt r e s p o n s i b l e) . (prop_subsel (selector_txt f i r s t n a m e ))))) from (entity icecore) <EOF>)</ParseTree> - <Role/> - <Entity>icecore</Entity> - <Selection> - <Selector name="Campaign.responsible.firstname"/> - </Selection> - </Query> +<Response> <Record id="6525" name="Test_IceCore_1"> - <Permissions/> <Property datatype="Campaign" id="6430" name="Campaign"> <Record id="6516" name="Test-2020_Camp1"> - <Permissions/> <Property datatype="REFERENCE" id="168" name="responsible"> <Record id="6515" name="Test_Scientist"> - <Permissions/> <Property datatype="DOUBLE" id="151" name="latitude" importance="FIX"> 1.34 - <Permissions/> </Property> <Property datatype="DOUBLE" id="151" name="longitude" importance="FIX"> 2 - <Permissions/> </Property> </Record> - <Permissions/> </Property> </Record> - <Permissions/> </Property> </Record> <Record id="6526" name="Test_IceCore_2"> - <Permissions/> <Property datatype="Campaign" id="6430" name="Campaign"> <Record id="6516" name="Test-2020_Camp1"> - <Permissions/> <Property datatype="REFERENCE" id="168" name="responsible"> <Record id="6515" name="Test_Scientist"> - <Permissions/> <Property datatype="DOUBLE" id="151" name="latitude" importance="FIX"> 3 - <Permissions/> </Property> <Property datatype="DOUBLE" id="151" name="longitude" importance="FIX"> 4.8345 - <Permissions/> </Property> </Record> - <Permissions/> </Property> </Record> - <Permissions/> </Property> </Record> </Response>`); @@ -359,6 +391,85 @@ QUnit.test("_get_leaf_prop", async function (assert) { }); + +QUnit.test("_get_leaf_prop - list of referenced map entities", async function (assert) { + const test_response = str2xml(` +<Response> + <Record id="7393"> + <Version id="7f04ebc3a09d43f8711371a1d62905e5fc6af80f" head="true" /> + <Parent id="7392" name="PathObject" /> + <Property datatype="LIST<MapObject>" id="7391" name="MapObject"> + <Value> + <Record id="7394" name="Object-0"> + <Version id="4c3b4a7ef4abc4d3b6045968f3b5f028d82baab2" head="true" /> + <Property datatype="DOUBLE" id="7389" name="longitude" importance="FIX" unit="°"> + -44.840238182501864 + </Property> + <Property datatype="DOUBLE" id="7390" name="latitude" importance="FIX" unit="°"> + 83.98152416509532 + </Property> + </Record> + </Value> + <Value> + <Record id="7395" name="Object-1"> + <Version id="42fbe0c9be68c356f81f590cddbdd3d5fc17cba4" head="true" /> + <Property datatype="DOUBLE" id="7389" name="longitude" importance="FIX" unit="°"> + -35.60247552143245 + </Property> + <Property datatype="DOUBLE" id="7390" name="latitude" importance="FIX" unit="°"> + 73.86388403927366 + </Property> + </Record> + </Value> + <Value> + <Record id="7396" name="Object-2"> + <Version id="45b71028261061e94ae198eaaa66af0612004173" head="true" /> + <Property datatype="DOUBLE" id="7389" name="longitude" importance="FIX" unit="°"> + -42.429495631197724 + </Property> + <Property datatype="DOUBLE" id="7390" name="latitude" importance="FIX" unit="°"> + 74.95382063506622 + </Property> + </Record> + </Value> + </Property> + </Record> +</Response> +`); + var leaves = caosdb_map._get_leaf_prop(test_response, 1, this.datamodel) + + assert.equal(Object.keys(leaves).length, 1, "number of records"); + var leave = leaves["7393"]; + assert.ok(leave, "has entity"); + assert.deepEqual(leave, [["83.98152416509532", + "73.86388403927366", "74.95382063506622"], [ "-44.840238182501864", + "-35.60247552143245", "-42.429495631197724" ]]); + + assert.equal( + caosdb_map._get_toplvl_rec_with_id(test_response, "7393")["id"], + "7393", + "number of records"); + + caosdb_map._set_subprops_at_top( + test_response, 1, this.datamodel, { + "7393": [["83.98152416509532", "73.86388403927366", + "74.95382063506622"], [ "-44.840238182501864", + "-35.60247552143245", "-42.429495631197724" ]] }) + assert.equal($(test_response).find(`[name='longitude']`).length, + 4, + "number lng props"); + assert.equal($(test_response).find(`[name='latitude']`).length, + 4, + "number lat props"); + // after transforming, the long/lat props should be accessible + var html_ents = await transformation.transformEntities(test_response); + assert.deepEqual( + getProperty(html_ents[0], "longitude"), [ "-44.840238182501864", + "-35.60247552143245", "-42.429495631197724" ], + "longitude of first rec"); + +}); + QUnit.test("_get_id_POV", function (assert) { assert.equal(caosdb_map._get_id_POV([]), "WITH ", "no POV"); assert.equal(caosdb_map._get_id_POV([5]), "WITH id=5", "one id"); diff --git a/test/core/js/modules/ext_references.js.js b/test/core/js/modules/ext_references.js.js index 54e06d33d5f1c33781efe11802a7fbfc5ba44d89..905b90674c6fdbeb683e42900960d646a0d0a315 100644 --- a/test/core/js/modules/ext_references.js.js +++ b/test/core/js/modules/ext_references.js.js @@ -104,7 +104,7 @@ QUnit.test("is_child", function(assert){ }); QUnit.test("get_person_str", function(assert){ - assert.ok(person_reference.get_person_str); + assert.ok(caosdb_default_person_reference.get_person_str); }); QUnit.test("update_visible_references_without_summary", async function(assert){ diff --git a/test/core/js/modules/ext_xls_download.js.js b/test/core/js/modules/ext_xls_download.js.js index 74cdb244dcf0f5c2238c8d2503a3194580c35d90..c0343828b3e4df976b006d8375936c8a339ea496 100644 --- a/test/core/js/modules/ext_xls_download.js.js +++ b/test/core/js/modules/ext_xls_download.js.js @@ -153,7 +153,6 @@ QUnit.test("_get_property_value", function (assert) { var summary = resolve_references.add_summary_field(property); - $(summary).find(".coasdb-property-value").append("summary bla"); ret = f(property); assert.equal(ret.pretty, "bla, bla, bla, bla, bla, bla, bla, bla, bla", "pretty shows list of reference info text"); diff --git a/test/core/js/modules/form_panel.js.js b/test/core/js/modules/form_panel.js.js index bc8343d4a65233e025039e7476861fb998c2abbc..be4c4ad8e66402adedf8c386ff086be4d7cb7955 100644 --- a/test/core/js/modules/form_panel.js.js +++ b/test/core/js/modules/form_panel.js.js @@ -55,7 +55,7 @@ QUnit.test("create_show_form_callback ", function (assert) { help: help_text, }, ], }; - cb = form_panel.create_show_form_callback( panel_id, title, csv_form_config); + const cb = form_panel.create_show_form_callback( panel_id, title, csv_form_config); assert.equal(typeof cb, "function", "function created"); cb() }); diff --git a/test/core/js/modules/webcaosdb.js.js b/test/core/js/modules/webcaosdb.js.js index 5f45c32adb9d171c5d362467d0bba0840f02836f..1c456847e1dfda20eb3a4dd70b7b3bcd37086d08 100644 --- a/test/core/js/modules/webcaosdb.js.js +++ b/test/core/js/modules/webcaosdb.js.js @@ -1160,7 +1160,7 @@ QUnit.test("restoreLastQuery", function (assert) { QUnit.test("bindOnClick", function (assert) { assert.ok(queryForm.bindOnClick, "available"); - var done = assert.async(2); + var done = assert.async(3); queryForm.redirect = function (a, b) { done(); }; @@ -1191,19 +1191,29 @@ QUnit.test("bindOnClick", function (assert) { assert.equal(storage(), undefined, "after1: storage still empty."); - // test the click handler of the button - form.query.value = "free text"; + // test the click handler of the button, first without spaces ... + form.query.value = "freetext"; assert.equal(storage(), undefined, "before2: storage empty."); form.getElementsByClassName("caosdb-search-btn")[0].onclick(); - assert.equal(storage(), "FIND ENTITY WHICH HAS A PROPERTY LIKE '*free text*'", "after2: storage not empty."); + assert.equal(storage(), "FIND ENTITY WHICH HAS A PROPERTY LIKE '*freetext*'", "after2: storage not empty."); // test the form submit handler analogously - form.query.value = "free text 2"; + form.query.value = "freetext2"; + $("body").append(form); + $(form).append(submitButton); + submitButton.click(); + assert.equal(storage(), "FIND ENTITY WHICH HAS A PROPERTY LIKE '*freetext2*'", "after3: storage not empty."); + + $(form).remove(); + + // ... then with spaces + form.query.value = "free text 3"; $("body").append(form); $(form).append(submitButton); submitButton.click(); - assert.equal(storage(), "FIND ENTITY WHICH HAS A PROPERTY LIKE '*free text 2*'", "after3: storage not empty."); + assert.equal(storage(), "FIND ENTITY WHICH HAS A PROPERTY LIKE '*free*' AND A PROPERTY LIKE '*text*' AND A PROPERTY LIKE '*3*'", "after4: storage not empty."); + $(form).remove(); }) diff --git a/test/core/xml/test_case_list_of_myrecordtype.xml b/test/core/xml/test_case_list_of_myrecordtype.xml index b67c857e22a983774b0ae1ba7e88d9ccd2515de8..63587832758049c6de22055225c9dbb9acb7ff48 100644 --- a/test/core/xml/test_case_list_of_myrecordtype.xml +++ b/test/core/xml/test_case_list_of_myrecordtype.xml @@ -24,6 +24,7 @@ <Property id="149315" name="MyRecordType" datatype="LIST<MyRecordType>" importance="FIX"> <Value>167510</Value> + <Value></Value> <Value>167546</Value> <Value>167574</Value> <Value>167625</Value> diff --git a/test/docker/Dockerfile b/test/docker/Dockerfile index 2880d864bc7df86bf7cade4d6c232b387e513bfd..dd6c8d65799d7ed7a8c4db0109d85f279d0778ac 100644 --- a/test/docker/Dockerfile +++ b/test/docker/Dockerfile @@ -1,24 +1,22 @@ -FROM debian:10 +FROM debian:11 ADD node_gpg.asc /etc/apt/ RUN apt-get update \ && apt-get install -y gnupg ca-certificates\ && apt-key add /etc/apt/node_gpg.asc \ - && echo "deb http://deb.debian.org/debian buster-backports main" >> /etc/apt/sources.list \ - && echo "deb https://deb.nodesource.com/node_14.x buster main" >> /etc/apt/sources.list \ && apt-get update \ && apt-get install -y \ firefox-esr gettext-base python3-pip \ python3-httpbin git curl x11-apps xvfb unzip \ libhdf5-dev \ pkgconf \ - nodejs # Don't install `npm` (Debian), it conflicts with the `nodejs` (Node) package \ + nodejs npm \ && apt-get install -f RUN pip3 install pylint pytest -RUN pip3 install caosdb>=0.5.2 +RUN pip3 install caosdb>=0.7.4 RUN pip3 install pandas RUN pip3 install git+https://gitlab.com/caosdb/caosdb-advanced-user-tools.git@dev # For automatic documentation -#RUN npm install -g jsdoc -#RUN npm install -g jsdoc-sphinx +# RUN npm install -g jsdoc +# RUN npm install -g jsdoc-sphinx RUN pip3 install sphinx-js sphinx-autoapi recommonmark sphinx-rtd-theme