Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/twbs/bootstrap.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/js
diff options
context:
space:
mode:
authorGeoSot <geo.sotis@gmail.com>2022-04-21 21:41:43 +0300
committerGitHub <noreply@github.com>2022-04-21 21:41:43 +0300
commit584600bda36ac13ea325617783216d6c6a331c08 (patch)
tree053f97d3e8401864aca9c0b8e180a3d2c089b4be /js
parent01cffa682249965eafa581058dd993853d82b3c4 (diff)
Manipulator: Add JSON parse support (#35077)
Support parsing JSON from each component's main element using the `data-bs-config` attribute. The `bs-config` attribute will be reserved and omitted during `getDataAttributes` parsing. With this commit, every component, will create its config object, using: * defaults * data-bs-config * the rest of data attributes * configuration object given during instance initialization Co-authored-by: XhmikosR <xhmikosr@gmail.com> Co-authored-by: Mark Otto <markd.otto@gmail.com> Co-authored-by: Mark Otto <markdotto@gmail.com>
Diffstat (limited to 'js')
-rw-r--r--js/src/dom/manipulator.js12
-rw-r--r--js/src/util/config.js3
-rw-r--r--js/tests/unit/dom/manipulator.spec.js27
-rw-r--r--js/tests/unit/tooltip.spec.js11
-rw-r--r--js/tests/unit/util/config.spec.js88
5 files changed, 132 insertions, 9 deletions
diff --git a/js/src/dom/manipulator.js b/js/src/dom/manipulator.js
index 5e6ad92ae7..2d96d65fc8 100644
--- a/js/src/dom/manipulator.js
+++ b/js/src/dom/manipulator.js
@@ -22,7 +22,15 @@ function normalizeData(value) {
return null
}
- return value
+ if (typeof value !== 'string') {
+ return value
+ }
+
+ try {
+ return JSON.parse(decodeURIComponent(value))
+ } catch {
+ return value
+ }
}
function normalizeDataKey(key) {
@@ -44,7 +52,7 @@ const Manipulator = {
}
const attributes = {}
- const bsKeys = Object.keys(element.dataset).filter(key => key.startsWith('bs'))
+ const bsKeys = Object.keys(element.dataset).filter(key => key.startsWith('bs') && !key.startsWith('bsConfig'))
for (const key of bsKeys) {
let pureKey = key.replace(/^bs/, '')
diff --git a/js/src/util/config.js b/js/src/util/config.js
index 19d02955dd..f6c194276b 100644
--- a/js/src/util/config.js
+++ b/js/src/util/config.js
@@ -38,8 +38,11 @@ class Config {
}
_mergeConfigObj(config, element) {
+ const jsonConfig = isElement(element) ? Manipulator.getDataAttribute(element, 'config') : {} // try to parse
+
return {
...this.constructor.Default,
+ ...(typeof jsonConfig === 'object' ? jsonConfig : {}),
...(isElement(element) ? Manipulator.getDataAttributes(element) : {}),
...(typeof config === 'object' ? config : {})
}
diff --git a/js/tests/unit/dom/manipulator.spec.js b/js/tests/unit/dom/manipulator.spec.js
index 2ed1995b60..4561e2e46c 100644
--- a/js/tests/unit/dom/manipulator.spec.js
+++ b/js/tests/unit/dom/manipulator.spec.js
@@ -70,6 +70,17 @@ describe('Manipulator', () => {
target: '#element'
})
})
+
+ it('should omit `bs-config` data attribute', () => {
+ fixtureEl.innerHTML = '<div data-bs-toggle="tabs" data-bs-target="#element" data-bs-config=\'{"testBool":false}\'></div>'
+
+ const div = fixtureEl.querySelector('div')
+
+ expect(Manipulator.getDataAttributes(div)).toEqual({
+ toggle: 'tabs',
+ target: '#element'
+ })
+ })
})
describe('getDataAttribute', () => {
@@ -104,5 +115,21 @@ describe('Manipulator', () => {
div.setAttribute('data-bs-test', '1')
expect(Manipulator.getDataAttribute(div, 'test')).toEqual(1)
})
+
+ it('should normalize json data', () => {
+ fixtureEl.innerHTML = '<div data-bs-test=\'{"delay":{"show":100,"hide":10}}\'></div>'
+
+ const div = fixtureEl.querySelector('div')
+
+ expect(Manipulator.getDataAttribute(div, 'test')).toEqual({ delay: { show: 100, hide: 10 } })
+
+ const objectData = { 'Super Hero': ['Iron Man', 'Super Man'], testNum: 90, url: 'http://localhost:8080/test?foo=bar' }
+ const dataStr = JSON.stringify(objectData)
+ div.setAttribute('data-bs-test', encodeURIComponent(dataStr))
+ expect(Manipulator.getDataAttribute(div, 'test')).toEqual(objectData)
+
+ div.setAttribute('data-bs-test', dataStr)
+ expect(Manipulator.getDataAttribute(div, 'test')).toEqual(objectData)
+ })
})
})
diff --git a/js/tests/unit/tooltip.spec.js b/js/tests/unit/tooltip.spec.js
index 5267305a54..ff44d4182a 100644
--- a/js/tests/unit/tooltip.spec.js
+++ b/js/tests/unit/tooltip.spec.js
@@ -730,15 +730,12 @@ describe('Tooltip', () => {
it('should not hide tooltip if leave event occurs and enter event occurs within the hide delay', () => {
return new Promise(resolve => {
- fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
+ fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip" data-bs-delay=\'{"show":0,"hide":150}\'>'
const tooltipEl = fixtureEl.querySelector('a')
- const tooltip = new Tooltip(tooltipEl, {
- delay: {
- show: 0,
- hide: 150
- }
- })
+ const tooltip = new Tooltip(tooltipEl)
+
+ expect(tooltip._config.delay).toEqual({ show: 0, hide: 150 })
setTimeout(() => {
expect(tooltip._getTipElement()).toHaveClass('show')
diff --git a/js/tests/unit/util/config.spec.js b/js/tests/unit/util/config.spec.js
index a8f8962ee3..e1693c0c1f 100644
--- a/js/tests/unit/util/config.spec.js
+++ b/js/tests/unit/util/config.spec.js
@@ -1,4 +1,5 @@
import Config from '../../../src/util/config'
+import { clearFixture, getFixture } from '../../helpers/fixture'
class DummyConfigClass extends Config {
static get NAME() {
@@ -7,7 +8,17 @@ class DummyConfigClass extends Config {
}
describe('Config', () => {
+ let fixtureEl
const name = 'dummy'
+
+ beforeAll(() => {
+ fixtureEl = getFixture()
+ })
+
+ afterEach(() => {
+ clearFixture()
+ })
+
describe('NAME', () => {
it('should return plugin NAME', () => {
expect(DummyConfigClass.NAME).toEqual(name)
@@ -26,6 +37,83 @@ describe('Config', () => {
})
})
+ describe('mergeConfigObj', () => {
+ it('should parse element\'s data attributes and merge it with default config. Element\'s data attributes must excel Defaults', () => {
+ fixtureEl.innerHTML = '<div id="test" data-bs-test-bool="false" data-bs-test-int="8" data-bs-test-string1="bar"></div>'
+
+ spyOnProperty(DummyConfigClass, 'Default', 'get').and.returnValue({
+ testBool: true,
+ testString: 'foo',
+ testString1: 'foo',
+ testInt: 7
+ })
+ const instance = new DummyConfigClass()
+ const configResult = instance._mergeConfigObj({}, fixtureEl.querySelector('#test'))
+
+ expect(configResult.testBool).toEqual(false)
+ expect(configResult.testString).toEqual('foo')
+ expect(configResult.testString1).toEqual('bar')
+ expect(configResult.testInt).toEqual(8)
+ })
+
+ it('should parse element\'s data attributes and merge it with default config, plug these given during method call. The programmatically given should excel all', () => {
+ fixtureEl.innerHTML = '<div id="test" data-bs-test-bool="false" data-bs-test-int="8" data-bs-test-string-1="bar"></div>'
+
+ spyOnProperty(DummyConfigClass, 'Default', 'get').and.returnValue({
+ testBool: true,
+ testString: 'foo',
+ testString1: 'foo',
+ testInt: 7
+ })
+ const instance = new DummyConfigClass()
+ const configResult = instance._mergeConfigObj({
+ testString1: 'test',
+ testInt: 3
+ }, fixtureEl.querySelector('#test'))
+
+ expect(configResult.testBool).toEqual(false)
+ expect(configResult.testString).toEqual('foo')
+ expect(configResult.testString1).toEqual('test')
+ expect(configResult.testInt).toEqual(3)
+ })
+
+ it('should parse element\'s data attribute `config` and any rest attributes. The programmatically given should excel all. Data attribute `config` should excel only Defaults', () => {
+ fixtureEl.innerHTML = '<div id="test" data-bs-config=\'{"testBool":false,"testInt":50,"testInt2":100}\' data-bs-test-int="8" data-bs-test-string-1="bar"></div>'
+
+ spyOnProperty(DummyConfigClass, 'Default', 'get').and.returnValue({
+ testBool: true,
+ testString: 'foo',
+ testString1: 'foo',
+ testInt: 7,
+ testInt2: 600
+ })
+ const instance = new DummyConfigClass()
+ const configResult = instance._mergeConfigObj({
+ testString1: 'test'
+ }, fixtureEl.querySelector('#test'))
+
+ expect(configResult.testBool).toEqual(false)
+ expect(configResult.testString).toEqual('foo')
+ expect(configResult.testString1).toEqual('test')
+ expect(configResult.testInt).toEqual(8)
+ expect(configResult.testInt2).toEqual(100)
+ })
+
+ it('should omit element\'s data attribute `config` if is not an object', () => {
+ fixtureEl.innerHTML = '<div id="test" data-bs-config="foo" data-bs-test-int="8"></div>'
+
+ spyOnProperty(DummyConfigClass, 'Default', 'get').and.returnValue({
+ testInt: 7,
+ testInt2: 79
+ })
+ const instance = new DummyConfigClass()
+ const configResult = instance._mergeConfigObj({}, fixtureEl.querySelector('#test'))
+
+ expect(configResult.testInt).toEqual(8)
+ expect(configResult.testInt2).toEqual(79)
+ })
+ })
+
describe('typeCheckConfig', () => {
it('should check type of the config object', () => {
spyOnProperty(DummyConfigClass, 'DefaultType', 'get').and.returnValue({