From 08e7dc7012d5c2e86244a3811f10fdcfb9859e20 Mon Sep 17 00:00:00 2001 From: Andreas Fernandez <a.fernandez@scripting-base.de> Date: Tue, 23 Aug 2022 12:06:53 +0200 Subject: [PATCH] [!!!][TASK] Remove jQuery in Popover The support for jQuery in the module `@typo3/backend/popover` has been dropped. Passing jQuery elements to the module's methods is not possible anymore. Resolves: #98261 Releases: main Change-Id: I8716ad1762d67faf51b67eacec82435690f5b097 Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/75535 Tested-by: core-ci <typo3@b13.com> Tested-by: Anja Leichsenring <aleichsenring@ab-softlab.de> Tested-by: Benni Mack <benni@typo3.org> Tested-by: Andreas Fernandez <a.fernandez@scripting-base.de> Reviewed-by: Anja Leichsenring <aleichsenring@ab-softlab.de> Reviewed-by: Benni Mack <benni@typo3.org> Reviewed-by: Andreas Fernandez <a.fernandez@scripting-base.de> --- .../TypeScript/backend/context-help.ts | 49 +++---- .../TypeScript/backend/form-engine-review.ts | 38 ++--- Build/Sources/TypeScript/backend/popover.ts | 135 ++++++++---------- .../TypeScript/backend/tests/popover-test.ts | 76 +++++----- .../Public/JavaScript/context-help.js | 2 +- .../Public/JavaScript/form-engine-review.js | 2 +- .../Resources/Public/JavaScript/popover.js | 2 +- .../backend/Tests/JavaScript/popover-test.js | 2 +- ...ing-98261-RemovedJQueryInPopoverModule.rst | 60 ++++++++ 9 files changed, 212 insertions(+), 154 deletions(-) create mode 100644 typo3/sysext/core/Documentation/Changelog/12.0/Breaking-98261-RemovedJQueryInPopoverModule.rst diff --git a/Build/Sources/TypeScript/backend/context-help.ts b/Build/Sources/TypeScript/backend/context-help.ts index 447f58ae5957..3edd127af497 100644 --- a/Build/Sources/TypeScript/backend/context-help.ts +++ b/Build/Sources/TypeScript/backend/context-help.ts @@ -12,9 +12,9 @@ */ import 'bootstrap'; -import $ from 'jquery'; import {Popover as BootstrapPopover} from 'bootstrap'; import Popover from './popover'; +import RegularEvent from '@typo3/core/event/regular-event'; /** * Module: @typo3/backend/context-help @@ -31,36 +31,37 @@ class ContextHelp { } public initialize(): void { - const $element = $(this.selector); - $element - .attr('data-bs-html', 'true') - .attr('data-bs-placement', this.placement) - .attr('data-bs-trigger', this.trigger); - Popover.popover($element); + const elements = document.querySelectorAll(this.selector); + elements.forEach((element: HTMLElement): void => { + element.dataset.bsHtml = 'true'; + element.dataset.bsPlacement = this.placement; + element.dataset.bsTrigger = this.trigger; - $(document).on('show.bs.popover', this.selector, (e: Event): void => { - const $me = $(e.currentTarget); - const description = $me.data('description'); - if (typeof description !== 'undefined' && description !== '') { + Popover.popover(element); + }); + + new RegularEvent('show.bs.popover', (e: Event): void => { + const me = e.target as HTMLElement; + const description = me.dataset.description; + + if (!!description) { const options = <BootstrapPopover.Options>{ - title: $me.data('title') || '', + title: me.dataset.title || '', content: description, }; - Popover.setOptions($me, options); + Popover.setOptions(me, options); } - }).on('click', 'body', (e: any): void => { - $(this.selector).each((index: number, triggerElement: Element): void => { - const $triggerElement = $(triggerElement); - // the 'is' for buttons that trigger popups - // the 'has' for icons within a button that triggers a popup - if (!$triggerElement.is(e.target) - && $triggerElement.has(e.target).length === 0 - && $('.popover').has(e.target).length === 0 - ) { - Popover.hide($triggerElement); + }).delegateTo(document, this.selector); + + new RegularEvent('click', (e: Event): void => { + const me = e.target as HTMLElement; + const elements = document.querySelectorAll(this.selector); + elements.forEach((element: HTMLElement): void => { + if (!element.isEqualNode(me)) { + Popover.hide(element); } }); - }); + }).delegateTo(document, 'body'); } } diff --git a/Build/Sources/TypeScript/backend/form-engine-review.ts b/Build/Sources/TypeScript/backend/form-engine-review.ts index 063a9eabf18c..ee714c65b97c 100644 --- a/Build/Sources/TypeScript/backend/form-engine-review.ts +++ b/Build/Sources/TypeScript/backend/form-engine-review.ts @@ -50,17 +50,20 @@ class FormEngineReview { * @param {Object} context */ public static attachButtonToModuleHeader(context: any): void { - const $leastButtonBar: any = $('.t3js-module-docheader-bar-buttons').children().last().find('[role="toolbar"]'); - const $button: any = $('<a />', { - class: 'btn btn-danger btn-sm hidden ' + context.toggleButtonClass, - href: '#', - title: TYPO3.lang['buttons.reviewFailedValidationFields'], - }).append( - $('<typo3-backend-icon/>', {identifier: 'actions-info', size: 'small'}), - ); - - Popover.popover($button); - $leastButtonBar.prepend($button); + const leastButtonBar: HTMLElement = document.querySelector('.t3js-module-docheader-bar-buttons').lastElementChild.querySelector('[role="toolbar"]'); + + const icon = document.createElement('typo3-backend-icon'); + icon.setAttribute('identifier', 'actions-info'); + icon.setAttribute('size', 'small'); + + const button = document.createElement('button'); + button.type = 'button'; + button.classList.add('btn', 'btn-danger', 'btn-sm', 'hidden', context.toggleButtonClass); + button.title = TYPO3.lang['buttons.reviewFailedValidationFields']; + button.appendChild(icon); + + Popover.popover(button); + leastButtonBar.prepend(button); } /** @@ -89,7 +92,10 @@ class FormEngineReview { public checkForReviewableField = (): void => { const me: any = this; const $invalidFields: any = FormEngineReview.findInvalidField(); - const $toggleButton: any = $('.' + this.toggleButtonClass); + const toggleButton: HTMLElement = document.querySelector('.' + this.toggleButtonClass); + if (toggleButton === null) { + return; + } if ($invalidFields.length > 0) { const $list: any = $('<div />', {class: 'list-group'}); @@ -107,14 +113,14 @@ class FormEngineReview { $list.append(link); }); - $toggleButton.removeClass('hidden'); - Popover.setOptions($toggleButton, <BootstrapPopover.Options>{ + toggleButton.classList.remove('hidden'); + Popover.setOptions(toggleButton, <BootstrapPopover.Options>{ html: true, content: $list[0] }); } else { - $toggleButton.addClass('hidden'); - Popover.hide($toggleButton); + toggleButton.classList.add('hidden'); + Popover.hide(toggleButton); } } diff --git a/Build/Sources/TypeScript/backend/popover.ts b/Build/Sources/TypeScript/backend/popover.ts index 0144cc5b8dc4..49826cd598db 100644 --- a/Build/Sources/TypeScript/backend/popover.ts +++ b/Build/Sources/TypeScript/backend/popover.ts @@ -11,7 +11,6 @@ * The TYPO3 project - inspiring people to share! */ -import $ from 'jquery'; import {Popover as BootstrapPopover} from 'bootstrap'; /** @@ -37,22 +36,20 @@ class Popover { */ public initialize(selector?: string): void { selector = selector || this.DEFAULT_SELECTOR; - $(selector).each((i, el) => { - const popover = new BootstrapPopover(el); - $(el).data('typo3.bs.popover', popover); + document.querySelectorAll(selector).forEach((element: HTMLElement): void => { + this.applyTitleIfAvailable(element); + new BootstrapPopover(element); }); } // noinspection JSMethodCanBeStatic /** * Popover wrapper function - * - * @param {JQuery} $element */ - public popover($element: JQuery) { - $element.each((i, el) => { - const popover = new BootstrapPopover(el); - $(el).data('typo3.bs.popover', popover); + public popover(element: NodeListOf<HTMLElement> | HTMLElement) { + this.toIterable(element).forEach((element: HTMLElement): void => { + this.applyTitleIfAvailable(element); + new BootstrapPopover(element); }); } @@ -60,117 +57,99 @@ class Popover { /** * Set popover options on $element * - * @param {JQuery} $element - * @param {PopoverOptions} options + * @param {element: HTMLElement} element + * @param {BootstrapPopover.Options} options */ - public setOptions($element: JQuery, options?: BootstrapPopover.Options): void { + public setOptions(element: HTMLElement, options?: BootstrapPopover.Options): void { options = options || <BootstrapPopover.Options>{}; - options.html = true; - const title: string = options.title || $element.data('title') || ''; - const content: string = options.content || $element.data('bs-content') || ''; - $element - .attr('data-bs-original-title', (title as string)) - .attr('data-bs-content', (content as string)) - .attr('data-bs-placement', 'auto') + const title: string = (options.title as string) || element.dataset.title || element.dataset.bsTitle || ''; + const content: string = (options.content as string) || element.dataset.bsContent || ''; + element.dataset.bsTitle = title; + element.dataset.bsOriginalTitle = title; + element.dataset.bsContent = content; + element.dataset.bsPlacement = 'auto'; delete options.title; delete options.content; - $.each(options, (key, value) => { - this.setOption($element, key, value); - }); - const popover = $element.data('typo3.bs.popover'); + const popover = BootstrapPopover.getInstance(element); + // @ts-ignore popover.setContent({ '.popover-header': title, '.popover-body': content }); - } - // noinspection JSMethodCanBeStatic - /** - * Set popover option on $element - * - * @param {JQuery} $element - * @param {String} key - * @param {String} value - */ - public setOption($element: JQuery, key: string, value: string): void { - $element.each((i, el) => { - const popover = $(el).data('typo3.bs.popover'); - if (popover) { - popover._config[key] = value; - } - }); + for (const [optionName, optionValue] of Object.entries(options)) { + // @ts-ignore: using internal _config attribute + popover._config[optionName] = optionValue; + } } // noinspection JSMethodCanBeStatic /** * Show popover with title and content on $element * - * @param {JQuery} $element + * @param {element: HTMLElement} element */ - public show($element: JQuery): void { - $element.each((i, el) => { - const popover = $(el).data('typo3.bs.popover'); - if (popover) { - popover.show(); - } - }); + public show(element: HTMLElement): void { + const popover = BootstrapPopover.getInstance(element); + popover.show(); } // noinspection JSMethodCanBeStatic /** * Hide popover on $element * - * @param {JQuery} $element + * @param {HTMLElement} element */ - public hide($element: JQuery): void { - $element.each((i, el) => { - const popover = $(el).data('typo3.bs.popover'); - if (popover) { - popover.hide(); - } - }); + public hide(element: HTMLElement): void { + const popover = BootstrapPopover.getInstance(element); + popover.hide(); } // noinspection JSMethodCanBeStatic /** * Destroy popover on $element * - * @param {Object} $element + * @param {HTMLElement} element */ - public destroy($element: JQuery): void { - $element.each((i, el) => { - const popover = $(el).data('typo3.bs.popover'); - if (popover) { - popover.dispose(); - } - }); + public destroy(element: HTMLElement): void { + const popover = BootstrapPopover.getInstance(element); + popover.dispose(); } // noinspection JSMethodCanBeStatic /** * Toggle popover on $element * - * @param {Object} $element + * @param {HTMLElement} element */ - public toggle($element: JQuery): void { - $element.each((i, el) => { - const popover = $(el).data('typo3.bs.popover'); - if (popover) { - popover.toggle(); - } - }); + public toggle(element: HTMLElement): void { + const popover = BootstrapPopover.getInstance(element); + popover.toggle(); + } + + private toIterable(element: NodeListOf<HTMLElement> | HTMLElement | unknown): NodeList | HTMLElement[] { + let elementList; + if (element instanceof HTMLElement) { + elementList = [element]; + } else if (element instanceof NodeList) { + elementList = element; + } else { + throw `Cannot consume element of type ${element.constructor.name}, expected NodeListOf<HTMLElement> or HTMLElement`; + } + + return elementList; } - // noinspection JSMethodCanBeStatic /** - * Update popover with new content - * - * @param $element + * If the element contains an attributes that qualifies as a title, store it as data attribute "bs-title" */ - public update($element: JQuery): void { - $element.data('typo3.bs.popover')._popper.update(); + private applyTitleIfAvailable(element: HTMLElement): void { + const title = (element.title as string) || element.dataset.title || ''; + if (title) { + element.dataset.bsTitle = title; + } } } diff --git a/Build/Sources/TypeScript/backend/tests/popover-test.ts b/Build/Sources/TypeScript/backend/tests/popover-test.ts index 6b6f419aeaa1..c34ad1ed00f8 100644 --- a/Build/Sources/TypeScript/backend/tests/popover-test.ts +++ b/Build/Sources/TypeScript/backend/tests/popover-test.ts @@ -1,4 +1,3 @@ -import $ from 'jquery'; import {Popover as BootstrapPopover} from 'bootstrap'; import Popover from '@typo3/backend/popover'; @@ -7,66 +6,79 @@ describe('TYPO3/CMS/Backend/PopoverTest:', () => { * @test */ describe('initialize', () => { - const $body = $('body'); - const $element = $('<div data-bs-toggle="popover">'); - $body.append($element); + const element = document.createElement('div'); + element.dataset.bsToggle = 'popover'; + document.body.append(element); + it('works with default selector', () => { Popover.initialize(); - expect($element[0].outerHTML).toBe('<div data-bs-toggle="popover"></div>'); + expect(element.outerHTML).toBe('<div data-bs-toggle="popover"></div>'); }); - const $element2 = $('<div data-bs-toggle="popover" data-title="foo">'); - $body.append($element2); + const element2 = document.createElement('div'); + element2.dataset.bsToggle = 'popover'; + element2.dataset.title = 'foo'; + document.body.append(element2); it('works with default selector and title attribute', () => { Popover.initialize(); - expect($element2[0].outerHTML).toBe('<div data-bs-toggle="popover" data-title="foo"></div>'); + expect(element2.outerHTML).toBe('<div data-bs-toggle="popover" data-title="foo" data-bs-title="foo"></div>'); }); - const $element3 = $('<div data-bs-toggle="popover" data-bs-content="foo">'); - $body.append($element3); + const element3 = document.createElement('div'); + element3.dataset.bsToggle = 'popover'; + element3.dataset.bsContent = 'foo'; + document.body.append(element3); it('works with default selector and content attribute', () => { Popover.initialize(); - expect($element3[0].outerHTML).toBe('<div data-bs-toggle="popover" data-bs-content="foo"></div>'); + expect(element3.outerHTML).toBe('<div data-bs-toggle="popover" data-bs-content="foo"></div>'); }); - const $element4 = $('<div class="t3js-popover">'); - $body.append($element4); + const element4 = document.createElement('div'); + element4.classList.add('t3js-popover') + document.body.append(element4); it('works with custom selector', () => { Popover.initialize('.t3js-popover'); - expect($element4[0].outerHTML).toBe('<div class="t3js-popover"></div>'); + expect(element4.outerHTML).toBe('<div class="t3js-popover"></div>'); }); }); describe('call setOptions', () => { - const $body = $('body'); - const $element = $('<div class="t3js-test-set-options" data-title="foo-title" data-bs-content="foo-content">'); - $body.append($element); + const element = document.createElement('div'); + element.classList.add('t3js-test-set-options'); + element.dataset.title = 'foo-title'; + element.dataset.bsContent = 'foo-content'; + document.body.append(element); + it('can set title', () => { Popover.initialize('.t3js-test-set-options'); - expect($element.attr('data-title')).toBe('foo-title'); - expect($element.attr('data-bs-content')).toBe('foo-content'); - Popover.setOptions($element, <BootstrapPopover.Options>{ + expect(element.getAttribute('data-title')).toBe('foo-title'); + expect(element.getAttribute('data-bs-content')).toBe('foo-content'); + Popover.setOptions(element, <BootstrapPopover.Options>{ 'title': 'bar-title' }); - expect($element.attr('data-title')).toBe('foo-title'); - expect($element.attr('data-bs-content')).toBe('foo-content'); - expect($element.attr('data-bs-original-title')).toBe('bar-title'); + expect(element.getAttribute('data-title')).toBe('foo-title'); + expect(element.getAttribute('data-bs-content')).toBe('foo-content'); + expect(element.getAttribute('data-bs-original-title')).toBe('bar-title'); }); - const $element2 = $('<div class="t3js-test-set-options2" data-title="foo-title" data-bs-content="foo-content">'); - $body.append($element2); + + const element2 = document.createElement('div'); + element2.classList.add('t3js-test-set-options2'); + element2.dataset.title = 'foo-title'; + element2.dataset.bsContent = 'foo-content'; + document.body.append(element2); it('can set content', () => { Popover.initialize('.t3js-test-set-options2'); // Popover must be visible before the content can be updated manually via setOptions() - Popover.show($element2); - expect($element2.attr('data-title')).toBe('foo-title'); - expect($element2.attr('data-bs-content')).toBe('foo-content'); - Popover.setOptions($element2, <BootstrapPopover.Options>{ + Popover.show(element2); + expect(element2.getAttribute('data-title')).toBe('foo-title'); + expect(element2.getAttribute('data-bs-content')).toBe('foo-content'); + Popover.setOptions(element2, <BootstrapPopover.Options>{ 'content': 'bar-content' }); - expect($element2.attr('data-title')).toBe('foo-title'); - expect($element2.attr('data-bs-content')).toBe('bar-content'); - expect($element2.attr('data-bs-original-title')).toBe('foo-title'); + expect(element2.getAttribute('data-title')).toBe('foo-title'); + expect(element2.getAttribute('data-bs-content')).toBe('bar-content'); + expect(element2.getAttribute('data-bs-original-title')).toBe('foo-title'); }); }); }); diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/context-help.js b/typo3/sysext/backend/Resources/Public/JavaScript/context-help.js index b3d17ef1446f..673c501d2012 100644 --- a/typo3/sysext/backend/Resources/Public/JavaScript/context-help.js +++ b/typo3/sysext/backend/Resources/Public/JavaScript/context-help.js @@ -10,4 +10,4 @@ * * The TYPO3 project - inspiring people to share! */ -import"bootstrap";import $ from"jquery";import Popover from"@typo3/backend/popover.js";class ContextHelp{constructor(){this.trigger="click",this.placement="auto",this.selector=".help-link",this.initialize()}initialize(){const t=$(this.selector);t.attr("data-bs-html","true").attr("data-bs-placement",this.placement).attr("data-bs-trigger",this.trigger),Popover.popover(t),$(document).on("show.bs.popover",this.selector,(t=>{const e=$(t.currentTarget),o=e.data("description");if(void 0!==o&&""!==o){const t={title:e.data("title")||"",content:o};Popover.setOptions(e,t)}})).on("click","body",(t=>{$(this.selector).each(((e,o)=>{const r=$(o);r.is(t.target)||0!==r.has(t.target).length||0!==$(".popover").has(t.target).length||Popover.hide(r)}))}))}}export default new ContextHelp; \ No newline at end of file +import"bootstrap";import Popover from"@typo3/backend/popover.js";import RegularEvent from"@typo3/core/event/regular-event.js";class ContextHelp{constructor(){this.trigger="click",this.placement="auto",this.selector=".help-link",this.initialize()}initialize(){document.querySelectorAll(this.selector).forEach((e=>{e.dataset.bsHtml="true",e.dataset.bsPlacement=this.placement,e.dataset.bsTrigger=this.trigger,Popover.popover(e)})),new RegularEvent("show.bs.popover",(e=>{const t=e.target,o=t.dataset.description;if(o){const e={title:t.dataset.title||"",content:o};Popover.setOptions(t,e)}})).delegateTo(document,this.selector),new RegularEvent("click",(e=>{const t=e.target;document.querySelectorAll(this.selector).forEach((e=>{e.isEqualNode(t)||Popover.hide(e)}))})).delegateTo(document,"body")}}export default new ContextHelp; \ No newline at end of file diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/form-engine-review.js b/typo3/sysext/backend/Resources/Public/JavaScript/form-engine-review.js index 8e7ce4854bc9..6f52ccb79ac0 100644 --- a/typo3/sysext/backend/Resources/Public/JavaScript/form-engine-review.js +++ b/typo3/sysext/backend/Resources/Public/JavaScript/form-engine-review.js @@ -10,4 +10,4 @@ * * The TYPO3 project - inspiring people to share! */ -import"bootstrap";import $ from"jquery";import FormEngine from"@typo3/backend/form-engine.js";import"@typo3/backend/element/icon-element.js";import Popover from"@typo3/backend/popover.js";class FormEngineReview{constructor(){this.toggleButtonClass="t3js-toggle-review-panel",this.labelSelector=".t3js-formengine-label",this.checkForReviewableField=()=>{const e=this,t=FormEngineReview.findInvalidField(),i=$("."+this.toggleButtonClass);if(t.length>0){const o=$("<div />",{class:"list-group"});t.each((function(){const t=$(this),i=t.find("[data-formengine-validation-rules]"),n=document.createElement("a");n.classList.add("list-group-item"),n.href="#",n.textContent=t.find(e.labelSelector).text(),n.addEventListener("click",(t=>e.switchToField(t,i))),o.append(n)})),i.removeClass("hidden"),Popover.setOptions(i,{html:!0,content:o[0]})}else i.addClass("hidden"),Popover.hide(i)},this.switchToField=(e,t)=>{e.preventDefault();e.currentTarget;t.parents('[id][role="tabpanel"]').each((function(){$('[aria-controls="'+$(this).attr("id")+'"]').tab("show")})),t.focus()},this.initialize()}static findInvalidField(){return $(document).find(".tab-content ."+FormEngine.Validation.errorClass)}static attachButtonToModuleHeader(e){const t=$(".t3js-module-docheader-bar-buttons").children().last().find('[role="toolbar"]'),i=$("<a />",{class:"btn btn-danger btn-sm hidden "+e.toggleButtonClass,href:"#",title:TYPO3.lang["buttons.reviewFailedValidationFields"]}).append($("<typo3-backend-icon/>",{identifier:"actions-info",size:"small"}));Popover.popover(i),t.prepend(i)}initialize(){const e=this,t=$(document);$((()=>{FormEngineReview.attachButtonToModuleHeader(e)})),t.on("t3-formengine-postfieldvalidation",this.checkForReviewableField)}}export default new FormEngineReview; \ No newline at end of file +import"bootstrap";import $ from"jquery";import FormEngine from"@typo3/backend/form-engine.js";import"@typo3/backend/element/icon-element.js";import Popover from"@typo3/backend/popover.js";class FormEngineReview{constructor(){this.toggleButtonClass="t3js-toggle-review-panel",this.labelSelector=".t3js-formengine-label",this.checkForReviewableField=()=>{const e=this,t=FormEngineReview.findInvalidField(),o=document.querySelector("."+this.toggleButtonClass);if(null!==o)if(t.length>0){const i=$("<div />",{class:"list-group"});t.each((function(){const t=$(this),o=t.find("[data-formengine-validation-rules]"),n=document.createElement("a");n.classList.add("list-group-item"),n.href="#",n.textContent=t.find(e.labelSelector).text(),n.addEventListener("click",(t=>e.switchToField(t,o))),i.append(n)})),o.classList.remove("hidden"),Popover.setOptions(o,{html:!0,content:i[0]})}else o.classList.add("hidden"),Popover.hide(o)},this.switchToField=(e,t)=>{e.preventDefault();e.currentTarget;t.parents('[id][role="tabpanel"]').each((function(){$('[aria-controls="'+$(this).attr("id")+'"]').tab("show")})),t.focus()},this.initialize()}static findInvalidField(){return $(document).find(".tab-content ."+FormEngine.Validation.errorClass)}static attachButtonToModuleHeader(e){const t=document.querySelector(".t3js-module-docheader-bar-buttons").lastElementChild.querySelector('[role="toolbar"]'),o=document.createElement("typo3-backend-icon");o.setAttribute("identifier","actions-info"),o.setAttribute("size","small");const i=document.createElement("button");i.type="button",i.classList.add("btn","btn-danger","btn-sm","hidden",e.toggleButtonClass),i.title=TYPO3.lang["buttons.reviewFailedValidationFields"],i.appendChild(o),Popover.popover(i),t.prepend(i)}initialize(){const e=this,t=$(document);$((()=>{FormEngineReview.attachButtonToModuleHeader(e)})),t.on("t3-formengine-postfieldvalidation",this.checkForReviewableField)}}export default new FormEngineReview; \ No newline at end of file diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/popover.js b/typo3/sysext/backend/Resources/Public/JavaScript/popover.js index 84e98a95d8e5..8fa5821ff2e4 100644 --- a/typo3/sysext/backend/Resources/Public/JavaScript/popover.js +++ b/typo3/sysext/backend/Resources/Public/JavaScript/popover.js @@ -10,4 +10,4 @@ * * The TYPO3 project - inspiring people to share! */ -import $ from"jquery";import{Popover as BootstrapPopover}from"bootstrap";class Popover{constructor(){this.DEFAULT_SELECTOR='[data-bs-toggle="popover"]',this.initialize()}initialize(t){t=t||this.DEFAULT_SELECTOR,$(t).each(((t,o)=>{const e=new BootstrapPopover(o);$(o).data("typo3.bs.popover",e)}))}popover(t){t.each(((t,o)=>{const e=new BootstrapPopover(o);$(o).data("typo3.bs.popover",e)}))}setOptions(t,o){(o=o||{}).html=!0;const e=o.title||t.data("title")||"",p=o.content||t.data("bs-content")||"";t.attr("data-bs-original-title",e).attr("data-bs-content",p).attr("data-bs-placement","auto"),delete o.title,delete o.content,$.each(o,((o,e)=>{this.setOption(t,o,e)}));t.data("typo3.bs.popover").setContent({".popover-header":e,".popover-body":p})}setOption(t,o,e){t.each(((t,p)=>{const a=$(p).data("typo3.bs.popover");a&&(a._config[o]=e)}))}show(t){t.each(((t,o)=>{const e=$(o).data("typo3.bs.popover");e&&e.show()}))}hide(t){t.each(((t,o)=>{const e=$(o).data("typo3.bs.popover");e&&e.hide()}))}destroy(t){t.each(((t,o)=>{const e=$(o).data("typo3.bs.popover");e&&e.dispose()}))}toggle(t){t.each(((t,o)=>{const e=$(o).data("typo3.bs.popover");e&&e.toggle()}))}update(t){t.data("typo3.bs.popover")._popper.update()}}export default new Popover; \ No newline at end of file +import{Popover as BootstrapPopover}from"bootstrap";class Popover{constructor(){this.DEFAULT_SELECTOR='[data-bs-toggle="popover"]',this.initialize()}initialize(t){t=t||this.DEFAULT_SELECTOR,document.querySelectorAll(t).forEach((t=>{this.applyTitleIfAvailable(t),new BootstrapPopover(t)}))}popover(t){this.toIterable(t).forEach((t=>{this.applyTitleIfAvailable(t),new BootstrapPopover(t)}))}setOptions(t,e){const o=(e=e||{}).title||t.dataset.title||t.dataset.bsTitle||"",s=e.content||t.dataset.bsContent||"";t.dataset.bsTitle=o,t.dataset.bsOriginalTitle=o,t.dataset.bsContent=s,t.dataset.bsPlacement="auto",delete e.title,delete e.content;const a=BootstrapPopover.getInstance(t);a.setContent({".popover-header":o,".popover-body":s});for(const[t,o]of Object.entries(e))a._config[t]=o}show(t){BootstrapPopover.getInstance(t).show()}hide(t){BootstrapPopover.getInstance(t).hide()}destroy(t){BootstrapPopover.getInstance(t).dispose()}toggle(t){BootstrapPopover.getInstance(t).toggle()}toIterable(t){let e;if(t instanceof HTMLElement)e=[t];else{if(!(t instanceof NodeList))throw`Cannot consume element of type ${t.constructor.name}, expected NodeListOf<HTMLElement> or HTMLElement`;e=t}return e}applyTitleIfAvailable(t){const e=t.title||t.dataset.title||"";e&&(t.dataset.bsTitle=e)}}export default new Popover; \ No newline at end of file diff --git a/typo3/sysext/backend/Tests/JavaScript/popover-test.js b/typo3/sysext/backend/Tests/JavaScript/popover-test.js index f738e6e1c05d..d1953289a2bb 100644 --- a/typo3/sysext/backend/Tests/JavaScript/popover-test.js +++ b/typo3/sysext/backend/Tests/JavaScript/popover-test.js @@ -10,4 +10,4 @@ * * The TYPO3 project - inspiring people to share! */ -import $ from"jquery";import Popover from"@typo3/backend/popover.js";describe("TYPO3/CMS/Backend/PopoverTest:",(()=>{describe("initialize",(()=>{const t=$("body"),e=$('<div data-bs-toggle="popover">');t.append(e),it("works with default selector",(()=>{Popover.initialize(),expect(e[0].outerHTML).toBe('<div data-bs-toggle="popover"></div>')}));const o=$('<div data-bs-toggle="popover" data-title="foo">');t.append(o),it("works with default selector and title attribute",(()=>{Popover.initialize(),expect(o[0].outerHTML).toBe('<div data-bs-toggle="popover" data-title="foo"></div>')}));const a=$('<div data-bs-toggle="popover" data-bs-content="foo">');t.append(a),it("works with default selector and content attribute",(()=>{Popover.initialize(),expect(a[0].outerHTML).toBe('<div data-bs-toggle="popover" data-bs-content="foo"></div>')}));const i=$('<div class="t3js-popover">');t.append(i),it("works with custom selector",(()=>{Popover.initialize(".t3js-popover"),expect(i[0].outerHTML).toBe('<div class="t3js-popover"></div>')}))})),describe("call setOptions",(()=>{const t=$("body"),e=$('<div class="t3js-test-set-options" data-title="foo-title" data-bs-content="foo-content">');t.append(e),it("can set title",(()=>{Popover.initialize(".t3js-test-set-options"),expect(e.attr("data-title")).toBe("foo-title"),expect(e.attr("data-bs-content")).toBe("foo-content"),Popover.setOptions(e,{title:"bar-title"}),expect(e.attr("data-title")).toBe("foo-title"),expect(e.attr("data-bs-content")).toBe("foo-content"),expect(e.attr("data-bs-original-title")).toBe("bar-title")}));const o=$('<div class="t3js-test-set-options2" data-title="foo-title" data-bs-content="foo-content">');t.append(o),it("can set content",(()=>{Popover.initialize(".t3js-test-set-options2"),Popover.show(o),expect(o.attr("data-title")).toBe("foo-title"),expect(o.attr("data-bs-content")).toBe("foo-content"),Popover.setOptions(o,{content:"bar-content"}),expect(o.attr("data-title")).toBe("foo-title"),expect(o.attr("data-bs-content")).toBe("bar-content"),expect(o.attr("data-bs-original-title")).toBe("foo-title")}))}))})); \ No newline at end of file +import Popover from"@typo3/backend/popover.js";describe("TYPO3/CMS/Backend/PopoverTest:",(()=>{describe("initialize",(()=>{const t=document.createElement("div");t.dataset.bsToggle="popover",document.body.append(t),it("works with default selector",(()=>{Popover.initialize(),expect(t.outerHTML).toBe('<div data-bs-toggle="popover"></div>')}));const e=document.createElement("div");e.dataset.bsToggle="popover",e.dataset.title="foo",document.body.append(e),it("works with default selector and title attribute",(()=>{Popover.initialize(),expect(e.outerHTML).toBe('<div data-bs-toggle="popover" data-title="foo" data-bs-title="foo"></div>')}));const o=document.createElement("div");o.dataset.bsToggle="popover",o.dataset.bsContent="foo",document.body.append(o),it("works with default selector and content attribute",(()=>{Popover.initialize(),expect(o.outerHTML).toBe('<div data-bs-toggle="popover" data-bs-content="foo"></div>')}));const i=document.createElement("div");i.classList.add("t3js-popover"),document.body.append(i),it("works with custom selector",(()=>{Popover.initialize(".t3js-popover"),expect(i.outerHTML).toBe('<div class="t3js-popover"></div>')}))})),describe("call setOptions",(()=>{const t=document.createElement("div");t.classList.add("t3js-test-set-options"),t.dataset.title="foo-title",t.dataset.bsContent="foo-content",document.body.append(t),it("can set title",(()=>{Popover.initialize(".t3js-test-set-options"),expect(t.getAttribute("data-title")).toBe("foo-title"),expect(t.getAttribute("data-bs-content")).toBe("foo-content"),Popover.setOptions(t,{title:"bar-title"}),expect(t.getAttribute("data-title")).toBe("foo-title"),expect(t.getAttribute("data-bs-content")).toBe("foo-content"),expect(t.getAttribute("data-bs-original-title")).toBe("bar-title")}));const e=document.createElement("div");e.classList.add("t3js-test-set-options2"),e.dataset.title="foo-title",e.dataset.bsContent="foo-content",document.body.append(e),it("can set content",(()=>{Popover.initialize(".t3js-test-set-options2"),Popover.show(e),expect(e.getAttribute("data-title")).toBe("foo-title"),expect(e.getAttribute("data-bs-content")).toBe("foo-content"),Popover.setOptions(e,{content:"bar-content"}),expect(e.getAttribute("data-title")).toBe("foo-title"),expect(e.getAttribute("data-bs-content")).toBe("bar-content"),expect(e.getAttribute("data-bs-original-title")).toBe("foo-title")}))}))})); \ No newline at end of file diff --git a/typo3/sysext/core/Documentation/Changelog/12.0/Breaking-98261-RemovedJQueryInPopoverModule.rst b/typo3/sysext/core/Documentation/Changelog/12.0/Breaking-98261-RemovedJQueryInPopoverModule.rst new file mode 100644 index 000000000000..8fd1c67cac29 --- /dev/null +++ b/typo3/sysext/core/Documentation/Changelog/12.0/Breaking-98261-RemovedJQueryInPopoverModule.rst @@ -0,0 +1,60 @@ +.. include:: /Includes.rst.txt + +.. _breaking-98261-1662389392: + +=================================================== +Breaking: #98261 - Removed jQuery in Popover module +=================================================== + +See :issue:`98261` + +Description +=========== + +The support for jQuery in the module :js:`@typo3/backend/popover` has been +dropped. Passing jQuery elements to the module's methods is not possible anymore. + +This affects the following methods: + +* :js:`popover()` +* :js:`setOptions()` +* :js:`show()` +* :js:`hide()` +* :js:`destroy()` +* :js:`toggle()` + + +Impact +====== + +Calling any of the aforementioned methods with passing a jQuery-based object is +undefined and will lead to JavaScript errors. + + +Affected installations +====================== + +All 3rd party extensions using the API of the :js:`@typo3/backend/popover` module +are affected. + + +Migration +========= + +The method :js:`popover()` accepts either an object of type :js:`HTMLElement` +or a collection of type :js:`NodeList`, where all elements must be of type +:js:`HTMLElement`. + +Any other method accepts objects of type :js:`HTMLElement` only. + +Example: + +.. code-block:: js + + // Before + Popover.popover($('button.popover')); + + // After + Popover.popover(document.querySelectorAll('button.popover')); + +.. index:: Backend, JavaScript, NotScanned, ext:backend -- GitLab