typo3-typescript-sources 13.4.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/sync-and-publish.yml +265 -0
- package/.last-synced-tag +1 -0
- package/LICENSE.txt +339 -0
- package/README-TYPO3.md +166 -0
- package/README.md +28 -0
- package/adminpanel/admin-panel.ts +375 -0
- package/adminpanel/modules/cache.ts +27 -0
- package/adminpanel/modules/preview.ts +75 -0
- package/backend/action-button/abstract-action.ts +22 -0
- package/backend/action-button/deferred-action.ts +41 -0
- package/backend/action-button/immediate-action.ts +31 -0
- package/backend/action-dispatcher.ts +104 -0
- package/backend/ajax-data-handler/message-interface.ts +18 -0
- package/backend/ajax-data-handler/response-interface.ts +20 -0
- package/backend/ajax-data-handler.ts +214 -0
- package/backend/backend-exception.ts +22 -0
- package/backend/broadcast-message.ts +50 -0
- package/backend/broadcast-service.ts +52 -0
- package/backend/browse-database.ts +38 -0
- package/backend/clear-cache.ts +88 -0
- package/backend/clipboard-panel.ts +240 -0
- package/backend/close-current-window.ts +2 -0
- package/backend/code-editor/autocomplete/completion-result.ts +105 -0
- package/backend/code-editor/autocomplete/ts-code-completion.ts +142 -0
- package/backend/code-editor/autocomplete/ts-parser.ts +485 -0
- package/backend/code-editor/autocomplete/ts-ref.ts +164 -0
- package/backend/code-editor/element/code-mirror-element.ts +282 -0
- package/backend/code-editor/language/typoscript.ts +181 -0
- package/backend/code-editor/stream-parser/typoscript.ts +1600 -0
- package/backend/color-picker.ts +158 -0
- package/backend/color-scheme-switch.ts +179 -0
- package/backend/column-selector-button.ts +302 -0
- package/backend/context-help.ts +68 -0
- package/backend/context-menu-actions.ts +322 -0
- package/backend/context-menu.ts +751 -0
- package/backend/copy-to-clipboard.ts +79 -0
- package/backend/date-time-picker.ts +270 -0
- package/backend/document-save-actions.ts +159 -0
- package/backend/drag-tooltip.ts +285 -0
- package/backend/drag-uploader.ts +914 -0
- package/backend/element/alert-element.ts +116 -0
- package/backend/element/dispatch-modal-button.ts +50 -0
- package/backend/element/draggable-resizable-element.ts +249 -0
- package/backend/element/editable-page-title.ts +306 -0
- package/backend/element/icon-element.ts +84 -0
- package/backend/element/immediate-action-element.ts +89 -0
- package/backend/element/pagination.ts +59 -0
- package/backend/element/progress-bar-element.ts +172 -0
- package/backend/element/progress-tracker-element.ts +119 -0
- package/backend/element/pseudo-button.ts +58 -0
- package/backend/element/spinner-element.ts +51 -0
- package/backend/element/thumbnail-element.ts +80 -0
- package/backend/element-browser.ts +182 -0
- package/backend/enum/data-transfer-types.ts +21 -0
- package/backend/enum/icon-types.ts +31 -0
- package/backend/enum/key-types.ts +27 -0
- package/backend/enum/severity.ts +20 -0
- package/backend/enum/viewport/scaffold-identifier.ts +24 -0
- package/backend/event/client-request.ts +25 -0
- package/backend/event/consumable.ts +18 -0
- package/backend/event/consumer-scope.ts +54 -0
- package/backend/event/event-dispatcher.ts +26 -0
- package/backend/event/interaction-request-assignment.ts +24 -0
- package/backend/event/interaction-request-map.ts +75 -0
- package/backend/event/interaction-request.ts +48 -0
- package/backend/event/trigger-request.ts +56 -0
- package/backend/form/submit-interceptor.ts +66 -0
- package/backend/form-engine/container/files-control-container.ts +716 -0
- package/backend/form-engine/container/flex-form-container-container.ts +166 -0
- package/backend/form-engine/container/flex-form-section-container.ts +210 -0
- package/backend/form-engine/container/inline-control-container.ts +1230 -0
- package/backend/form-engine/container/site-language-container.ts +616 -0
- package/backend/form-engine/element/abstract-sortable-select-items.ts +186 -0
- package/backend/form-engine/element/category-element.ts +160 -0
- package/backend/form-engine/element/color-element.ts +81 -0
- package/backend/form-engine/element/datetime-element.ts +66 -0
- package/backend/form-engine/element/extra/char-counter.ts +112 -0
- package/backend/form-engine/element/extra/select-box-filter.ts +85 -0
- package/backend/form-engine/element/folder-element.ts +66 -0
- package/backend/form-engine/element/group-element.ts +47 -0
- package/backend/form-engine/element/json-element.ts +52 -0
- package/backend/form-engine/element/link-element.ts +121 -0
- package/backend/form-engine/element/mfa-info-element.ts +169 -0
- package/backend/form-engine/element/modifier/resizable.ts +27 -0
- package/backend/form-engine/element/modifier/tabbable.ts +29 -0
- package/backend/form-engine/element/online-media-form-element.ts +65 -0
- package/backend/form-engine/element/password-element.ts +71 -0
- package/backend/form-engine/element/select-multiple-side-by-side-element.ts +83 -0
- package/backend/form-engine/element/select-single-element.ts +82 -0
- package/backend/form-engine/element/select-tree-element.ts +148 -0
- package/backend/form-engine/element/select-tree-toolbar.ts +118 -0
- package/backend/form-engine/element/select-tree.ts +210 -0
- package/backend/form-engine/element/slug-element.ts +243 -0
- package/backend/form-engine/element/suggest/result-container.ts +118 -0
- package/backend/form-engine/element/suggest/result-item.ts +90 -0
- package/backend/form-engine/element/table-permission-element.ts +101 -0
- package/backend/form-engine/element/table-wizard-element.ts +427 -0
- package/backend/form-engine/element/text-element.ts +52 -0
- package/backend/form-engine/element/text-table-element.ts +31 -0
- package/backend/form-engine/field-control/add-record.ts +40 -0
- package/backend/form-engine/field-control/edit-popup.ts +64 -0
- package/backend/form-engine/field-control/insert-clipboard.ts +50 -0
- package/backend/form-engine/field-control/link-popup.ts +53 -0
- package/backend/form-engine/field-control/list-module.ts +40 -0
- package/backend/form-engine/field-control/password-generator.ts +102 -0
- package/backend/form-engine/field-control/reset-selection.ts +48 -0
- package/backend/form-engine/field-wizard/localization-state-selector.ts +67 -0
- package/backend/form-engine/field-wizard/value-picker.ts +123 -0
- package/backend/form-engine/field-wizard/value-slider.ts +86 -0
- package/backend/form-engine/inline-relation/ajax-dispatcher.ts +124 -0
- package/backend/form-engine/inline-relation/inline-response-interface.ts +41 -0
- package/backend/form-engine-link-browser-adapter.ts +129 -0
- package/backend/form-engine-review.ts +155 -0
- package/backend/form-engine-suggest.ts +133 -0
- package/backend/form-engine-validation.ts +686 -0
- package/backend/form-engine.ts +1391 -0
- package/backend/global-event-handler.ts +252 -0
- package/backend/grid-editor.ts +1077 -0
- package/backend/hashing/md5.ts +252 -0
- package/backend/helper.ts +24 -0
- package/backend/hotkeys/hotkey-storage.ts +63 -0
- package/backend/hotkeys.ts +216 -0
- package/backend/icons.ts +236 -0
- package/backend/image-manipulation.ts +885 -0
- package/backend/info-window.ts +46 -0
- package/backend/input/clearable.ts +128 -0
- package/backend/input/index.d.ts +8 -0
- package/backend/java-script-module-import-event-handler.ts +19 -0
- package/backend/key-bindings.ts +42 -0
- package/backend/layout-module/drag-drop.ts +359 -0
- package/backend/layout-module/paste.ts +165 -0
- package/backend/link-browser.ts +89 -0
- package/backend/live-search/element/backend-search.ts +34 -0
- package/backend/live-search/element/hint.ts +33 -0
- package/backend/live-search/element/provider/default-result-item.ts +52 -0
- package/backend/live-search/element/provider/page-provider-result-item.ts +52 -0
- package/backend/live-search/element/result/item/action/action-container.ts +135 -0
- package/backend/live-search/element/result/item/action/action.ts +56 -0
- package/backend/live-search/element/result/item/item-container.ts +224 -0
- package/backend/live-search/element/result/item/item.ts +74 -0
- package/backend/live-search/element/result/result-container.ts +110 -0
- package/backend/live-search/element/result/result-detail-container.ts +50 -0
- package/backend/live-search/element/result/result-pagination.ts +122 -0
- package/backend/live-search/element/search-option-item.ts +70 -0
- package/backend/live-search/live-search-configurator.ts +54 -0
- package/backend/live-search/live-search-shortcut.ts +29 -0
- package/backend/live-search/result-types/backend-module-result-type.ts +8 -0
- package/backend/live-search/result-types/default-result-type.ts +21 -0
- package/backend/live-search/result-types/page-result-type.ts +23 -0
- package/backend/localization.ts +390 -0
- package/backend/login-refresh.ts +525 -0
- package/backend/login.ts +179 -0
- package/backend/mail-link-handler.ts +45 -0
- package/backend/modal-interface.ts +19 -0
- package/backend/modal.ts +685 -0
- package/backend/module/iframe.ts +125 -0
- package/backend/module/router.ts +275 -0
- package/backend/module-menu.ts +550 -0
- package/backend/module.ts +133 -0
- package/backend/multi-record-selection-action.ts +44 -0
- package/backend/multi-record-selection-delete-action.ts +108 -0
- package/backend/multi-record-selection-edit-action.ts +56 -0
- package/backend/multi-record-selection.ts +468 -0
- package/backend/multi-step-wizard.ts +568 -0
- package/backend/new-content-element-wizard-button.ts +51 -0
- package/backend/new-multiple-pages.ts +108 -0
- package/backend/new-record-wizard.ts +680 -0
- package/backend/notification.ts +366 -0
- package/backend/offset.ts +40 -0
- package/backend/online-media.ts +110 -0
- package/backend/page-actions.ts +91 -0
- package/backend/page-link-handler.ts +51 -0
- package/backend/pagetsconfig/pagetsconfig-includes.ts +77 -0
- package/backend/popover.ts +165 -0
- package/backend/record-download-button.ts +104 -0
- package/backend/record-link-handler.ts +31 -0
- package/backend/record-search.ts +44 -0
- package/backend/recordlist.ts +230 -0
- package/backend/resource/resource.ts +35 -0
- package/backend/security/element/csp-reports.ts +406 -0
- package/backend/security/element/sudo-mode.ts +249 -0
- package/backend/security/sudo-mode-interceptor.ts +19 -0
- package/backend/settings/editor/editable-setting.ts +231 -0
- package/backend/settings/editor.ts +451 -0
- package/backend/settings/enum/settings-mode.enum.ts +24 -0
- package/backend/settings/type/base.ts +200 -0
- package/backend/settings/type/bool.ts +56 -0
- package/backend/settings/type/color.ts +64 -0
- package/backend/settings/type/int.ts +85 -0
- package/backend/settings/type/number.ts +61 -0
- package/backend/settings/type/page.ts +100 -0
- package/backend/settings/type/string.ts +77 -0
- package/backend/settings/type/stringlist.ts +87 -0
- package/backend/severity.ts +88 -0
- package/backend/site-inline-actions.ts +32 -0
- package/backend/sortable-table.ts +208 -0
- package/backend/storage/abstract-client-storage.ts +76 -0
- package/backend/storage/browser-session.ts +28 -0
- package/backend/storage/client.ts +28 -0
- package/backend/storage/module-state-storage.ts +118 -0
- package/backend/storage/persistent.ts +204 -0
- package/backend/switch-user.ts +82 -0
- package/backend/tabs.ts +72 -0
- package/backend/telephone-link-handler.ts +40 -0
- package/backend/tests/backend/storage/browser-storage-test.ts +87 -0
- package/backend/tests/backend/storage/client-test.ts +87 -0
- package/backend/tests/backend/utility/dom-helper-test.ts +51 -0
- package/backend/tests/backend-exception-test.ts +28 -0
- package/backend/tests/element/immediate-action-element-test.ts +81 -0
- package/backend/tests/form-engine-validation-test.ts +304 -0
- package/backend/tests/grid-editor-test.ts +29 -0
- package/backend/tests/hashing/md5Test.ts +28 -0
- package/backend/tests/icons-test.ts +23 -0
- package/backend/tests/notification-test.ts +161 -0
- package/backend/tests/popover-test.ts +83 -0
- package/backend/toolbar/clear-cache-menu.ts +88 -0
- package/backend/toolbar/live-search.ts +284 -0
- package/backend/toolbar/shortcut-menu.ts +254 -0
- package/backend/toolbar/system-information-menu.ts +161 -0
- package/backend/toolbar.ts +41 -0
- package/backend/tree/file-storage-browser.ts +194 -0
- package/backend/tree/file-storage-tree-container.ts +465 -0
- package/backend/tree/file-storage-tree.ts +42 -0
- package/backend/tree/page-browser.ts +272 -0
- package/backend/tree/page-tree-element.ts +602 -0
- package/backend/tree/page-tree.ts +152 -0
- package/backend/tree/tree-module-state.ts +159 -0
- package/backend/tree/tree-node-toggle.ts +31 -0
- package/backend/tree/tree-node.ts +97 -0
- package/backend/tree/tree-toolbar.ts +117 -0
- package/backend/tree/tree.ts +1595 -0
- package/backend/url-link-handler.ts +36 -0
- package/backend/user-pass-login.ts +255 -0
- package/backend/user-settings-manager.ts +189 -0
- package/backend/utility/collapse-state-persister.ts +205 -0
- package/backend/utility/collapse-state-search.ts +183 -0
- package/backend/utility/dom-helper.ts +91 -0
- package/backend/utility/drag-drop-utility.ts +47 -0
- package/backend/utility/message-utility.ts +45 -0
- package/backend/utility/top-level-module-import.ts +32 -0
- package/backend/utility.ts +194 -0
- package/backend/viewport/abstract-container.ts +22 -0
- package/backend/viewport/content-container.ts +98 -0
- package/backend/viewport/loader.ts +28 -0
- package/backend/viewport/navigation-container.ts +119 -0
- package/backend/viewport/resizable-navigation.ts +176 -0
- package/backend/viewport/toolbar.ts +27 -0
- package/backend/viewport/topbar.ts +39 -0
- package/backend/viewport.ts +46 -0
- package/backend/window-manager.ts +84 -0
- package/backend/wizard/move-content-element.ts +107 -0
- package/backend/wizard/move-page.ts +74 -0
- package/backend/wizard.ts +331 -0
- package/belog/backend-log.ts +88 -0
- package/beuser/backend-user-listing.ts +40 -0
- package/beuser/permissions.ts +291 -0
- package/core/ajax/ajax-request-types.ts +15 -0
- package/core/ajax/ajax-request.ts +202 -0
- package/core/ajax/ajax-response.ts +57 -0
- package/core/ajax/input-transformer.ts +92 -0
- package/core/ajax/simple-response-interface.ts +5 -0
- package/core/authentication/mfa-provider/totp.ts +54 -0
- package/core/cache/dedupe-async-task.ts +95 -0
- package/core/directive/markdown.ts +82 -0
- package/core/directive/nl2br.ts +16 -0
- package/core/document-service.ts +35 -0
- package/core/event/debounce-event.ts +40 -0
- package/core/event/event-interface.ts +24 -0
- package/core/event/regular-event.ts +58 -0
- package/core/event/request-animation-frame-event.ts +42 -0
- package/core/event/throttle-event.ts +54 -0
- package/core/java-script-item-handler.ts +29 -0
- package/core/java-script-item-processor.ts +171 -0
- package/core/lit-helper.ts +126 -0
- package/core/literals.ts +17 -0
- package/core/referrer-refresh.ts +2 -0
- package/core/security-utility.ts +89 -0
- package/core/tests/ajax/ajax-request-test.ts +290 -0
- package/core/tests/ajax/input-transformer-test.ts +64 -0
- package/core/tests/cache/dedupe-async-task-test.ts +152 -0
- package/core/tests/event/throttle-event-test.ts +47 -0
- package/core/tests/literals-test.ts +24 -0
- package/core/tests/security-utility-test.ts +50 -0
- package/core/utility/types.ts +9 -0
- package/dashboard/chart-initializer.ts +136 -0
- package/dashboard/dashboard.ts +1487 -0
- package/extensionmanager/distribution-image.ts +55 -0
- package/extensionmanager/main.ts +291 -0
- package/extensionmanager/repository.ts +195 -0
- package/extensionmanager/update.ts +118 -0
- package/extensionmanager/upload-form.ts +42 -0
- package/filelist/browse-files.ts +148 -0
- package/filelist/browse-folders.ts +94 -0
- package/filelist/context-menu-actions.ts +335 -0
- package/filelist/create-folder.ts +69 -0
- package/filelist/file-delete.ts +69 -0
- package/filelist/file-list-actions.ts +132 -0
- package/filelist/file-list-dragdrop.ts +139 -0
- package/filelist/file-list-rename-handler.ts +128 -0
- package/filelist/file-list-transfer-handler.ts +138 -0
- package/filelist/file-list.ts +396 -0
- package/filelist/file-replace.ts +37 -0
- package/filelist/linkbrowser-file-handler.ts +94 -0
- package/filelist/linkbrowser-folder-handler.ts +74 -0
- package/filelist/rename-file.ts +109 -0
- package/form/backend/form-editor/component/property-grid-editor.ts +340 -0
- package/form/backend/form-editor/core.ts +2104 -0
- package/form/backend/form-editor/helper.ts +226 -0
- package/form/backend/form-editor/inspector-component.ts +2323 -0
- package/form/backend/form-editor/mediator.ts +941 -0
- package/form/backend/form-editor/modals-component.ts +433 -0
- package/form/backend/form-editor/stage-component.ts +908 -0
- package/form/backend/form-editor/tree-component.ts +548 -0
- package/form/backend/form-editor/view-model.ts +1343 -0
- package/form/backend/form-editor.ts +846 -0
- package/form/backend/form-manager/main.ts +39 -0
- package/form/backend/form-manager/view-model.ts +789 -0
- package/form/backend/form-manager.ts +180 -0
- package/form/backend/helper.ts +60 -0
- package/form/frontend/date-picker.ts +38 -0
- package/frontend/default_frontend.ts +82 -0
- package/impexp/context-menu-actions.ts +50 -0
- package/impexp/import-export.ts +62 -0
- package/indexed-search/pagination.ts +26 -0
- package/indexed-search/statistics.ts +50 -0
- package/info/translation-status.ts +48 -0
- package/install/ajax/ajax-queue.ts +83 -0
- package/install/init-install.ts +2 -0
- package/install/init-installer.ts +2 -0
- package/install/install.ts +25 -0
- package/install/installer.ts +458 -0
- package/install/message-interface.ts +18 -0
- package/install/module/abstract-inline-module.ts +27 -0
- package/install/module/abstract-interactable-module.ts +107 -0
- package/install/module/environment/environment-check.ts +92 -0
- package/install/module/environment/folder-structure.ts +145 -0
- package/install/module/environment/image-processing.ts +141 -0
- package/install/module/environment/mail-test.ts +127 -0
- package/install/module/environment/php-info.ts +50 -0
- package/install/module/environment/system-information.ts +50 -0
- package/install/module/maintenance/cache.ts +59 -0
- package/install/module/maintenance/clear-tables.ts +134 -0
- package/install/module/maintenance/clear-typo3temp-files.ts +137 -0
- package/install/module/maintenance/create-admin.ts +126 -0
- package/install/module/maintenance/database-analyzer.ts +266 -0
- package/install/module/maintenance/dump-autoload.ts +58 -0
- package/install/module/maintenance/language-packs.ts +338 -0
- package/install/module/maintenance/reset-backend-user-uc.ts +58 -0
- package/install/module/password-strength.ts +56 -0
- package/install/module/settings/change-install-tool-password.ts +101 -0
- package/install/module/settings/extension-configuration.ts +224 -0
- package/install/module/settings/features.ts +102 -0
- package/install/module/settings/local-configuration.ts +316 -0
- package/install/module/settings/presets.ts +147 -0
- package/install/module/settings/system-maintainer.ts +111 -0
- package/install/module/upgrade/core-update.ts +255 -0
- package/install/module/upgrade/extension-compat-tester.ts +195 -0
- package/install/module/upgrade/extension-scanner.ts +381 -0
- package/install/module/upgrade/tca-ext-tables-check.ts +93 -0
- package/install/module/upgrade/tca-migrations-check.ts +101 -0
- package/install/module/upgrade/upgrade-docs.ts +310 -0
- package/install/module/upgrade/upgrade-wizards.ts +612 -0
- package/install/renderable/clearable.ts +113 -0
- package/install/renderable/flash-message.ts +64 -0
- package/install/renderable/info-box.ts +66 -0
- package/install/renderable/language-packs.ts +304 -0
- package/install/renderable/offset-group.ts +38 -0
- package/install/renderable/severity.ts +49 -0
- package/install/renderable/wrap-group.ts +36 -0
- package/install/router.ts +468 -0
- package/linkvalidator/linkvalidator-modal.ts +45 -0
- package/linkvalidator/linkvalidator.ts +101 -0
- package/lowlevel/query-generator.ts +132 -0
- package/lowlevel/reference-index.ts +44 -0
- package/opendocs/toolbar/opendocs-menu.ts +117 -0
- package/package.json +34 -0
- package/recycler/recycler.ts +539 -0
- package/redirects/event-handler.ts +108 -0
- package/redirects/form-engine-evaluation.ts +34 -0
- package/redirects/redirects-module.ts +58 -0
- package/rte-ckeditor/ckeditor5.ts +402 -0
- package/rte-ckeditor/css-prefixer.ts +94 -0
- package/rte-ckeditor/legacy/ckeditor5-bundle.ts +17 -0
- package/rte-ckeditor/legacy/ckeditor5-inspector.ts +2 -0
- package/rte-ckeditor/plugin/typo3-link.ts +810 -0
- package/rte-ckeditor/plugin/whitespace.ts +114 -0
- package/rte-ckeditor/rte-link-browser.ts +112 -0
- package/rte-ckeditor/tests/css-prefixer-test.ts +314 -0
- package/scheduler/scheduler-add-group.ts +119 -0
- package/scheduler/scheduler-editable-group-name.ts +307 -0
- package/scheduler/scheduler-sortable-groups.ts +59 -0
- package/scheduler/scheduler.ts +466 -0
- package/setup/setup-module.ts +122 -0
- package/styleguide/checkbox-demo.ts +5 -0
- package/styleguide/element/theme-switcher-element.ts +101 -0
- package/styleguide/filter.ts +22 -0
- package/styleguide/find-icons.ts +78 -0
- package/styleguide/processing-indicator.ts +78 -0
- package/styleguide/render-notifications.ts +64 -0
- package/sys-note/element/delete-button.ts +78 -0
- package/tstemplate/constant-editor.ts +64 -0
- package/tstemplate/information-module.ts +31 -0
- package/tstemplate/template-analyzer.ts +77 -0
- package/viewpage/main.ts +253 -0
- package/workspaces/backend.ts +979 -0
- package/workspaces/preview.ts +232 -0
- package/workspaces/renderable/comment-view.ts +79 -0
- package/workspaces/renderable/diff-view.ts +58 -0
- package/workspaces/renderable/history-view.ts +97 -0
- package/workspaces/renderable/record-information.ts +118 -0
- package/workspaces/renderable/record-table.ts +355 -0
- package/workspaces/renderable/send-to-stage-form.ts +108 -0
- package/workspaces/toolbar/workspaces-menu.ts +190 -0
- package/workspaces/utility/icon-helper.ts +57 -0
- package/workspaces/workspaces.ts +136 -0
|
@@ -0,0 +1,1391 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file is part of the TYPO3 CMS project.
|
|
3
|
+
*
|
|
4
|
+
* It is free software; you can redistribute it and/or modify it under
|
|
5
|
+
* the terms of the GNU General Public License, either version 2
|
|
6
|
+
* of the License, or any later version.
|
|
7
|
+
*
|
|
8
|
+
* For the full copyright and license information, please read the
|
|
9
|
+
* LICENSE.txt file that was distributed with this source code.
|
|
10
|
+
*
|
|
11
|
+
* The TYPO3 project - inspiring people to share!
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* contains all JS functions related to TYPO3 TCEforms/FormEngine
|
|
16
|
+
*
|
|
17
|
+
* there are separate issues in this main object
|
|
18
|
+
* - functions, related to Element Browser ("Popup Window") and select fields
|
|
19
|
+
* - filling select fields (by wizard etc) from outside, formerly known via "setFormValueFromBrowseWin"
|
|
20
|
+
* - select fields: move selected items up and down via buttons, remove items etc
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import DocumentService from '@typo3/core/document-service';
|
|
24
|
+
import $ from 'jquery';
|
|
25
|
+
import FormEngineValidation from '@typo3/backend/form-engine-validation';
|
|
26
|
+
import { default as Modal, type ModalElement } from '@typo3/backend/modal';
|
|
27
|
+
import * as MessageUtility from '@typo3/backend/utility/message-utility';
|
|
28
|
+
import Severity from '@typo3/backend/severity';
|
|
29
|
+
import * as BackendExceptionModule from '@typo3/backend/backend-exception';
|
|
30
|
+
import InteractionRequestMap from '@typo3/backend/event/interaction-request-map';
|
|
31
|
+
import type InteractionRequest from '@typo3/backend/event/interaction-request';
|
|
32
|
+
import type TriggerRequest from '@typo3/backend/event/trigger-request';
|
|
33
|
+
import Utility from '@typo3/backend/utility';
|
|
34
|
+
import { selector } from '@typo3/core/literals';
|
|
35
|
+
import '@typo3/backend/form-engine/element/extra/char-counter';
|
|
36
|
+
import type { PromiseControls } from '@typo3/backend/event/interaction-request-assignment';
|
|
37
|
+
import Hotkeys, { ModifierKeys } from '@typo3/backend/hotkeys';
|
|
38
|
+
import RegularEvent from '@typo3/core/event/regular-event';
|
|
39
|
+
|
|
40
|
+
export interface OnFieldChangeItem {
|
|
41
|
+
name: string;
|
|
42
|
+
data: {[key: string]: string|number|boolean|null}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
type FormEngineType = {
|
|
46
|
+
[functionName: string]: any,
|
|
47
|
+
consumeTypes: string[],
|
|
48
|
+
Validation: typeof FormEngineValidation,
|
|
49
|
+
interactionRequestMap: typeof InteractionRequestMap,
|
|
50
|
+
formName: string,
|
|
51
|
+
formElement: HTMLFormElement,
|
|
52
|
+
openedPopupWindow: Window | null,
|
|
53
|
+
browserUrl: string,
|
|
54
|
+
doSaveFieldName: string,
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
type OnChangeFieldHandlerCallback = (data: object, e: Event) => void;
|
|
58
|
+
type PreviewActionCallback = (targetName: string, previewUrl: string, $actionElement: JQuery, modal: ModalElement) => void;
|
|
59
|
+
type NewActionCallback = (targetName: string, $actionElement: JQuery) => void;
|
|
60
|
+
type DuplicateActionCallback = (targetName: string, $actionElement: JQuery) => void;
|
|
61
|
+
type DeleteActionCallback = (targetName: string, $actionElement: JQuery) => void;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Module: @typo3/backend/form-engine
|
|
65
|
+
*/
|
|
66
|
+
export default (function() {
|
|
67
|
+
|
|
68
|
+
function handleConsumeResponse(interactionRequest: InteractionRequest, response: boolean): void {
|
|
69
|
+
if (response) {
|
|
70
|
+
FormEngine.interactionRequestMap.resolveFor(interactionRequest);
|
|
71
|
+
} else {
|
|
72
|
+
FormEngine.interactionRequestMap.rejectFor(interactionRequest);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const onFieldChangeHandlers: Map<string, OnChangeFieldHandlerCallback> = new Map();
|
|
77
|
+
|
|
78
|
+
// @see \TYPO3\CMS\Backend\Form\Behavior\UpdateValueOnFieldChange
|
|
79
|
+
onFieldChangeHandlers.set('typo3-backend-form-update-value', (data: {elementName: string}) => {
|
|
80
|
+
const valueField = document.querySelector(selector`[name="${data.elementName}"]`);
|
|
81
|
+
const humanReadableField = document.querySelector(selector`[data-formengine-input-name="${data.elementName}"]`);
|
|
82
|
+
FormEngine.Validation.updateInputField(data.elementName);
|
|
83
|
+
if (valueField !== null) {
|
|
84
|
+
FormEngine.Validation.markFieldAsChanged(valueField as HTMLInputElement);
|
|
85
|
+
FormEngine.Validation.validateField(valueField as HTMLInputElement);
|
|
86
|
+
}
|
|
87
|
+
if (humanReadableField !== null && humanReadableField !== valueField) {
|
|
88
|
+
FormEngine.Validation.validateField(humanReadableField as HTMLTextAreaElement);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
// @see \TYPO3\CMS\Backend\Form\Behavior\ReloadOnFieldChange
|
|
92
|
+
onFieldChangeHandlers.set('typo3-backend-form-reload', (data: {confirmation: boolean}) => {
|
|
93
|
+
const saveDocumentWithoutValidation = (): void => {
|
|
94
|
+
// Shortcut method to suspend FormEngine validation on purpose as user attempts to switch to another document type
|
|
95
|
+
// and fields may become irrelevant after switching the type (e.g. the "URL" field when switching a page's doktype from "External URL" to "Standard").
|
|
96
|
+
// This is a workaround! FormEngine must be able to determine on a field basis whether the field is still relevant or not.
|
|
97
|
+
FormEngine.Validation.suspend();
|
|
98
|
+
FormEngine.saveDocument();
|
|
99
|
+
FormEngine.Validation.resume();
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
if (!data.confirmation) {
|
|
103
|
+
saveDocumentWithoutValidation();
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const modal = Modal.advanced({
|
|
107
|
+
title: TYPO3.lang['FormEngine.refreshRequiredTitle'],
|
|
108
|
+
content: TYPO3.lang['FormEngine.refreshRequiredContent'],
|
|
109
|
+
severity: Severity.warning,
|
|
110
|
+
staticBackdrop: true,
|
|
111
|
+
buttons: [
|
|
112
|
+
{
|
|
113
|
+
text: TYPO3.lang['FormEngine.refreshRequiredCancel'] || TYPO3.lang['button.cancel'] || 'Cancel',
|
|
114
|
+
active: true,
|
|
115
|
+
btnClass: 'btn-default',
|
|
116
|
+
name: 'cancel',
|
|
117
|
+
trigger: () => {
|
|
118
|
+
modal.hideModal();
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
text: TYPO3.lang['FormEngine.refreshRequiredConfirm'] || TYPO3.lang['button.ok'] || 'OK',
|
|
123
|
+
btnClass: 'btn-' + Severity.getCssClass(Severity.warning),
|
|
124
|
+
name: 'ok',
|
|
125
|
+
trigger: () => {
|
|
126
|
+
FormEngine.disableDocHeaderButtons();
|
|
127
|
+
FormEngine.closeModalsRecursive();
|
|
128
|
+
saveDocumentWithoutValidation();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
]
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
// @see \TYPO3\CMS\Backend\Form\Behavior\UpdateBitmaskOnFieldChange
|
|
135
|
+
onFieldChangeHandlers.set('typo3-backend-form-update-bitmask', (data: {position: number, total: number, invert: boolean, elementName: string }, evt: Event) => {
|
|
136
|
+
const targetRef = evt.target; // clicked element
|
|
137
|
+
const elementRef = FormEngine.formElement[data.elementName]; // (hidden) element holding value
|
|
138
|
+
const active = (targetRef as HTMLInputElement).checked !== data.invert; // `xor` either checked or inverted
|
|
139
|
+
const mask = Math.pow(2, data.position);
|
|
140
|
+
const unmask = Math.pow(2, data.total) - mask - 1;
|
|
141
|
+
elementRef.value = active ? (elementRef.value | mask) : (elementRef.value & unmask);
|
|
142
|
+
elementRef.dispatchEvent(new Event('change', { bubbles: true, cancelable: true }));
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* @exports @typo3/backend/form-engine
|
|
147
|
+
*/
|
|
148
|
+
const FormEngine: FormEngineType = {
|
|
149
|
+
consumeTypes: ['typo3.setUrl', 'typo3.beforeSetUrl', 'typo3.refresh'],
|
|
150
|
+
Validation: FormEngineValidation,
|
|
151
|
+
interactionRequestMap: InteractionRequestMap,
|
|
152
|
+
formName: TYPO3.settings.FormEngine.formName,
|
|
153
|
+
formElement: undefined,
|
|
154
|
+
openedPopupWindow: null,
|
|
155
|
+
browserUrl: '',
|
|
156
|
+
doSaveFieldName: ''
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
Object.defineProperty(
|
|
160
|
+
FormEngine,
|
|
161
|
+
'formElement',
|
|
162
|
+
{
|
|
163
|
+
get: () => document.forms.namedItem(FormEngine.formName),
|
|
164
|
+
enumerable: true,
|
|
165
|
+
configurable: false,
|
|
166
|
+
}
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Opens a popup window with the element browser (browser.php)
|
|
171
|
+
*
|
|
172
|
+
* @param {string} mode can be "db" or "file"
|
|
173
|
+
* @param {string} params additional params for the browser window
|
|
174
|
+
* @param {string} entryPoint the entry point, which should be expanded by default
|
|
175
|
+
*/
|
|
176
|
+
FormEngine.openPopupWindow = function(mode: string, params: string, entryPoint: string): ModalElement {
|
|
177
|
+
const queryParams: {mode: string, bparams: string, expandPage?: string, expandFolder?: string} = {
|
|
178
|
+
mode: mode,
|
|
179
|
+
bparams: params
|
|
180
|
+
};
|
|
181
|
+
if (entryPoint) {
|
|
182
|
+
if (mode === 'db') {
|
|
183
|
+
queryParams.expandPage = entryPoint;
|
|
184
|
+
} else {
|
|
185
|
+
queryParams.expandFolder = entryPoint;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return Modal.advanced({
|
|
189
|
+
type: Modal.types.iframe,
|
|
190
|
+
content: FormEngine.browserUrl + '&' + (new URLSearchParams(queryParams)).toString(),
|
|
191
|
+
size: Modal.sizes.large
|
|
192
|
+
});
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* properly fills the select field from the popup window (element browser, link browser)
|
|
197
|
+
* or from a multi-select (two selects side-by-side)
|
|
198
|
+
* previously known as "setFormValueFromBrowseWin"
|
|
199
|
+
*
|
|
200
|
+
* @param {string} fieldName Formerly known as "fsetFormValueFromBrowseWinName" name of the field, like [tt_content][2387][header]
|
|
201
|
+
* @param {string|number} value The value to fill in (could be an integer)
|
|
202
|
+
* @param {string} label The visible name in the selector
|
|
203
|
+
* @param {string} title The title when hovering over it
|
|
204
|
+
* @param {string} exclusiveValues If the select field has exclusive options that are not combine-able
|
|
205
|
+
* @param {HTMLOptionElement} optionEl The HTMLOptionElement object of the selected <option> tag
|
|
206
|
+
*/
|
|
207
|
+
FormEngine.setSelectOptionFromExternalSource = function(
|
|
208
|
+
fieldName: string,
|
|
209
|
+
value: string,
|
|
210
|
+
label: string,
|
|
211
|
+
title: string,
|
|
212
|
+
exclusiveValues: string[] = [],
|
|
213
|
+
optionEl: HTMLOptionElement = undefined,
|
|
214
|
+
): void {
|
|
215
|
+
let
|
|
216
|
+
fieldEl,
|
|
217
|
+
$fieldEl,
|
|
218
|
+
isMultiple = false,
|
|
219
|
+
isList = false;
|
|
220
|
+
|
|
221
|
+
$fieldEl = FormEngine.getFieldElement(fieldName);
|
|
222
|
+
fieldEl = $fieldEl.get(0);
|
|
223
|
+
const originalFieldEl = $fieldEl.get(0);
|
|
224
|
+
|
|
225
|
+
if (originalFieldEl === null || value === '--div--' || originalFieldEl instanceof HTMLOptGroupElement) {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Check if the form object has a "_list" element
|
|
230
|
+
// The "_list" element exists for multiple selection select types
|
|
231
|
+
const $listFieldEl = FormEngine.getFieldElement(fieldName, '_list', true);
|
|
232
|
+
if ($listFieldEl.length > 0) {
|
|
233
|
+
$fieldEl = $listFieldEl;
|
|
234
|
+
fieldEl = $fieldEl.get(0);
|
|
235
|
+
|
|
236
|
+
isMultiple = ($fieldEl.prop('multiple') && $fieldEl.prop('size') != '1');
|
|
237
|
+
isList = true;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (isMultiple || isList) {
|
|
241
|
+
const $availableFieldEl = FormEngine.getFieldElement(fieldName, '_avail');
|
|
242
|
+
const availableFieldEl = $availableFieldEl.get(0);
|
|
243
|
+
|
|
244
|
+
// If multiple values are not allowed, clear anything that is in the control already
|
|
245
|
+
if (!isMultiple) {
|
|
246
|
+
for (const el of fieldEl.querySelectorAll('option') as NodeListOf<HTMLOptionElement>) {
|
|
247
|
+
const $option = $availableFieldEl.find(selector`option[value="${$(el).attr('value')}"]`);
|
|
248
|
+
if ($option.length) {
|
|
249
|
+
$option.removeClass('hidden').prop('disabled', false);
|
|
250
|
+
FormEngine.enableOptGroup($option.get(0));
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
$fieldEl.empty();
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Clear elements if exclusive values are found
|
|
257
|
+
if (exclusiveValues.length > 0) {
|
|
258
|
+
let reenableOptions = false;
|
|
259
|
+
|
|
260
|
+
// the new value is exclusive => remove all existing values
|
|
261
|
+
if (exclusiveValues.includes(value)) {
|
|
262
|
+
$fieldEl.empty();
|
|
263
|
+
reenableOptions = true;
|
|
264
|
+
} else if ($fieldEl.find('option').length == 1) {
|
|
265
|
+
// there is an old value, and it was exclusive => it has to be removed
|
|
266
|
+
if (exclusiveValues.includes($fieldEl.find('option').prop('value'))) {
|
|
267
|
+
$fieldEl.empty();
|
|
268
|
+
reenableOptions = true;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (reenableOptions && typeof optionEl !== 'undefined') {
|
|
273
|
+
optionEl.closest('select').querySelectorAll('[disabled]').forEach(function (disabledOption: HTMLOptionElement) {
|
|
274
|
+
disabledOption.classList.remove('hidden');
|
|
275
|
+
disabledOption.disabled = false;
|
|
276
|
+
FormEngine.enableOptGroup(disabledOption);
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Inserting the new element
|
|
282
|
+
let addNewValue = true;
|
|
283
|
+
|
|
284
|
+
// check if there is a "_mul" field (a field on the right) and if the field was already added
|
|
285
|
+
const $multipleFieldEl = FormEngine.getFieldElement(fieldName, '_mul', true);
|
|
286
|
+
if ($multipleFieldEl.length == 0 || $multipleFieldEl.val() == 0) {
|
|
287
|
+
for (const optionEl of fieldEl.querySelectorAll('option') as NodeListOf<HTMLOptionElement>) {
|
|
288
|
+
if (optionEl.value == value) {
|
|
289
|
+
addNewValue = false;
|
|
290
|
+
break;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (addNewValue && typeof optionEl !== 'undefined') {
|
|
295
|
+
optionEl.classList.add('hidden');
|
|
296
|
+
optionEl.disabled = true;
|
|
297
|
+
// In case the disabled option was the last active option and is in an optGroup, also disable the optGroup
|
|
298
|
+
const optGroup = <HTMLOptGroupElement>optionEl.parentElement;
|
|
299
|
+
if (optGroup instanceof HTMLOptGroupElement
|
|
300
|
+
&& optGroup.querySelectorAll('option:not([disabled]):not([hidden]):not(.hidden)').length === 0
|
|
301
|
+
) {
|
|
302
|
+
optGroup.disabled = true;
|
|
303
|
+
optGroup.classList.add('hidden');
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// element can be added
|
|
309
|
+
if (addNewValue) {
|
|
310
|
+
// finally add the option
|
|
311
|
+
const $option = $('<option></option>');
|
|
312
|
+
$option.attr({ value: value, title: title }).text(label);
|
|
313
|
+
$option.appendTo($fieldEl);
|
|
314
|
+
|
|
315
|
+
// set the hidden field
|
|
316
|
+
FormEngine.updateHiddenFieldValueFromSelect(fieldEl, originalFieldEl);
|
|
317
|
+
|
|
318
|
+
FormEngine.Validation.markFieldAsChanged(originalFieldEl);
|
|
319
|
+
FormEngine.Validation.validateField(fieldEl);
|
|
320
|
+
FormEngine.Validation.validateField(availableFieldEl);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
} else {
|
|
324
|
+
|
|
325
|
+
// The incoming value consists of the table name, an underscore and the uid
|
|
326
|
+
// or just the uid
|
|
327
|
+
// For a single selection field we need only the uid, so we extract it
|
|
328
|
+
const pattern = /_(\d+)$/
|
|
329
|
+
, result = value.toString().match(pattern);
|
|
330
|
+
|
|
331
|
+
if (result != null) {
|
|
332
|
+
value = result[1];
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Change the selected value
|
|
336
|
+
$fieldEl.val(value);
|
|
337
|
+
FormEngine.Validation.validateField(fieldEl);
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* sets the value of the hidden field, from the select list, always executed after the select field was updated
|
|
343
|
+
* previously known as global function setHiddenFromList()
|
|
344
|
+
*
|
|
345
|
+
* @param {HTMLElement} selectFieldEl the select field
|
|
346
|
+
* @param {HTMLElement} originalFieldEl the hidden form field
|
|
347
|
+
*/
|
|
348
|
+
FormEngine.updateHiddenFieldValueFromSelect = function(selectFieldEl: HTMLSelectElement, originalFieldEl: HTMLSelectElement): void {
|
|
349
|
+
const selectedValues: string[] = Array.from(selectFieldEl.options).map((el: HTMLOptionElement) => el.value);
|
|
350
|
+
|
|
351
|
+
// make a comma separated list, if it is a multi-select
|
|
352
|
+
// set the values to the final hidden field
|
|
353
|
+
originalFieldEl.value = selectedValues.join(',');
|
|
354
|
+
originalFieldEl.dispatchEvent(new Event('change', { bubbles: true, cancelable: true }));
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* returns a jQuery object for the given form name of the current form,
|
|
359
|
+
* if the parameter "fieldName" is given, then the form element is only returned if the field name is available
|
|
360
|
+
* the latter behaviour mirrors the one of the function "setFormValue_getFObj"
|
|
361
|
+
*
|
|
362
|
+
* @param {String} fieldName the field name to check for, optional
|
|
363
|
+
* @returns {*|HTMLElement}
|
|
364
|
+
*/
|
|
365
|
+
FormEngine.getFormElement = function(fieldName: string): JQuery|HTMLElement|void {
|
|
366
|
+
const $formEl = $(selector`form[name="${FormEngine.formName}"]:first`);
|
|
367
|
+
if (fieldName) {
|
|
368
|
+
const $fieldEl = FormEngine.getFieldElement(fieldName)
|
|
369
|
+
, $listFieldEl = FormEngine.getFieldElement(fieldName, '_list');
|
|
370
|
+
|
|
371
|
+
// Take the form object if it is either of type select-one or of type-multiple and it has a "_list" element
|
|
372
|
+
if ($fieldEl.length > 0 &&
|
|
373
|
+
(
|
|
374
|
+
($fieldEl.prop('type') === 'select-one') ||
|
|
375
|
+
($listFieldEl.length > 0 && $listFieldEl.prop('type').match(/select-(one|multiple)/))
|
|
376
|
+
)
|
|
377
|
+
) {
|
|
378
|
+
return $formEl;
|
|
379
|
+
} else {
|
|
380
|
+
console.error('Form fields missing: form: ' + FormEngine.formName + ', field name: ' + fieldName);
|
|
381
|
+
alert('Form field is invalid');
|
|
382
|
+
}
|
|
383
|
+
} else {
|
|
384
|
+
return $formEl;
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Returns a jQuery object of the field DOM element of the current form, can also be used to
|
|
391
|
+
* request an alternative field like "_list", "_avail" or "_mul"
|
|
392
|
+
*
|
|
393
|
+
* @param {String} fieldName the name of the field (<input name="fieldName">)
|
|
394
|
+
* @param {String} appendix optional
|
|
395
|
+
* @param {Boolean} noFallback if set, then the appendix value is returned no matter if it exists or not
|
|
396
|
+
*/
|
|
397
|
+
FormEngine.getFieldElement = function(fieldName: string, appendix: string, noFallback: boolean): JQuery {
|
|
398
|
+
// if an appendix is set, return the field with the appendix (like _mul or _list)
|
|
399
|
+
if (appendix) {
|
|
400
|
+
let $fieldEl;
|
|
401
|
+
switch (appendix) {
|
|
402
|
+
case '_list':
|
|
403
|
+
$fieldEl = $(selector`:input[data-formengine-input-name="${fieldName}"]:not([type=hidden])`, FormEngine.formElement);
|
|
404
|
+
break;
|
|
405
|
+
case '_avail':
|
|
406
|
+
$fieldEl = $(selector`:input[data-relatedfieldname="${fieldName}"]`, FormEngine.formElement);
|
|
407
|
+
break;
|
|
408
|
+
case '_mul':
|
|
409
|
+
$fieldEl = $(selector`:input[type=hidden][data-formengine-input-name="${fieldName}"]`, FormEngine.formElement);
|
|
410
|
+
break;
|
|
411
|
+
default:
|
|
412
|
+
$fieldEl = null;
|
|
413
|
+
break;
|
|
414
|
+
}
|
|
415
|
+
if (($fieldEl && $fieldEl.length > 0) || noFallback === true) {
|
|
416
|
+
return $fieldEl;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return $(FormEngine.formElement.elements.namedItem(fieldName));
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Initialize events for all form engine relevant tasks.
|
|
425
|
+
* This function only needs to be called once on page load,
|
|
426
|
+
* as it using deferrer methods only
|
|
427
|
+
*/
|
|
428
|
+
FormEngine.initializeEvents = function() {
|
|
429
|
+
if (top.TYPO3 && typeof top.TYPO3.Backend !== 'undefined') {
|
|
430
|
+
top.TYPO3.Backend.consumerScope.attach(FormEngine);
|
|
431
|
+
window.addEventListener('pagehide', () => top.TYPO3.Backend.consumerScope.detach(FormEngine), { once: true });
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
new RegularEvent('click', (e: Event): void => {
|
|
435
|
+
e.preventDefault();
|
|
436
|
+
FormEngine.preventExitIfNotSaved(
|
|
437
|
+
FormEngine.preventExitIfNotSavedCallback
|
|
438
|
+
);
|
|
439
|
+
}).delegateTo(document, '.t3js-editform-close');
|
|
440
|
+
|
|
441
|
+
new RegularEvent('click', (e: Event): void => {
|
|
442
|
+
e.preventDefault();
|
|
443
|
+
FormEngine.previewAction(e, FormEngine.previewActionCallback);
|
|
444
|
+
}).delegateTo(document, '.t3js-editform-view');
|
|
445
|
+
|
|
446
|
+
new RegularEvent('click', (e: Event): void => {
|
|
447
|
+
e.preventDefault();
|
|
448
|
+
FormEngine.newAction(e, FormEngine.newActionCallback);
|
|
449
|
+
}).delegateTo(document, '.t3js-editform-new');
|
|
450
|
+
|
|
451
|
+
new RegularEvent('click', (e: Event): void => {
|
|
452
|
+
e.preventDefault();
|
|
453
|
+
FormEngine.duplicateAction(e, FormEngine.duplicateActionCallback);
|
|
454
|
+
}).delegateTo(document, '.t3js-editform-duplicate');
|
|
455
|
+
|
|
456
|
+
new RegularEvent('click', (e: Event): void => {
|
|
457
|
+
e.preventDefault();
|
|
458
|
+
FormEngine.deleteAction(e, FormEngine.deleteActionCallback);
|
|
459
|
+
}).delegateTo(document, '.t3js-editform-delete-record');
|
|
460
|
+
|
|
461
|
+
new RegularEvent('change', (event: Event, target: HTMLInputElement): void => {
|
|
462
|
+
target.closest('.t3js-formengine-field-item').classList.toggle('disabled');
|
|
463
|
+
}).delegateTo(document, '.t3-form-field-eval-null-checkbox input[type="checkbox"]');
|
|
464
|
+
|
|
465
|
+
new RegularEvent('change', (event: Event, target: HTMLInputElement): void => {
|
|
466
|
+
FormEngine.toggleCheckboxField(target);
|
|
467
|
+
FormEngine.Validation.markFieldAsChanged(target);
|
|
468
|
+
}).delegateTo(document, '.t3js-form-field-eval-null-placeholder-checkbox input[type="checkbox"]');
|
|
469
|
+
|
|
470
|
+
new RegularEvent('click', (e: Event, target: HTMLElement): void => {
|
|
471
|
+
e.preventDefault();
|
|
472
|
+
e.stopPropagation();
|
|
473
|
+
|
|
474
|
+
const mode = target.dataset.mode;
|
|
475
|
+
const params = target.dataset.params;
|
|
476
|
+
const entryPoint = target.dataset.entryPoint;
|
|
477
|
+
|
|
478
|
+
FormEngine.openPopupWindow(mode, params, entryPoint);
|
|
479
|
+
}).delegateTo(document, '.t3js-element-browser');
|
|
480
|
+
|
|
481
|
+
new RegularEvent('click', (evt: Event, target: HTMLElement): void => {
|
|
482
|
+
const items = JSON.parse(target.dataset.formengineFieldChangeItems);
|
|
483
|
+
FormEngine.processOnFieldChange(items, evt);
|
|
484
|
+
}).delegateTo(document, '[data-formengine-field-change-event="click"]');
|
|
485
|
+
|
|
486
|
+
new RegularEvent('change', (evt: Event, target: HTMLElement): void => {
|
|
487
|
+
const items = JSON.parse(target.dataset.formengineFieldChangeItems);
|
|
488
|
+
FormEngine.processOnFieldChange(items, evt);
|
|
489
|
+
}).delegateTo(document, '[data-formengine-field-change-event="change"]');
|
|
490
|
+
|
|
491
|
+
FormEngine.formElement.addEventListener('submit', function (e: SubmitEvent) {
|
|
492
|
+
const form = e.target as HTMLFormElement;
|
|
493
|
+
if (form.closeDoc?.value !== '0') {
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
if (e.submitter !== null && (e.submitter.tagName === 'A' || e.submitter.hasAttribute('form')) && !e.defaultPrevented) {
|
|
498
|
+
const saveField = form.querySelector(selector`input[name="${FormEngine.doSaveFieldName}"]`) as HTMLInputElement|null;
|
|
499
|
+
if (saveField !== null) {
|
|
500
|
+
saveField.value = '1';
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
window.addEventListener('message', FormEngine.handlePostMessage);
|
|
506
|
+
};
|
|
507
|
+
|
|
508
|
+
FormEngine.consume = function(interactionRequest: TriggerRequest): Promise<void> {
|
|
509
|
+
if (!interactionRequest) {
|
|
510
|
+
// @todo better return a rejected promise
|
|
511
|
+
throw new BackendExceptionModule.BackendException('No interaction request given', 1496589980);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
let promiseControls: PromiseControls<void>;
|
|
515
|
+
const promise = new Promise<void>((resolve, reject) => {
|
|
516
|
+
promiseControls = { resolve, reject };
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
if (interactionRequest.concernsTypes(FormEngine.consumeTypes)) {
|
|
520
|
+
const outerMostRequest = interactionRequest.outerMostRequest;
|
|
521
|
+
|
|
522
|
+
FormEngine.interactionRequestMap.attachFor(
|
|
523
|
+
outerMostRequest,
|
|
524
|
+
promiseControls
|
|
525
|
+
);
|
|
526
|
+
// resolve or reject with previous user choice
|
|
527
|
+
if (outerMostRequest.isProcessed()) {
|
|
528
|
+
handleConsumeResponse(
|
|
529
|
+
outerMostRequest,
|
|
530
|
+
outerMostRequest.getProcessedData().response
|
|
531
|
+
);
|
|
532
|
+
// show confirmation dialog
|
|
533
|
+
} else if (FormEngine.hasChange() || FormEngine.isNew()) {
|
|
534
|
+
FormEngine.preventExitIfNotSaved(function(response: boolean) {
|
|
535
|
+
outerMostRequest.setProcessedData(
|
|
536
|
+
{ response: response }
|
|
537
|
+
);
|
|
538
|
+
handleConsumeResponse(outerMostRequest, response);
|
|
539
|
+
});
|
|
540
|
+
// resolve directly
|
|
541
|
+
} else {
|
|
542
|
+
FormEngine.interactionRequestMap.resolveFor(outerMostRequest);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
return promise;
|
|
547
|
+
};
|
|
548
|
+
|
|
549
|
+
FormEngine.handlePostMessage = function (e: MessageEvent) {
|
|
550
|
+
if (!MessageUtility.MessageUtility.verifyOrigin(e.origin)) {
|
|
551
|
+
throw 'Denied message sent by ' + e.origin;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
if (e.data.actionName === 'typo3:elementBrowser:elementAdded') {
|
|
555
|
+
if (typeof e.data.fieldName === 'undefined') {
|
|
556
|
+
throw 'fieldName not defined in message';
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
if (typeof e.data.value === 'undefined') {
|
|
560
|
+
throw 'value not defined in message';
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
const label = e.data.label || e.data.value;
|
|
564
|
+
const title = e.data.title || label;
|
|
565
|
+
const exclusiveValues = Utility.trimExplode(',', e.data?.exclusiveValues ?? '');
|
|
566
|
+
|
|
567
|
+
FormEngine.setSelectOptionFromExternalSource(e.data.fieldName, e.data.value, label, title, exclusiveValues);
|
|
568
|
+
}
|
|
569
|
+
};
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* Initializes the remaining character views based on the fields' maxlength attribute
|
|
573
|
+
*/
|
|
574
|
+
FormEngine.initializeRemainingCharacterViews = function() {
|
|
575
|
+
// all fields with a "maxlength" attribute
|
|
576
|
+
const elementsWithMaxLengths: NodeListOf<HTMLInputElement|HTMLTextAreaElement> = document.querySelectorAll('[maxlength]:not([data-input-type="datetimepicker"]):not(.t3js-color-picker)');
|
|
577
|
+
elementsWithMaxLengths.forEach((element): void => {
|
|
578
|
+
const fieldItem = element.closest('.t3js-formengine-field-item');
|
|
579
|
+
if (fieldItem !== null && fieldItem.querySelector('typo3-backend-formengine-char-counter') === null) {
|
|
580
|
+
const charCounterElement = document.createElement('typo3-backend-formengine-char-counter');
|
|
581
|
+
charCounterElement.setAttribute('target', `[data-formengine-input-name="${selector`${element.dataset.formengineInputName}`}"]`);
|
|
582
|
+
fieldItem.append(charCounterElement);
|
|
583
|
+
}
|
|
584
|
+
});
|
|
585
|
+
};
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* Initializes the left character count needed to reach the minimum value based on the field's minlength attribute
|
|
589
|
+
*/
|
|
590
|
+
FormEngine.initializeMinimumCharactersLeftViews = function () {
|
|
591
|
+
const addOrUpdateCounter = (minCharacterCountLeft: string, event: Event) => {
|
|
592
|
+
const parent = (event.currentTarget as HTMLInputElement).closest('.t3js-formengine-field-item');
|
|
593
|
+
const counter = parent.querySelector('.t3js-charcounter-min');
|
|
594
|
+
const labelValue = TYPO3.lang['FormEngine.minCharactersLeft'].replace('{0}', minCharacterCountLeft);
|
|
595
|
+
if (counter) {
|
|
596
|
+
counter.querySelector('span').innerHTML = labelValue;
|
|
597
|
+
} else {
|
|
598
|
+
const counter = document.createElement('div');
|
|
599
|
+
counter.classList.add('t3js-charcounter-min');
|
|
600
|
+
const label = document.createElement('span');
|
|
601
|
+
label.classList.add('badge', 'badge-danger');
|
|
602
|
+
label.innerHTML = labelValue;
|
|
603
|
+
counter.append(label);
|
|
604
|
+
let wrapper = parent.querySelector('.t3js-charcounter-wrapper');
|
|
605
|
+
if (!wrapper) {
|
|
606
|
+
wrapper = document.createElement('div');
|
|
607
|
+
wrapper.classList.add('t3js-charcounter-wrapper');
|
|
608
|
+
parent.append(wrapper);
|
|
609
|
+
}
|
|
610
|
+
wrapper.prepend(counter);
|
|
611
|
+
}
|
|
612
|
+
};
|
|
613
|
+
const removeCounter = (event: Event) => {
|
|
614
|
+
const parent = (event.currentTarget as HTMLInputElement).closest('.t3js-formengine-field-item');
|
|
615
|
+
const counter = parent.querySelector('.t3js-charcounter-min');
|
|
616
|
+
if (counter) {
|
|
617
|
+
counter.remove();
|
|
618
|
+
}
|
|
619
|
+
};
|
|
620
|
+
|
|
621
|
+
const minlengthElements = document.querySelectorAll('[minlength]:not([data-input-type="datetimepicker"]):not(.t3js-charcounter-min-initialized)');
|
|
622
|
+
minlengthElements.forEach((field: HTMLInputElement|HTMLTextAreaElement) => {
|
|
623
|
+
field.addEventListener('focus', (event) => {
|
|
624
|
+
const minCharacterCountLeft = FormEngine.getMinCharacterLeftCount(field);
|
|
625
|
+
if (minCharacterCountLeft > 0) {
|
|
626
|
+
addOrUpdateCounter(minCharacterCountLeft, event);
|
|
627
|
+
}
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
field.addEventListener('blur', removeCounter);
|
|
631
|
+
|
|
632
|
+
field.addEventListener('keyup', (event) => {
|
|
633
|
+
const minCharacterCountLeft = FormEngine.getMinCharacterLeftCount(field);
|
|
634
|
+
if (minCharacterCountLeft > 0) {
|
|
635
|
+
addOrUpdateCounter(minCharacterCountLeft, event);
|
|
636
|
+
} else {
|
|
637
|
+
removeCounter(event);
|
|
638
|
+
}
|
|
639
|
+
});
|
|
640
|
+
});
|
|
641
|
+
};
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* Get the properties required for proper rendering of the character counter
|
|
645
|
+
*
|
|
646
|
+
* @param {HTMLElement} field
|
|
647
|
+
* @returns number
|
|
648
|
+
*/
|
|
649
|
+
FormEngine.getMinCharacterLeftCount = function (field: HTMLInputElement|HTMLTextAreaElement) {
|
|
650
|
+
const text = field.value;
|
|
651
|
+
const minlength = field.minLength;
|
|
652
|
+
const currentFieldLength = text.length;
|
|
653
|
+
|
|
654
|
+
// minLength doesn't care about empty fields.
|
|
655
|
+
if (currentFieldLength === 0) {
|
|
656
|
+
return 0;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
const numberOfLineBreaks = (text.match(/\n/g) || []).length; // count line breaks
|
|
660
|
+
const minimumCharactersLeft = minlength - currentFieldLength - numberOfLineBreaks;
|
|
661
|
+
|
|
662
|
+
return minimumCharactersLeft;
|
|
663
|
+
};
|
|
664
|
+
|
|
665
|
+
/**
|
|
666
|
+
* Initialize input / text field "null" checkbox CSS overlay if no placeholder is set.
|
|
667
|
+
*/
|
|
668
|
+
FormEngine.initializeNullNoPlaceholderCheckboxes = function(): void {
|
|
669
|
+
document.querySelectorAll('.t3-form-field-eval-null-checkbox').forEach((el: HTMLElement) => {
|
|
670
|
+
// Add disabled class to "t3js-formengine-field-item" if the null checkbox is NOT set,
|
|
671
|
+
// This activates a CSS overlay "disabling" the input field and everything around.
|
|
672
|
+
const checkbox = el.querySelector('input[type="checkbox"]') as HTMLInputElement;
|
|
673
|
+
const fieldItem = el.closest('.t3js-formengine-field-item');
|
|
674
|
+
if (!checkbox.checked) {
|
|
675
|
+
fieldItem.classList.add('disabled');
|
|
676
|
+
}
|
|
677
|
+
});
|
|
678
|
+
};
|
|
679
|
+
|
|
680
|
+
/**
|
|
681
|
+
* Initialize input / text field "null" checkbox placeholder / real field if placeholder is set.
|
|
682
|
+
*/
|
|
683
|
+
FormEngine.initializeNullWithPlaceholderCheckboxes = function(): void {
|
|
684
|
+
document.querySelectorAll('.t3js-form-field-eval-null-placeholder-checkbox').forEach((el: HTMLElement) => {
|
|
685
|
+
FormEngine.toggleCheckboxField(el.querySelector('input[type="checkbox"]'), false);
|
|
686
|
+
});
|
|
687
|
+
};
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* Set initial state of both div's (one containing actual field, other containing placeholder field)
|
|
691
|
+
* depending on whether checkbox is checked or not
|
|
692
|
+
*/
|
|
693
|
+
FormEngine.toggleCheckboxField = function(checkbox: HTMLInputElement, triggerFocusWhenChecked: boolean = true): void {
|
|
694
|
+
const item = checkbox.closest('.t3js-formengine-field-item');
|
|
695
|
+
const placeholder = item.querySelector('.t3js-formengine-placeholder-placeholder') as HTMLElement;
|
|
696
|
+
const formFieldWrapper = item.querySelector('.t3js-formengine-placeholder-formfield') as HTMLElement;
|
|
697
|
+
|
|
698
|
+
if (checkbox.checked) {
|
|
699
|
+
placeholder.hidden = true;
|
|
700
|
+
formFieldWrapper.hidden = false;
|
|
701
|
+
if (triggerFocusWhenChecked) {
|
|
702
|
+
(formFieldWrapper.querySelector('input,select,textarea') as HTMLInputElement|HTMLSelectElement|HTMLTextAreaElement)?.focus();
|
|
703
|
+
}
|
|
704
|
+
} else {
|
|
705
|
+
placeholder.hidden = false;
|
|
706
|
+
formFieldWrapper.hidden = true;
|
|
707
|
+
}
|
|
708
|
+
};
|
|
709
|
+
|
|
710
|
+
/**
|
|
711
|
+
* This is the main function that is called on page load, but also after elements are asynchronously
|
|
712
|
+
* called e.g. after inline elements are loaded, or a new flexform section is added.
|
|
713
|
+
* Use this function in your extension like this "TYPO3.FormEngine.initialize()"
|
|
714
|
+
* if you add new fields dynamically.
|
|
715
|
+
*/
|
|
716
|
+
FormEngine.reinitialize = function(): void {
|
|
717
|
+
// Apply "close" button to all input / datetime fields
|
|
718
|
+
const clearables = document.querySelectorAll('.t3js-clearable') as NodeListOf<HTMLInputElement>;
|
|
719
|
+
if (clearables.length > 0) {
|
|
720
|
+
import('@typo3/backend/input/clearable').then(function() {
|
|
721
|
+
clearables.forEach(clearableField => clearableField.clearable());
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
FormEngine.initializeNullNoPlaceholderCheckboxes();
|
|
726
|
+
FormEngine.initializeNullWithPlaceholderCheckboxes();
|
|
727
|
+
FormEngine.initializeLocalizationStateSelector();
|
|
728
|
+
FormEngine.initializeMinimumCharactersLeftViews();
|
|
729
|
+
FormEngine.initializeRemainingCharacterViews();
|
|
730
|
+
};
|
|
731
|
+
|
|
732
|
+
/**
|
|
733
|
+
* Disable the input field on load if localization state selector is set to "parent" or "source"
|
|
734
|
+
*/
|
|
735
|
+
FormEngine.initializeLocalizationStateSelector = function(): void {
|
|
736
|
+
document.querySelectorAll('.t3js-l10n-state-container').forEach((el: HTMLElement) => {
|
|
737
|
+
const input = el.closest('.t3js-formengine-field-item')?.querySelector('[data-formengine-input-name]') as HTMLInputElement|null;
|
|
738
|
+
if (input === undefined || input === null) {
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
const currentState = (el.querySelector('input[type="radio"]:checked') as HTMLInputElement)?.value;
|
|
742
|
+
if (currentState === undefined) {
|
|
743
|
+
console.warn('The localization state of the field ' + input.dataset.formengineInputName + ' cannot be determined. This smells like a DataHandler bug.');
|
|
744
|
+
}
|
|
745
|
+
if (currentState === 'parent' || currentState === 'source') {
|
|
746
|
+
input.disabled = true;
|
|
747
|
+
}
|
|
748
|
+
});
|
|
749
|
+
};
|
|
750
|
+
|
|
751
|
+
/**
|
|
752
|
+
* @return {boolean}
|
|
753
|
+
*/
|
|
754
|
+
FormEngine.hasChange = function(): boolean {
|
|
755
|
+
|
|
756
|
+
const formElementChanges = $(selector`form[name="${FormEngine.formName}"] .has-change`).length > 0,
|
|
757
|
+
inlineRecordChanges = $('[name^="data["].has-change').length > 0;
|
|
758
|
+
return formElementChanges || inlineRecordChanges;
|
|
759
|
+
};
|
|
760
|
+
|
|
761
|
+
/**
|
|
762
|
+
* @return {boolean}
|
|
763
|
+
*/
|
|
764
|
+
FormEngine.isNew = function(): boolean {
|
|
765
|
+
return (document.querySelector('form[name="' + FormEngine.formName + '"] .typo3-TCEforms.is-new') !== null);
|
|
766
|
+
};
|
|
767
|
+
|
|
768
|
+
/**
|
|
769
|
+
* @param {boolean} response
|
|
770
|
+
*/
|
|
771
|
+
FormEngine.preventExitIfNotSavedCallback = (): void => {
|
|
772
|
+
FormEngine.closeDocument();
|
|
773
|
+
};
|
|
774
|
+
|
|
775
|
+
/**
|
|
776
|
+
* Show modal to confirm following a clicked link to confirm leaving the document without saving
|
|
777
|
+
*
|
|
778
|
+
* @param {String} href
|
|
779
|
+
* @returns {Boolean}
|
|
780
|
+
*/
|
|
781
|
+
FormEngine.preventFollowLinkIfNotSaved = function(href: string): boolean {
|
|
782
|
+
FormEngine.preventExitIfNotSaved(
|
|
783
|
+
function () {
|
|
784
|
+
window.location.href = href;
|
|
785
|
+
}
|
|
786
|
+
);
|
|
787
|
+
return false;
|
|
788
|
+
};
|
|
789
|
+
|
|
790
|
+
/**
|
|
791
|
+
* Show modal to confirm closing the document without saving.
|
|
792
|
+
*
|
|
793
|
+
* @param {Function} callback
|
|
794
|
+
*/
|
|
795
|
+
FormEngine.preventExitIfNotSaved = function(callback: (response: boolean) => void): void {
|
|
796
|
+
callback = callback || FormEngine.preventExitIfNotSavedCallback;
|
|
797
|
+
|
|
798
|
+
if (FormEngine.hasChange() || FormEngine.isNew()) {
|
|
799
|
+
const title = TYPO3.lang['label.confirm.close_without_save.title'] || 'Do you want to close without saving?';
|
|
800
|
+
const content = TYPO3.lang['label.confirm.close_without_save.content'] || 'You currently have unsaved changes. Are you sure you want to discard these changes?';
|
|
801
|
+
const buttons: Array<{text: string, btnClass: string, name: string, active?: boolean}> = [
|
|
802
|
+
{
|
|
803
|
+
text: TYPO3.lang['buttons.confirm.close_without_save.no'] || 'No, I will continue editing',
|
|
804
|
+
btnClass: 'btn-default',
|
|
805
|
+
name: 'no'
|
|
806
|
+
},
|
|
807
|
+
{
|
|
808
|
+
text: TYPO3.lang['buttons.confirm.close_without_save.yes'] || 'Yes, discard my changes',
|
|
809
|
+
btnClass: 'btn-default',
|
|
810
|
+
name: 'yes'
|
|
811
|
+
}
|
|
812
|
+
];
|
|
813
|
+
if ($('.has-error').length === 0) {
|
|
814
|
+
buttons.push({
|
|
815
|
+
text: TYPO3.lang['buttons.confirm.save_and_close'] || 'Save and close',
|
|
816
|
+
btnClass: 'btn-primary',
|
|
817
|
+
name: 'save',
|
|
818
|
+
active: true
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
const modal = Modal.confirm(title, content, Severity.warning, buttons);
|
|
823
|
+
modal.addEventListener('button.clicked', function(e: Event) {
|
|
824
|
+
if ((e.target as HTMLButtonElement).name === 'no') {
|
|
825
|
+
modal.hideModal();
|
|
826
|
+
} else if ((e.target as HTMLButtonElement).name === 'yes') {
|
|
827
|
+
modal.hideModal();
|
|
828
|
+
callback.call(null, true);
|
|
829
|
+
} else if ((e.target as HTMLButtonElement).name === 'save') {
|
|
830
|
+
modal.hideModal();
|
|
831
|
+
FormEngine.saveAndCloseDocument();
|
|
832
|
+
}
|
|
833
|
+
});
|
|
834
|
+
} else {
|
|
835
|
+
callback.call(null, true);
|
|
836
|
+
}
|
|
837
|
+
};
|
|
838
|
+
|
|
839
|
+
/**
|
|
840
|
+
* Show modal to confirm closing the document without saving
|
|
841
|
+
*/
|
|
842
|
+
FormEngine.preventSaveIfHasErrors = function(): boolean {
|
|
843
|
+
if ($('.has-error').length > 0) {
|
|
844
|
+
const title = TYPO3.lang['label.alert.save_with_error.title'] || 'You have errors in your form!';
|
|
845
|
+
const content = TYPO3.lang['label.alert.save_with_error.content'] || 'Please check the form, there is at least one error in your form.';
|
|
846
|
+
const modal = Modal.confirm(title, content, Severity.error, [
|
|
847
|
+
{
|
|
848
|
+
text: TYPO3.lang['buttons.alert.save_with_error.ok'] || 'OK',
|
|
849
|
+
btnClass: 'btn-danger',
|
|
850
|
+
name: 'ok'
|
|
851
|
+
}
|
|
852
|
+
]);
|
|
853
|
+
modal.addEventListener('button.clicked', function(e: Event) {
|
|
854
|
+
if ((e.target as HTMLButtonElement).name === 'ok') {
|
|
855
|
+
modal.hideModal();
|
|
856
|
+
}
|
|
857
|
+
});
|
|
858
|
+
return false;
|
|
859
|
+
}
|
|
860
|
+
return true;
|
|
861
|
+
};
|
|
862
|
+
|
|
863
|
+
/**
|
|
864
|
+
* @param {OnFieldChangeItem[]} items
|
|
865
|
+
* @param {Event|null|undefined} evt
|
|
866
|
+
*/
|
|
867
|
+
FormEngine.processOnFieldChange = function(items: Array<OnFieldChangeItem>, evt?: Event|null) {
|
|
868
|
+
items.forEach((item) => {
|
|
869
|
+
const handler = onFieldChangeHandlers.get(item.name);
|
|
870
|
+
if (handler instanceof Function) {
|
|
871
|
+
handler.call(null, item.data || null, evt);
|
|
872
|
+
}
|
|
873
|
+
});
|
|
874
|
+
};
|
|
875
|
+
|
|
876
|
+
FormEngine.registerOnFieldChangeHandler = function(name: string, handler: OnChangeFieldHandlerCallback): void {
|
|
877
|
+
if (onFieldChangeHandlers.has(name)) {
|
|
878
|
+
console.warn('Handler for onFieldChange name `' + name + '` has been overridden.');
|
|
879
|
+
}
|
|
880
|
+
onFieldChangeHandlers.set(name, handler);
|
|
881
|
+
};
|
|
882
|
+
|
|
883
|
+
FormEngine.closeModalsRecursive = function() {
|
|
884
|
+
if (typeof Modal.currentModal !== 'undefined' && Modal.currentModal !== null) {
|
|
885
|
+
Modal.currentModal.addEventListener('typo3-modal-hidden', function () {
|
|
886
|
+
FormEngine.closeModalsRecursive();
|
|
887
|
+
});
|
|
888
|
+
Modal.currentModal.hideModal();
|
|
889
|
+
}
|
|
890
|
+
};
|
|
891
|
+
|
|
892
|
+
/**
|
|
893
|
+
* Disables all doc header buttons to prevent unintended operations.
|
|
894
|
+
* Useful when showing modals that require page reload (e.g., refreshRequiredConfirm)
|
|
895
|
+
* to prevent operations (e.g. save) on slow connections before the page is actually reloaded.
|
|
896
|
+
*/
|
|
897
|
+
FormEngine.disableDocHeaderButtons = function(): void {
|
|
898
|
+
const docHeaderBar = document.querySelector('.t3js-module-docheader-bar-buttons');
|
|
899
|
+
if (!docHeaderBar) {
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
// Disable all buttons and links in the doc header
|
|
904
|
+
docHeaderBar.querySelectorAll('button, a, input[type="submit"]').forEach((element: HTMLElement): void => {
|
|
905
|
+
if (element instanceof HTMLButtonElement || element instanceof HTMLInputElement) {
|
|
906
|
+
element.disabled = true;
|
|
907
|
+
} else if (element instanceof HTMLAnchorElement) {
|
|
908
|
+
// For anchor elements - use TYPO3 standard approach
|
|
909
|
+
element.classList.add('disabled');
|
|
910
|
+
element.setAttribute('aria-disabled', 'true');
|
|
911
|
+
}
|
|
912
|
+
});
|
|
913
|
+
};
|
|
914
|
+
|
|
915
|
+
/**
|
|
916
|
+
* Enables all doc header buttons.
|
|
917
|
+
* Called after FormEngine initialization is complete to restore button interactivity.
|
|
918
|
+
*/
|
|
919
|
+
FormEngine.enableDocHeaderButtons = function(): void {
|
|
920
|
+
const docHeaderBar = document.querySelector('.t3js-module-docheader-bar-buttons');
|
|
921
|
+
if (!docHeaderBar) {
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
// Enable all buttons and links in the doc header
|
|
926
|
+
docHeaderBar.querySelectorAll('button, a, input[type="submit"]').forEach((element: HTMLElement): void => {
|
|
927
|
+
if (element instanceof HTMLButtonElement || element instanceof HTMLInputElement) {
|
|
928
|
+
element.disabled = false;
|
|
929
|
+
} else if (element instanceof HTMLAnchorElement) {
|
|
930
|
+
// For anchor elements - remove TYPO3 disabled state
|
|
931
|
+
element.classList.remove('disabled');
|
|
932
|
+
element.removeAttribute('aria-disabled');
|
|
933
|
+
}
|
|
934
|
+
});
|
|
935
|
+
};
|
|
936
|
+
|
|
937
|
+
/**
|
|
938
|
+
* Preview action
|
|
939
|
+
*
|
|
940
|
+
* When there are changes:
|
|
941
|
+
* Will take action based on local storage preset
|
|
942
|
+
* If preset is not available, a modal will open
|
|
943
|
+
*
|
|
944
|
+
* @param {Event} event
|
|
945
|
+
* @param {Function} callback
|
|
946
|
+
*/
|
|
947
|
+
FormEngine.previewAction = function(event: Event, callback: PreviewActionCallback): void {
|
|
948
|
+
callback = callback || FormEngine.previewActionCallback;
|
|
949
|
+
|
|
950
|
+
const previewUrl = (event.target as HTMLAnchorElement).href;
|
|
951
|
+
const isNew = ('isNew' in (event.target as HTMLAnchorElement).dataset);
|
|
952
|
+
const $actionElement = $('<input />').attr('type', 'hidden').attr('name', '_savedokview').attr('value', '1');
|
|
953
|
+
if (FormEngine.hasChange() || FormEngine.isNew()) {
|
|
954
|
+
FormEngine.showPreviewModal(previewUrl, isNew, $actionElement, callback);
|
|
955
|
+
} else {
|
|
956
|
+
$(selector`form[name="${FormEngine.formName}"]`).append($actionElement);
|
|
957
|
+
window.open('', 'newTYPO3frontendWindow');
|
|
958
|
+
FormEngine.formElement.submit();
|
|
959
|
+
}
|
|
960
|
+
};
|
|
961
|
+
|
|
962
|
+
/**
|
|
963
|
+
* The callback for the preview action
|
|
964
|
+
*
|
|
965
|
+
* @param {string} modalButtonName
|
|
966
|
+
* @param {string} previewUrl
|
|
967
|
+
* @param {ModalElement} actionElement
|
|
968
|
+
*/
|
|
969
|
+
FormEngine.previewActionCallback = function(modalButtonName: string, previewUrl: string, actionElement: ModalElement): void {
|
|
970
|
+
Modal.dismiss();
|
|
971
|
+
switch(modalButtonName) {
|
|
972
|
+
case 'discard':
|
|
973
|
+
const previewWin = window.open(previewUrl, 'newTYPO3frontendWindow');
|
|
974
|
+
previewWin.focus();
|
|
975
|
+
|
|
976
|
+
if (Utility.urlsPointToSameServerSideResource(previewWin.location.href, previewUrl)) {
|
|
977
|
+
previewWin.location.reload();
|
|
978
|
+
}
|
|
979
|
+
break;
|
|
980
|
+
case 'save':
|
|
981
|
+
$(selector`form[name="${FormEngine.formName}"]`).append($(actionElement));
|
|
982
|
+
window.open('', 'newTYPO3frontendWindow');
|
|
983
|
+
FormEngine.saveDocument();
|
|
984
|
+
break;
|
|
985
|
+
default:
|
|
986
|
+
break;
|
|
987
|
+
}
|
|
988
|
+
};
|
|
989
|
+
|
|
990
|
+
/**
|
|
991
|
+
* Show the preview modal
|
|
992
|
+
*
|
|
993
|
+
* @param {string} previewUrl
|
|
994
|
+
* @param {bool} isNew
|
|
995
|
+
* @param {element} $actionElement
|
|
996
|
+
* @param {Function} callback
|
|
997
|
+
*/
|
|
998
|
+
FormEngine.showPreviewModal = function(previewUrl: string, isNew: boolean, $actionElement: JQuery, callback: PreviewActionCallback): void {
|
|
999
|
+
const title = TYPO3.lang['label.confirm.view_record_changed.title'] || 'Do you want to save before viewing?';
|
|
1000
|
+
const modalCancelButtonConfiguration = {
|
|
1001
|
+
text: TYPO3.lang['buttons.confirm.view_record_changed.cancel'] || 'Cancel',
|
|
1002
|
+
btnClass: 'btn-default',
|
|
1003
|
+
name: 'cancel'
|
|
1004
|
+
};
|
|
1005
|
+
const modaldismissViewButtonConfiguration = {
|
|
1006
|
+
text: TYPO3.lang['buttons.confirm.view_record_changed.no-save'] || 'View without changes',
|
|
1007
|
+
btnClass: 'btn-default',
|
|
1008
|
+
name: 'discard'
|
|
1009
|
+
};
|
|
1010
|
+
const modalsaveViewButtonConfiguration = {
|
|
1011
|
+
text: TYPO3.lang['buttons.confirm.view_record_changed.save'] || 'Save changes and view',
|
|
1012
|
+
btnClass: 'btn-primary',
|
|
1013
|
+
name: 'save',
|
|
1014
|
+
active: true
|
|
1015
|
+
};
|
|
1016
|
+
let modalButtons = [];
|
|
1017
|
+
let contentStrings = [];
|
|
1018
|
+
if (isNew) {
|
|
1019
|
+
if (!FormEngine.Validation.isValid()) {
|
|
1020
|
+
FormEngine.Validation.showErrorModal();
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
1023
|
+
modalButtons = [
|
|
1024
|
+
modalCancelButtonConfiguration,
|
|
1025
|
+
modalsaveViewButtonConfiguration
|
|
1026
|
+
];
|
|
1027
|
+
contentStrings = [
|
|
1028
|
+
TYPO3.lang['label.confirm.view_record_changed.content.is-new-page']
|
|
1029
|
+
|| 'You need to save your changes before viewing the page. Do you want to save and view them now?'
|
|
1030
|
+
];
|
|
1031
|
+
} else {
|
|
1032
|
+
modalButtons = [
|
|
1033
|
+
modalCancelButtonConfiguration,
|
|
1034
|
+
modaldismissViewButtonConfiguration,
|
|
1035
|
+
];
|
|
1036
|
+
contentStrings = [
|
|
1037
|
+
TYPO3.lang['label.confirm.view_record_changed.content']
|
|
1038
|
+
|| 'You currently have unsaved changes. You can either discard these changes or save and view them.'
|
|
1039
|
+
];
|
|
1040
|
+
if (FormEngine.Validation.isValid()) {
|
|
1041
|
+
modalButtons.push(modalsaveViewButtonConfiguration);
|
|
1042
|
+
} else {
|
|
1043
|
+
contentStrings.push(
|
|
1044
|
+
TYPO3.lang['label.confirm.view_record_changed.invalid_form']
|
|
1045
|
+
|| 'The form appears to be invalid, therefore "Save changes and view" is not available.'
|
|
1046
|
+
);
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
const contentElement = document.createElement('p');
|
|
1050
|
+
contentStrings.forEach((item: string, i: number): void => {
|
|
1051
|
+
contentElement.append(item);
|
|
1052
|
+
if (i !== contentStrings.length - 1) {
|
|
1053
|
+
contentElement.append(document.createElement('br'));
|
|
1054
|
+
}
|
|
1055
|
+
});
|
|
1056
|
+
const modal = Modal.confirm(title, contentElement, Severity.info, modalButtons);
|
|
1057
|
+
modal.addEventListener('button.clicked', function (event: Event) {
|
|
1058
|
+
callback((event.target as HTMLButtonElement).name, previewUrl, $actionElement, modal);
|
|
1059
|
+
});
|
|
1060
|
+
};
|
|
1061
|
+
|
|
1062
|
+
/**
|
|
1063
|
+
* New action
|
|
1064
|
+
*
|
|
1065
|
+
* When there are changes:
|
|
1066
|
+
* Will take action based on local storage preset
|
|
1067
|
+
* If preset is not available, a modal will open
|
|
1068
|
+
*
|
|
1069
|
+
* @param {Event} event
|
|
1070
|
+
* @param {Function} callback
|
|
1071
|
+
*/
|
|
1072
|
+
FormEngine.newAction = function(event: Event, callback: NewActionCallback): void {
|
|
1073
|
+
callback = callback || FormEngine.newActionCallback;
|
|
1074
|
+
|
|
1075
|
+
const $actionElement = $('<input />').attr('type', 'hidden').attr('name', '_savedoknew').attr('value', '1');
|
|
1076
|
+
const isNew = ('isNew' in (event.target as HTMLElement).dataset);
|
|
1077
|
+
if (FormEngine.hasChange() || FormEngine.isNew()) {
|
|
1078
|
+
FormEngine.showNewModal(isNew, $actionElement, callback);
|
|
1079
|
+
} else {
|
|
1080
|
+
$(selector`form[name="${FormEngine.formName}"]`).append($actionElement);
|
|
1081
|
+
FormEngine.formElement.submit();
|
|
1082
|
+
}
|
|
1083
|
+
};
|
|
1084
|
+
|
|
1085
|
+
/**
|
|
1086
|
+
* The callback for the preview action
|
|
1087
|
+
*
|
|
1088
|
+
* @param {string} modalButtonName
|
|
1089
|
+
* @param {element} $actionElement
|
|
1090
|
+
*/
|
|
1091
|
+
FormEngine.newActionCallback = function(modalButtonName: string, $actionElement: JQuery): void {
|
|
1092
|
+
const $form = $(selector`form[name="${FormEngine.formName}"]`);
|
|
1093
|
+
Modal.dismiss();
|
|
1094
|
+
switch(modalButtonName) {
|
|
1095
|
+
case 'no':
|
|
1096
|
+
$form.append($actionElement);
|
|
1097
|
+
FormEngine.formElement.submit();
|
|
1098
|
+
break;
|
|
1099
|
+
case 'yes':
|
|
1100
|
+
$form.append($actionElement);
|
|
1101
|
+
FormEngine.saveDocument();
|
|
1102
|
+
break;
|
|
1103
|
+
default:
|
|
1104
|
+
break;
|
|
1105
|
+
}
|
|
1106
|
+
};
|
|
1107
|
+
|
|
1108
|
+
/**
|
|
1109
|
+
* Show the new modal
|
|
1110
|
+
*
|
|
1111
|
+
* @param {bool} isNew
|
|
1112
|
+
* @param {element} $actionElement
|
|
1113
|
+
* @param {Function} callback
|
|
1114
|
+
*/
|
|
1115
|
+
FormEngine.showNewModal = function(isNew: boolean, $actionElement: JQuery, callback: NewActionCallback): void {
|
|
1116
|
+
const title = TYPO3.lang['label.confirm.new_record_changed.title'] || 'Do you want to save before adding?';
|
|
1117
|
+
const content = (
|
|
1118
|
+
TYPO3.lang['label.confirm.new_record_changed.content']
|
|
1119
|
+
|| 'You need to save your changes before creating a new record. Do you want to save and create now?'
|
|
1120
|
+
);
|
|
1121
|
+
let modalButtons = [];
|
|
1122
|
+
const modalCancelButtonConfiguration = {
|
|
1123
|
+
text: TYPO3.lang['buttons.confirm.new_record_changed.cancel'] || 'Cancel',
|
|
1124
|
+
btnClass: 'btn-default',
|
|
1125
|
+
name: 'cancel'
|
|
1126
|
+
};
|
|
1127
|
+
const modalNoButtonConfiguration = {
|
|
1128
|
+
text: TYPO3.lang['buttons.confirm.new_record_changed.no'] || 'No, just add',
|
|
1129
|
+
btnClass: 'btn-default',
|
|
1130
|
+
name: 'no'
|
|
1131
|
+
};
|
|
1132
|
+
const modalYesButtonConfiguration = {
|
|
1133
|
+
text: TYPO3.lang['buttons.confirm.new_record_changed.yes'] || 'Yes, save and create now',
|
|
1134
|
+
btnClass: 'btn-primary',
|
|
1135
|
+
name: 'yes',
|
|
1136
|
+
active: true
|
|
1137
|
+
};
|
|
1138
|
+
if (isNew) {
|
|
1139
|
+
modalButtons = [
|
|
1140
|
+
modalCancelButtonConfiguration,
|
|
1141
|
+
modalYesButtonConfiguration
|
|
1142
|
+
];
|
|
1143
|
+
} else {
|
|
1144
|
+
modalButtons = [
|
|
1145
|
+
modalCancelButtonConfiguration,
|
|
1146
|
+
modalNoButtonConfiguration,
|
|
1147
|
+
modalYesButtonConfiguration
|
|
1148
|
+
];
|
|
1149
|
+
}
|
|
1150
|
+
const modal = Modal.confirm(title, content, Severity.info, modalButtons);
|
|
1151
|
+
modal.addEventListener('button.clicked', function (event: Event) {
|
|
1152
|
+
callback((event.target as HTMLButtonElement).name, $actionElement);
|
|
1153
|
+
});
|
|
1154
|
+
};
|
|
1155
|
+
|
|
1156
|
+
/**
|
|
1157
|
+
* Duplicate action
|
|
1158
|
+
*
|
|
1159
|
+
* When there are changes:
|
|
1160
|
+
* Will take action based on local storage preset
|
|
1161
|
+
* If preset is not available, a modal will open
|
|
1162
|
+
*/
|
|
1163
|
+
FormEngine.duplicateAction = function(event: Event, callback: DuplicateActionCallback): void {
|
|
1164
|
+
callback = callback || FormEngine.duplicateActionCallback;
|
|
1165
|
+
|
|
1166
|
+
const $actionElement = $('<input />').attr('type', 'hidden').attr('name', '_duplicatedoc').attr('value', '1');
|
|
1167
|
+
const isNew = ('isNew' in (event.target as HTMLElement).dataset);
|
|
1168
|
+
if (FormEngine.hasChange() || FormEngine.isNew()) {
|
|
1169
|
+
FormEngine.showDuplicateModal(isNew, $actionElement, callback);
|
|
1170
|
+
} else {
|
|
1171
|
+
$(selector`form[name="${FormEngine.formName}"]`).append($actionElement);
|
|
1172
|
+
FormEngine.formElement.submit();
|
|
1173
|
+
}
|
|
1174
|
+
};
|
|
1175
|
+
|
|
1176
|
+
/**
|
|
1177
|
+
* The callback for the duplicate action
|
|
1178
|
+
*
|
|
1179
|
+
* @param {string} modalButtonName
|
|
1180
|
+
* @param {element} $actionElement
|
|
1181
|
+
*/
|
|
1182
|
+
FormEngine.duplicateActionCallback = function(modalButtonName: string, $actionElement: JQuery): void {
|
|
1183
|
+
const $form = $(selector`form[name="${FormEngine.formName}"]`);
|
|
1184
|
+
Modal.dismiss();
|
|
1185
|
+
switch(modalButtonName) {
|
|
1186
|
+
case 'no':
|
|
1187
|
+
$form.append($actionElement);
|
|
1188
|
+
FormEngine.formElement.submit();
|
|
1189
|
+
break;
|
|
1190
|
+
case 'yes':
|
|
1191
|
+
$form.append($actionElement);
|
|
1192
|
+
FormEngine.saveDocument();
|
|
1193
|
+
break;
|
|
1194
|
+
default:
|
|
1195
|
+
break;
|
|
1196
|
+
}
|
|
1197
|
+
};
|
|
1198
|
+
|
|
1199
|
+
FormEngine.showDuplicateModal = function(isNew: boolean, $actionElement: JQuery, callback: DuplicateActionCallback): void {
|
|
1200
|
+
const title = TYPO3.lang['label.confirm.duplicate_record_changed.title'] || 'Do you want to save before duplicating this record?';
|
|
1201
|
+
const content = (
|
|
1202
|
+
TYPO3.lang['label.confirm.duplicate_record_changed.content']
|
|
1203
|
+
|| 'You currently have unsaved changes. Do you want to save your changes before duplicating this record?'
|
|
1204
|
+
);
|
|
1205
|
+
let modalButtons = [];
|
|
1206
|
+
const modalCancelButtonConfiguration = {
|
|
1207
|
+
text: TYPO3.lang['buttons.confirm.duplicate_record_changed.cancel'] || 'Cancel',
|
|
1208
|
+
btnClass: 'btn-default',
|
|
1209
|
+
name: 'cancel'
|
|
1210
|
+
};
|
|
1211
|
+
const modalDismissDuplicateButtonConfiguration = {
|
|
1212
|
+
text: TYPO3.lang['buttons.confirm.duplicate_record_changed.no'] || 'No, just duplicate the original',
|
|
1213
|
+
btnClass: 'btn-default',
|
|
1214
|
+
name: 'no'
|
|
1215
|
+
};
|
|
1216
|
+
const modalSaveDuplicateButtonConfiguration = {
|
|
1217
|
+
text: TYPO3.lang['buttons.confirm.duplicate_record_changed.yes'] || 'Yes, save and duplicate this record',
|
|
1218
|
+
btnClass: 'btn-primary',
|
|
1219
|
+
name: 'yes',
|
|
1220
|
+
active: true
|
|
1221
|
+
};
|
|
1222
|
+
if (isNew) {
|
|
1223
|
+
modalButtons = [
|
|
1224
|
+
modalCancelButtonConfiguration,
|
|
1225
|
+
modalSaveDuplicateButtonConfiguration
|
|
1226
|
+
];
|
|
1227
|
+
} else {
|
|
1228
|
+
modalButtons = [
|
|
1229
|
+
modalCancelButtonConfiguration,
|
|
1230
|
+
modalDismissDuplicateButtonConfiguration,
|
|
1231
|
+
modalSaveDuplicateButtonConfiguration
|
|
1232
|
+
];
|
|
1233
|
+
}
|
|
1234
|
+
const modal = Modal.confirm(title, content, Severity.info, modalButtons);
|
|
1235
|
+
modal.addEventListener('button.clicked', function (event: Event) {
|
|
1236
|
+
callback((event.target as HTMLButtonElement).name, $actionElement);
|
|
1237
|
+
});
|
|
1238
|
+
};
|
|
1239
|
+
|
|
1240
|
+
/**
|
|
1241
|
+
* Delete action
|
|
1242
|
+
*
|
|
1243
|
+
* When there are changes:
|
|
1244
|
+
* Will take action based on local storage preset
|
|
1245
|
+
* If preset is not available, a modal will open
|
|
1246
|
+
*/
|
|
1247
|
+
FormEngine.deleteAction = function(event: Event, callback: DeleteActionCallback): void {
|
|
1248
|
+
callback = callback || FormEngine.deleteActionCallback;
|
|
1249
|
+
|
|
1250
|
+
const $anchorElement = $(event.target);
|
|
1251
|
+
|
|
1252
|
+
FormEngine.showDeleteModal($anchorElement, callback);
|
|
1253
|
+
};
|
|
1254
|
+
|
|
1255
|
+
/**
|
|
1256
|
+
* The callback for the delete action
|
|
1257
|
+
*
|
|
1258
|
+
* @param {string} modalButtonName
|
|
1259
|
+
* @param {element} $anchorElement
|
|
1260
|
+
*/
|
|
1261
|
+
FormEngine.deleteActionCallback = function(modalButtonName: string, $anchorElement: JQuery): void {
|
|
1262
|
+
Modal.dismiss();
|
|
1263
|
+
if (modalButtonName === 'yes') {
|
|
1264
|
+
FormEngine.invokeRecordDeletion($anchorElement);
|
|
1265
|
+
}
|
|
1266
|
+
};
|
|
1267
|
+
|
|
1268
|
+
/**
|
|
1269
|
+
* Show the delete modal
|
|
1270
|
+
*
|
|
1271
|
+
* @param {element} $anchorElement
|
|
1272
|
+
* @param {Function} callback
|
|
1273
|
+
*/
|
|
1274
|
+
FormEngine.showDeleteModal = function($anchorElement: JQuery, callback: DeleteActionCallback): void {
|
|
1275
|
+
const title = TYPO3.lang['label.confirm.delete_record.title'] || 'Delete this record?';
|
|
1276
|
+
let content = (TYPO3.lang['label.confirm.delete_record.content'] || 'Are you sure you want to delete the record \'%s\'?').replace('%s', $anchorElement.data('record-info'));
|
|
1277
|
+
|
|
1278
|
+
if ($anchorElement.data('reference-count-message')) {
|
|
1279
|
+
content += '\n' + $anchorElement.data('reference-count-message');
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
if ($anchorElement.data('translation-count-message')) {
|
|
1283
|
+
content += '\n' + $anchorElement.data('translation-count-message');
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
const modal = Modal.confirm(title, content, Severity.warning, [
|
|
1287
|
+
{
|
|
1288
|
+
text: TYPO3.lang['buttons.confirm.delete_record.no'] || 'Cancel',
|
|
1289
|
+
btnClass: 'btn-default',
|
|
1290
|
+
name: 'no'
|
|
1291
|
+
},
|
|
1292
|
+
{
|
|
1293
|
+
text: TYPO3.lang['buttons.confirm.delete_record.yes'] || 'Yes, delete this record',
|
|
1294
|
+
btnClass: 'btn-warning',
|
|
1295
|
+
name: 'yes',
|
|
1296
|
+
active: true
|
|
1297
|
+
}
|
|
1298
|
+
]);
|
|
1299
|
+
modal.addEventListener('button.clicked', function (event: Event) {
|
|
1300
|
+
callback((event.target as HTMLButtonElement).name, $anchorElement);
|
|
1301
|
+
});
|
|
1302
|
+
};
|
|
1303
|
+
|
|
1304
|
+
/**
|
|
1305
|
+
* In case the given option is a child of a disabled optGroup, enable the optGroup
|
|
1306
|
+
*/
|
|
1307
|
+
FormEngine.enableOptGroup = function (option: HTMLOptionElement): void {
|
|
1308
|
+
const optGroup = <HTMLOptGroupElement>option.parentElement;
|
|
1309
|
+
if (optGroup instanceof HTMLOptGroupElement && optGroup.querySelectorAll('option:not([hidden]):not([disabled]):not(.hidden)').length) {
|
|
1310
|
+
optGroup.hidden = false;
|
|
1311
|
+
optGroup.disabled = false;
|
|
1312
|
+
optGroup.classList.remove('hidden');
|
|
1313
|
+
}
|
|
1314
|
+
};
|
|
1315
|
+
|
|
1316
|
+
/**
|
|
1317
|
+
* Close current open document
|
|
1318
|
+
*/
|
|
1319
|
+
FormEngine.closeDocument = function(): void {
|
|
1320
|
+
FormEngine.formElement.closeDoc.value = 1;
|
|
1321
|
+
FormEngine.formElement.submit();
|
|
1322
|
+
};
|
|
1323
|
+
|
|
1324
|
+
FormEngine.saveDocument = function(): void {
|
|
1325
|
+
const currentlyFocussed = document.activeElement;
|
|
1326
|
+
if (currentlyFocussed instanceof HTMLInputElement || currentlyFocussed instanceof HTMLSelectElement || currentlyFocussed instanceof HTMLTextAreaElement) {
|
|
1327
|
+
// Blur currently focussed :input element to trigger FormEngine's internal data normalization
|
|
1328
|
+
currentlyFocussed.blur();
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
const saveField = FormEngine.formElement.querySelector(selector`input[name="${FormEngine.doSaveFieldName}"]`) as HTMLInputElement|null;
|
|
1332
|
+
if (saveField !== null) {
|
|
1333
|
+
saveField.value = '1';
|
|
1334
|
+
}
|
|
1335
|
+
FormEngine.formElement.requestSubmit();
|
|
1336
|
+
};
|
|
1337
|
+
|
|
1338
|
+
FormEngine.saveAndCloseDocument = function(): void {
|
|
1339
|
+
const saveAndCloseInput = document.createElement('input');
|
|
1340
|
+
saveAndCloseInput.type = 'hidden';
|
|
1341
|
+
saveAndCloseInput.name = '_saveandclosedok';
|
|
1342
|
+
saveAndCloseInput.value = '1';
|
|
1343
|
+
document.querySelector(selector`form[name="${FormEngine.formName}"]`).append(saveAndCloseInput);
|
|
1344
|
+
|
|
1345
|
+
FormEngine.saveDocument();
|
|
1346
|
+
};
|
|
1347
|
+
|
|
1348
|
+
/**
|
|
1349
|
+
* Main init function called from outside
|
|
1350
|
+
*
|
|
1351
|
+
* Sets some options and registers the DOMready handler to initialize further things
|
|
1352
|
+
*
|
|
1353
|
+
* @param {String} browserUrl
|
|
1354
|
+
* @param {String} doSaveFieldName
|
|
1355
|
+
*/
|
|
1356
|
+
FormEngine.initialize = function(browserUrl: string, doSaveFieldName: string): void {
|
|
1357
|
+
FormEngine.browserUrl = browserUrl;
|
|
1358
|
+
// Add doSaveFieldName - fall back to to `doSave` for b/w compatibility
|
|
1359
|
+
FormEngine.doSaveFieldName = doSaveFieldName || 'doSave';
|
|
1360
|
+
|
|
1361
|
+
DocumentService.ready().then((): void => {
|
|
1362
|
+
FormEngine.initializeEvents();
|
|
1363
|
+
FormEngine.Validation.initialize(this);
|
|
1364
|
+
FormEngine.reinitialize();
|
|
1365
|
+
$('#t3js-ui-block').remove();
|
|
1366
|
+
FormEngine.enableDocHeaderButtons();
|
|
1367
|
+
|
|
1368
|
+
Hotkeys.setScope('backend/form-engine');
|
|
1369
|
+
Hotkeys.register([Hotkeys.normalizedCtrlModifierKey, 's'], (e: KeyboardEvent): void => {
|
|
1370
|
+
e.preventDefault();
|
|
1371
|
+
|
|
1372
|
+
FormEngine.saveDocument();
|
|
1373
|
+
}, { scope: 'backend/form-engine', allowOnEditables: true, bindElement: FormEngine.formElement._savedok });
|
|
1374
|
+
Hotkeys.register([Hotkeys.normalizedCtrlModifierKey, ModifierKeys.SHIFT, 's'], (e: KeyboardEvent): void => {
|
|
1375
|
+
e.preventDefault();
|
|
1376
|
+
|
|
1377
|
+
FormEngine.saveAndCloseDocument();
|
|
1378
|
+
}, { scope: 'backend/form-engine', allowOnEditables: true });
|
|
1379
|
+
});
|
|
1380
|
+
};
|
|
1381
|
+
|
|
1382
|
+
FormEngine.invokeRecordDeletion = function ($anchorElement: JQuery) {
|
|
1383
|
+
window.location.href = $anchorElement.attr('href');
|
|
1384
|
+
};
|
|
1385
|
+
|
|
1386
|
+
// make the form engine object publicly visible for other objects in the TYPO3 namespace
|
|
1387
|
+
TYPO3.FormEngine = FormEngine;
|
|
1388
|
+
|
|
1389
|
+
// return the object in the global space
|
|
1390
|
+
return FormEngine;
|
|
1391
|
+
})();
|