diff --git a/docs/4.0/components/collapse.md b/docs/4.0/components/collapse.md index cbdc50bb72d9..e1d3e3b64442 100644 --- a/docs/4.0/components/collapse.md +++ b/docs/4.0/components/collapse.md @@ -32,6 +32,35 @@ You can use a link with the `href` attribute, or a button with the `data-target` {% endexample %} +## Multiple triggers / targets + +A ` + +

+
+
+
+
+ Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. +
+
+
+
+
+
+ Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. +
+
+
+
+{% endexample %} + ## Accordion example Using the [card]({{ site.baseurl }}/docs/{{ site.docs_version }}/components/card/) component, you can extend the default collapse behavior to create an accordion. @@ -129,7 +158,7 @@ These classes can be found in `_transitions.scss`. ### Via data attributes -Just add `data-toggle="collapse"` and a `data-target` to the element to automatically assign control of a collapsible element. The `data-target` attribute accepts a CSS selector to apply the collapse to. Be sure to add the class `collapse` to the collapsible element. If you'd like it to default open, add the additional class `show`. +Just add `data-toggle="collapse"` and a `data-target` to the element to automatically assign control of one or more collapsible elements. The `data-target` attribute accepts a CSS selector to apply the collapse to. Be sure to add the class `collapse` to the collapsible element. If you'd like it to default open, add the additional class `show`. To add accordion-like group management to a collapsible area, add the data attribute `data-parent="#selector"`. Refer to the demo to see this in action. diff --git a/js/src/collapse.js b/js/src/collapse.js index bf1c738f56c8..78ed32906bc3 100644 --- a/js/src/collapse.js +++ b/js/src/collapse.js @@ -77,6 +77,14 @@ const Collapse = (($) => { `[data-toggle="collapse"][href="#${element.id}"],` + `[data-toggle="collapse"][data-target="#${element.id}"]` )) + const tabToggles = $(Selector.DATA_TOGGLE) + for (let i = 0; i < tabToggles.length; i++) { + const elem = tabToggles[i] + const selector = Util.getSelectorFromElement(elem) + if (selector !== null && $(selector).filter(element).length > 0) { + this._triggerArray.push(elem) + } + } this._parent = this._config.parent ? this._getParent() : null @@ -215,9 +223,17 @@ const Collapse = (($) => { .removeClass(ClassName.SHOW) if (this._triggerArray.length) { - $(this._triggerArray) - .addClass(ClassName.COLLAPSED) - .attr('aria-expanded', false) + for (let i = 0; i < this._triggerArray.length; i++) { + const trigger = this._triggerArray[i] + const selector = Util.getSelectorFromElement(trigger) + if (selector !== null) { + const $elem = $(selector) + if (!$elem.hasClass(ClassName.SHOW)) { + $(trigger).addClass(ClassName.COLLAPSED) + .attr('aria-expanded', false) + } + } + } } this.setTransitioning(true) @@ -349,11 +365,14 @@ const Collapse = (($) => { event.preventDefault() } - const target = Collapse._getTargetFromElement(this) - const data = $(target).data(DATA_KEY) - const config = data ? 'toggle' : $(this).data() - - Collapse._jQueryInterface.call($(target), config) + const $trigger = $(this) + const selector = Util.getSelectorFromElement(this) + $(selector).each(function () { + const $target = $(this) + const data = $target.data(DATA_KEY) + const config = data ? 'toggle' : $trigger.data() + Collapse._jQueryInterface.call($target, config) + }) }) diff --git a/js/tests/unit/collapse.js b/js/tests/unit/collapse.js index 2b9d0e58fc0b..0e9e8b6a73dd 100644 --- a/js/tests/unit/collapse.js +++ b/js/tests/unit/collapse.js @@ -52,8 +52,28 @@ $(function () { assert.ok(!/height/i.test($el.attr('style')), 'has height reset') }) + + QUnit.test('should show multiple collapsed elements', function (assert) { + assert.expect(4) + var done = assert.async() + var $target = $('
').appendTo('#qunit-fixture') + var $trigger2 = $('').appendTo('#qunit-fixture') + var $trigger3 = $('').appendTo('#qunit-fixture') + + var $target1 = $('
').appendTo('#qunit-fixture') + var $target2 = $('
').appendTo('#qunit-fixture') + + $target2.one('shown.bs.collapse', function () { + assert.ok(!$trigger1.hasClass('collapsed'), 'trigger1 does not have collapsed class') + assert.ok(!$trigger2.hasClass('collapsed'), 'trigger2 does not have collapsed class') + assert.ok(!$trigger3.hasClass('collapsed'), 'trigger3 does not have collapsed class') + $target2.one('hidden.bs.collapse', function () { + assert.ok(!$trigger1.hasClass('collapsed'), 'trigger1 does not have collapsed class') + assert.ok($trigger2.hasClass('collapsed'), 'trigger2 has collapsed class') + assert.ok(!$trigger3.hasClass('collapsed'), 'trigger3 does not have collapsed class') + $target1.one('hidden.bs.collapse', function () { + assert.ok($trigger1.hasClass('collapsed'), 'trigger1 has collapsed class') + assert.ok($trigger2.hasClass('collapsed'), 'trigger2 has collapsed class') + assert.ok($trigger3.hasClass('collapsed'), 'trigger3 has collapsed class') + done() + }) + $trigger1.trigger('click') + }) + $trigger2.trigger('click') + }) + $trigger3.trigger('click') + }) + + QUnit.test('should set aria-expanded="true" to triggers targetting shown collaspe and aria-expanded="false" only when all the targeted collapses are shown', function (assert) { + assert.expect(9) + var done = assert.async() + + var $trigger1 = $('').appendTo('#qunit-fixture') + var $trigger2 = $('').appendTo('#qunit-fixture') + var $trigger3 = $('').appendTo('#qunit-fixture') + + var $target1 = $('
').appendTo('#qunit-fixture') + var $target2 = $('
').appendTo('#qunit-fixture') + + $target2.one('shown.bs.collapse', function () { + assert.strictEqual($trigger1.attr('aria-expanded'), 'true', 'aria-expanded on trigger1 is "true"') + assert.strictEqual($trigger2.attr('aria-expanded'), 'true', 'aria-expanded on trigger2 is "true"') + assert.strictEqual($trigger3.attr('aria-expanded'), 'true', 'aria-expanded on trigger3 is "true"') + $target2.one('hidden.bs.collapse', function () { + assert.strictEqual($trigger1.attr('aria-expanded'), 'true', 'aria-expanded on trigger1 is "true"') + assert.strictEqual($trigger2.attr('aria-expanded'), 'false', 'aria-expanded on trigger2 is "false"') + assert.strictEqual($trigger3.attr('aria-expanded'), 'true', 'aria-expanded on trigger3 is "true"') + $target1.one('hidden.bs.collapse', function () { + assert.strictEqual($trigger1.attr('aria-expanded'), 'false', 'aria-expanded on trigger1 is "fasle"') + assert.strictEqual($trigger2.attr('aria-expanded'), 'false', 'aria-expanded on trigger2 is "false"') + assert.strictEqual($trigger3.attr('aria-expanded'), 'false', 'aria-expanded on trigger3 is "false"') + done() + }) + $trigger1.trigger('click') + }) + $trigger2.trigger('click') + }) + $trigger3.trigger('click') + }) })