diff options
author | GeoSot <geo.sotis@gmail.com> | 2021-11-25 20:14:02 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-11-25 20:14:02 +0300 |
commit | 94a596fbcb1011ba990da2078ba7e20b39dba2d9 (patch) | |
tree | 26af41580d5cae017e32e29cfef96178e897afa6 /js/src/tooltip.js | |
parent | fa33e83f25faf8c378b99126fbd69977e667ad9a (diff) |
Add a template factory helper to handle all template cases (#34519)
Co-authored-by: XhmikosR <xhmikosr@gmail.com>
Diffstat (limited to 'js/src/tooltip.js')
-rw-r--r-- | js/src/tooltip.js | 129 |
1 files changed, 47 insertions, 82 deletions
diff --git a/js/src/tooltip.js b/js/src/tooltip.js index f069dc7515..c845961011 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -11,17 +11,16 @@ import { findShadowRoot, getElement, getUID, - isElement, isRTL, noop, typeCheckConfig } from './util/index' -import { DefaultAllowlist, sanitizeHtml } from './util/sanitizer' +import { DefaultAllowlist } from './util/sanitizer' import Data from './dom/data' import EventHandler from './dom/event-handler' import Manipulator from './dom/manipulator' -import SelectorEngine from './dom/selector-engine' import BaseComponent from './base-component' +import TemplateFactory from './util/template-factory' /** * Constants @@ -40,6 +39,7 @@ const CLASS_NAME_SHOW = 'show' const HOVER_STATE_SHOW = 'show' const HOVER_STATE_OUT = 'out' +const SELECTOR_TOOLTIP_ARROW = '.tooltip-arrow' const SELECTOR_TOOLTIP_INNER = '.tooltip-inner' const SELECTOR_MODAL = `.${CLASS_NAME_MODAL}` @@ -132,6 +132,7 @@ class Tooltip extends BaseComponent { this._hoverState = '' this._activeTrigger = {} this._popper = null + this._templateFactory = null // Protected this._config = this._getConfig(config) @@ -227,23 +228,9 @@ class Tooltip extends BaseComponent { return } - // A trick to recreate a tooltip in case a new title is given by using the NOT documented `data-bs-original-title` - // This will be removed later in favor of a `setContent` method - if (this.constructor.NAME === 'tooltip' && this.tip && this.getTitle() !== this.tip.querySelector(SELECTOR_TOOLTIP_INNER).innerHTML) { - this._disposePopper() - this.tip.remove() - this.tip = null - } - const tip = this.getTipElement() - const tipId = getUID(this.constructor.NAME) - - tip.setAttribute('id', tipId) - this._element.setAttribute('aria-describedby', tipId) - if (this._config.animation) { - tip.classList.add(CLASS_NAME_FADE) - } + this._element.setAttribute('aria-describedby', tip.getAttribute('id')) const placement = typeof this._config.placement === 'function' ? this._config.placement.call(this, tip, this._element) : @@ -268,11 +255,6 @@ class Tooltip extends BaseComponent { tip.classList.add(CLASS_NAME_SHOW) - const customClass = this._resolvePossibleFunction(this._config.customClass) - if (customClass) { - tip.classList.add(...customClass.split(' ')) - } - // If this is a touch-enabled device we add extra // empty mouseover listeners to the body's immediate children; // only needed because of broken event delegation on iOS @@ -360,69 +342,63 @@ class Tooltip extends BaseComponent { return this.tip } - const element = document.createElement('div') - element.innerHTML = this._config.template + const templateFactory = this._getTemplateFactory(this._getContentForTemplate()) - const tip = element.children[0] - this.setContent(tip) + const tip = templateFactory.toHtml() tip.classList.remove(CLASS_NAME_FADE, CLASS_NAME_SHOW) - this.tip = tip - return this.tip - } - - setContent(tip) { - this._sanitizeAndSetContent(tip, this.getTitle(), SELECTOR_TOOLTIP_INNER) - } + const tipId = getUID(this.constructor.NAME).toString() - _sanitizeAndSetContent(template, content, selector) { - const templateElement = SelectorEngine.findOne(selector, template) + tip.setAttribute('id', tipId) - if (!content && templateElement) { - templateElement.remove() - return + if (this._config.animation) { + tip.classList.add(CLASS_NAME_FADE) } - // we use append for html objects to maintain js events - this.setElementContent(templateElement, content) + this.tip = tip + return this.tip } - setElementContent(element, content) { - if (element === null) { - return + setContent(content) { + let isShown = false + if (this.tip) { + isShown = this.tip.classList.contains(CLASS_NAME_SHOW) + this.tip.remove() } - if (isElement(content)) { - content = getElement(content) + this._disposePopper() - // content is a DOM node or a jQuery - if (this._config.html) { - if (content.parentNode !== element) { - element.innerHTML = '' - element.append(content) - } - } else { - element.textContent = content.textContent - } + this.tip = this._getTemplateFactory(content).toHtml() - return + if (isShown) { + this.show() } + } - if (this._config.html) { - if (this._config.sanitize) { - content = sanitizeHtml(content, this._config.allowList, this._config.sanitizeFn) - } - - element.innerHTML = content // lgtm [js/xss-through-dom] + _getTemplateFactory(content) { + if (this._templateFactory) { + this._templateFactory.changeContent(content) } else { - element.textContent = content + this._templateFactory = new TemplateFactory({ + ...this._config, + // the `content` var has to be after `this._config` + // to override config.content in case of popover + content, + extraClass: this._resolvePossibleFunction(this._config.customClass) + }) } + + return this._templateFactory } - getTitle() { - const title = this._element.getAttribute('data-bs-original-title') || this._config.title + _getContentForTemplate() { + return { + [SELECTOR_TOOLTIP_INNER]: this.getTitle() + } + } - return this._resolvePossibleFunction(title) + getTitle() { + return this._resolvePossibleFunction(this._config.title) || this._element.getAttribute('title') } updateAttachment(attachment) { @@ -456,8 +432,8 @@ class Tooltip extends BaseComponent { return offset } - _resolvePossibleFunction(content) { - return typeof content === 'function' ? content.call(this._element) : content + _resolvePossibleFunction(arg) { + return typeof arg === 'function' ? arg.call(this._element) : arg } _getPopperConfig(attachment) { @@ -485,7 +461,7 @@ class Tooltip extends BaseComponent { { name: 'arrow', options: { - element: `.${this.constructor.NAME}-arrow` + element: SELECTOR_TOOLTIP_ARROW } }, { @@ -556,15 +532,9 @@ class Tooltip extends BaseComponent { _fixTitle() { const title = this._element.getAttribute('title') - const originalTitleType = typeof this._element.getAttribute('data-bs-original-title') - if (title || originalTitleType !== 'string') { - this._element.setAttribute('data-bs-original-title', title || '') - if (title && !this._element.getAttribute('aria-label') && !this._element.textContent) { - this._element.setAttribute('aria-label', title) - } - - this._element.setAttribute('title', '') + if (title && !this._element.getAttribute('aria-label') && !this._element.textContent) { + this._element.setAttribute('aria-label', title) } } @@ -670,11 +640,6 @@ class Tooltip extends BaseComponent { } typeCheckConfig(NAME, config, this.constructor.DefaultType) - - if (config.sanitize) { - config.template = sanitizeHtml(config.template, config.allowList, config.sanitizeFn) - } - return config } |