Skip to content

Commit

Permalink
Merge branch 'collapse-multiple-target' into current
Browse files Browse the repository at this point in the history
* collapse-multiple-target:
  Use $(document).find(selector) to avoid case in twbs#20184
  Muti-target support for collapse plugin
  make getTargets to always return a JQuery to avoid calling JQuery on the same element further down
  Add a dropdown test case for twbs#21328
  Simplify targets.length test
  Simplify null check when possible
  Rework getSelectorFromElement to not rely on regex

# Conflicts:
#	js/src/alert.js
#	js/src/dropdown.js
#	js/tests/unit/collapse.js
  • Loading branch information
pvdlg committed Jan 27, 2017
2 parents 2d0a7fe + a504979 commit 5f66fa3
Show file tree
Hide file tree
Showing 11 changed files with 225 additions and 103 deletions.
38 changes: 37 additions & 1 deletion docs/components/collapse.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,42 @@ You can use a link with the `href` attribute, or a button with the `data-target`
</div>
{% endexample %}

## Multiple triggers / targets

A `<button>` or `<a>` can show and hide multiple elements by referencing them with a JQuery selector in its `href` or `data-target` attribute.
Multiple `<button>` or `<a>` can show and hide an element if they each reference it with their `href` or `data-target` attribute

{% example html %}
<p>
<a class="btn btn-primary" data-toggle="collapse" href="#multiCollapseExample1" aria-expanded="false" aria-controls="multiCollapseExample1">
Toggle first element
</a>
<button class="btn btn-primary" type="button" data-toggle="collapse" data-target="#multiCollapseExample2" aria-expanded="false" aria-controls="multiCollapseExample1">
Toggle second element
</button>
<button class="btn btn-primary" type="button" data-toggle="collapse" data-target=".multi-collapse" aria-expanded="false" aria-controls="multiCollapseExample1 multiCollapseExample2">
Toggle both elements
</button>
</p>
<div class="row">
<div class="col">
<div class="collapse multi-collapse" id="multiCollapseExample1">
<div class="card card-block">
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.
</div>
</div>
</div>
<div class="col">
<div class="collapse multi-collapse" id="multiCollapseExample2">
<div class="card card-block">
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.
</div>
</div>
</div>
</div>

{% endexample %}

## Accordion example

Using the [card]({{ site.baseurl }}/components/card) component, you can extend the default collapse behavior to create an accordion.
Expand Down Expand Up @@ -108,7 +144,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 control, add the data attribute `data-parent="#selector"`. Refer to the demo to see this in action.

Expand Down
25 changes: 7 additions & 18 deletions js/src/alert.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,14 @@ const Alert = (($) => {
close(element) {
element = element || this._element

const rootElement = this._getRootElement(element)
const customEvent = this._triggerCloseEvent(rootElement)
const $rootElement = this._getRootElement(element)
const customEvent = this._triggerCloseEvent($rootElement)

if (customEvent.isDefaultPrevented()) {
return
}

$(rootElement).transition(() => $(rootElement).removeClass(ClassName.SHOW), () => this._destroyElement(rootElement))
$rootElement.transition(() => $rootElement.removeClass(ClassName.SHOW), () => this._destroyElement($rootElement))
}

dispose() {
Expand All @@ -84,18 +84,8 @@ const Alert = (($) => {
// private

_getRootElement(element) {
const selector = Util.getSelectorFromElement(element)
let parent = false

if (selector) {
parent = $(selector)[0]
}

if (!parent) {
parent = $(element).closest(`.${ClassName.ALERT}`)[0]
}

return parent
const targets = Util.getTargets(element)
return (targets.length ? targets : $(element).closest(`.${ClassName.ALERT}`)).first()
}

_triggerCloseEvent(element) {
Expand All @@ -105,14 +95,13 @@ const Alert = (($) => {
return closeEvent
}

_destroyElement(element) {
$(element)
_destroyElement($element) {
$element
.detach()
.trigger(Event.CLOSED)
.remove()
}


// static

static _jQueryInterface(config) {
Expand Down
16 changes: 5 additions & 11 deletions js/src/carousel.js
Original file line number Diff line number Diff line change
Expand Up @@ -421,29 +421,23 @@ const Carousel = (($) => {
}

static _dataApiClickHandler(event) {
const selector = Util.getSelectorFromElement(this)
const $target = Util.getTargets(this).first()

if (!selector) {
if (!$target.hasClass(ClassName.CAROUSEL)) {
return
}

const target = $(selector)[0]

if (!target || !$(target).hasClass(ClassName.CAROUSEL)) {
return
}

const config = $.extend({}, $(target).data(), $(this).data())
const config = $.extend({}, $target.data(), $(this).data())
const slideIndex = this.getAttribute('data-slide-to')

if (slideIndex) {
config.interval = false
}

Carousel._jQueryInterface.call($(target), config)
Carousel._jQueryInterface.call($target, config)

if (slideIndex) {
$(target).data(DATA_KEY).to(slideIndex)
$target.data(DATA_KEY).to(slideIndex)
}

event.preventDefault()
Expand Down
42 changes: 23 additions & 19 deletions js/src/collapse.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,14 @@ const Collapse = (($) => {
this._isTransitioning = false
this._element = element
this._config = this._getConfig(config)
this._triggerArray = $.makeArray($(
`[data-toggle="collapse"][href="#${element.id}"],` +
`[data-toggle="collapse"][data-target="#${element.id}"]`
))

const triggerArray = []
$(Selector.DATA_TOGGLE).each(function () {
if (Util.getTargets(this).filter(element).length) {
triggerArray.push(this)
}
})
this._triggerArray = triggerArray

this._parent = this._config.parent ? this._getParent() : null

Expand Down Expand Up @@ -215,10 +219,14 @@ const Collapse = (($) => {

this._element.setAttribute('aria-expanded', false)

// Remove COLLAPSED and set aria-expanded=false to each trigger for which all its targets are not showed
if (this._triggerArray.length) {
$(this._triggerArray)
.addClass(ClassName.COLLAPSED)
.attr('aria-expanded', false)
$(this._triggerArray).each(function () {
if (!Util.getTargets(this).hasClass(ClassName.SHOW)) {
$(this).addClass(ClassName.COLLAPSED)
.attr('aria-expanded', false)
}
})
}

const start = () => {
Expand Down Expand Up @@ -273,7 +281,7 @@ const Collapse = (($) => {

$(parent).find(selector).each((i, element) => {
this._addAriaAndCollapsedClass(
Collapse._getTargetFromElement(element),
Util.getTargets(element)[0],
[element]
)
})
Expand All @@ -297,11 +305,6 @@ const Collapse = (($) => {

// static

static _getTargetFromElement(element) {
const selector = Util.getSelectorFromElement(element)
return selector ? $(selector)[0] : null
}

static _jQueryInterface(config) {
return this.each(function () {
const $this = $(this)
Expand Down Expand Up @@ -342,12 +345,13 @@ const Collapse = (($) => {

$(document).on(Event.CLICK_DATA_API, Selector.DATA_TOGGLE, function (event) {
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)
Util.getTargets(this).each(function () {
const $target = $(this)
const data = $target.data(DATA_KEY)
const config = data ? 'toggle' : $trigger.data()
Collapse._jQueryInterface.call($target, config)
})
})


Expand Down
38 changes: 16 additions & 22 deletions js/src/dropdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@ const Dropdown = (($) => {
return false
}

const parent = Dropdown._getParentFromElement(this)
const isActive = $(parent).hasClass(ClassName.SHOW)
const $parent = Dropdown._getParentFromElement(this)
const isActive = $parent.hasClass(ClassName.SHOW)

Dropdown._clearMenus()

Expand All @@ -100,7 +100,7 @@ const Dropdown = (($) => {
}
const showEvent = $.Event(Event.SHOW, relatedTarget)

$(parent).trigger(showEvent)
$parent.trigger(showEvent)

if (showEvent.isDefaultPrevented()) {
return false
Expand All @@ -120,8 +120,8 @@ const Dropdown = (($) => {
this.focus()
this.setAttribute('aria-expanded', true)

$(parent).toggleClass(ClassName.SHOW)
$(parent).trigger($.Event(Event.SHOWN, relatedTarget))
$parent.toggleClass(ClassName.SHOW)
$parent.trigger($.Event(Event.SHOWN, relatedTarget))

return false
}
Expand Down Expand Up @@ -168,23 +168,23 @@ const Dropdown = (($) => {
const toggles = $.makeArray($(Selector.DATA_TOGGLE))

for (let i = 0; i < toggles.length; i++) {
const parent = Dropdown._getParentFromElement(toggles[i])
const $parent = Dropdown._getParentFromElement(toggles[i])
const relatedTarget = {
relatedTarget : toggles[i]
}

if (!$(parent).hasClass(ClassName.SHOW)) {
if (!$parent.hasClass(ClassName.SHOW)) {
continue
}

if (event && (event.type === 'click' &&
/input|textarea/i.test(event.target.tagName) || event.type === 'focusin')
&& $.contains(parent, event.target)) {
&& $.contains($parent[0], event.target)) {
continue
}

const hideEvent = $.Event(Event.HIDE, relatedTarget)
$(parent).trigger(hideEvent)
$parent.trigger(hideEvent)
if (hideEvent.isDefaultPrevented()) {
continue
}
Expand All @@ -197,21 +197,15 @@ const Dropdown = (($) => {

toggles[i].setAttribute('aria-expanded', 'false')

$(parent)
$parent
.removeClass(ClassName.SHOW)
.trigger($.Event(Event.HIDDEN, relatedTarget))
}
}

static _getParentFromElement(element) {
let parent
const selector = Util.getSelectorFromElement(element)

if (selector) {
parent = $(selector)[0]
}

return parent || element.parentNode
const targets = Util.getTargets(element)
return targets.length ? targets.first() : $(element.parentNode)
}

static _dataApiKeydownHandler(event) {
Expand All @@ -227,22 +221,22 @@ const Dropdown = (($) => {
return
}

const parent = Dropdown._getParentFromElement(this)
const isActive = $(parent).hasClass(ClassName.SHOW)
const $parent = Dropdown._getParentFromElement(this)
const isActive = $parent.hasClass(ClassName.SHOW)

if (!isActive && (event.which !== ESCAPE_KEYCODE || event.which !== SPACE_KEYCODE) ||
isActive && (event.which === ESCAPE_KEYCODE || event.which === SPACE_KEYCODE)) {

if (event.which === ESCAPE_KEYCODE) {
const toggle = $(parent).find(Selector.DATA_TOGGLE)[0]
const toggle = $parent.find(Selector.DATA_TOGGLE)[0]
$(toggle).trigger('focus')
}

$(this).trigger('click')
return
}

const items = $(parent).find(Selector.VISIBLE_ITEMS).get()
const items = $parent.find(Selector.VISIBLE_ITEMS).get()

if (!items.length) {
return
Expand Down
15 changes: 5 additions & 10 deletions js/src/modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -443,21 +443,16 @@ const Modal = (($) => {
*/

$(document).on(Event.CLICK_DATA_API, Selector.DATA_TOGGLE, function (event) {
let target
const selector = Util.getSelectorFromElement(this)
const $target = Util.getTargets(this).first()

if (selector) {
target = $(selector)[0]
}

const config = $(target).data(DATA_KEY) ?
'toggle' : $.extend({}, $(target).data(), $(this).data())
const config = $target.data(DATA_KEY) ?
'toggle' : $.extend({}, $target.data(), $(this).data())

if (this.tagName === 'A' || this.tagName === 'AREA') {
event.preventDefault()
}

const $target = $(target).one(Event.SHOW, (showEvent) => {
$target.one(Event.SHOW, (showEvent) => {
if (showEvent.isDefaultPrevented()) {
// only register focus restorer if modal will actually get shown
return
Expand All @@ -470,7 +465,7 @@ const Modal = (($) => {
})
})

Modal._jQueryInterface.call($(target), config, this)
Modal._jQueryInterface.call($target, config, this)
})


Expand Down
9 changes: 2 additions & 7 deletions js/src/scrollspy.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,18 +126,13 @@ const ScrollSpy = (($) => {

targets
.map((element) => {
let target
const targetSelector = Util.getSelectorFromElement(element)

if (targetSelector) {
target = $(targetSelector)[0]
}
const target = Util.getTargets(element)[0]

if (target && (target.offsetWidth || target.offsetHeight)) {
// todo (fat): remove sketch reliance on jQuery position/offset
return [
$(target)[offsetMethod]().top + offsetBase,
targetSelector
`#${$(target).attr('id')}`
]
}
return null
Expand Down
Loading

0 comments on commit 5f66fa3

Please sign in to comment.