From e38d9faa344e064d13fccdb8b7fb5d4e359f8ec7 Mon Sep 17 00:00:00 2001 From: "Patrick H. Lauke" Date: Mon, 24 Jun 2019 21:01:44 +0100 Subject: [PATCH] Ensure button plugin sets/removes active class correctly on page load (#28952) * Ensure correct active class is set on button toggles/checkboxes/radios on page load Sanity check, ensures that the UI visually matches the actual values/states of controls. Also ensures that if any autocomplete/autofill happened, this is visually accounted for by having the correct class set. Includes unit tests (and `autocomplete` has been removed from these as it's no longer necessary) * Remove now unnecessary autocomplete attribute As the attribute was there to force/ensure that the visual presentation matched the state, and this is now taken care of programmatically, there's no need to unnecessarily suppress autocomplete...let them autocomplete if they want to... --- js/src/button.js | 44 ++++++++++++--- js/tests/unit/button.js | 80 ++++++++++++++++++++++++---- js/tests/visual/button.html | 14 ++--- site/docs/4.3/components/buttons.md | 10 ++-- site/docs/4.3/components/progress.md | 2 +- 5 files changed, 120 insertions(+), 30 deletions(-) diff --git a/js/src/button.js b/js/src/button.js index 622513701968..70e6d379942f 100644 --- a/js/src/button.js +++ b/js/src/button.js @@ -27,17 +27,20 @@ const ClassName = { } const Selector = { - DATA_TOGGLE_CARROT : '[data-toggle^="button"]', - DATA_TOGGLE : '[data-toggle="buttons"]', - INPUT : 'input:not([type="hidden"])', - ACTIVE : '.active', - BUTTON : '.btn' + DATA_TOGGLE_CARROT : '[data-toggle^="button"]', + DATA_TOGGLES : '[data-toggle="buttons"]', + DATA_TOGGLE : '[data-toggle="button"]', + DATA_TOGGLES_BUTTONS : '[data-toggle="buttons"] .btn', + INPUT : 'input:not([type="hidden"])', + ACTIVE : '.active', + BUTTON : '.btn' } const Event = { CLICK_DATA_API : `click${EVENT_KEY}${DATA_API_KEY}`, FOCUS_BLUR_DATA_API : `focus${EVENT_KEY}${DATA_API_KEY} ` + - `blur${EVENT_KEY}${DATA_API_KEY}` + `blur${EVENT_KEY}${DATA_API_KEY}`, + LOAD_DATA_API : `load${EVENT_KEY}${DATA_API_KEY}` } /** @@ -63,7 +66,7 @@ class Button { let triggerChangeEvent = true let addAriaPressed = true const rootElement = $(this._element).closest( - Selector.DATA_TOGGLE + Selector.DATA_TOGGLES )[0] if (rootElement) { @@ -167,6 +170,33 @@ $(document) $(button).toggleClass(ClassName.FOCUS, /^focus(in)?$/.test(event.type)) }) +$(window).on(Event.LOAD_DATA_API, () => { + // ensure correct active class is set to match the controls' actual values/states + + // find all checkboxes/readio buttons inside data-toggle groups + let buttons = [].slice.call(document.querySelectorAll(Selector.DATA_TOGGLES_BUTTONS)) + for (let i = 0, len = buttons.length; i < len; i++) { + const button = buttons[i] + const input = button.querySelector(Selector.INPUT) + if (input.checked || input.hasAttribute('checked')) { + button.classList.add(ClassName.ACTIVE) + } else { + button.classList.remove(ClassName.ACTIVE) + } + } + + // find all button toggles + buttons = [].slice.call(document.querySelectorAll(Selector.DATA_TOGGLE)) + for (let i = 0, len = buttons.length; i < len; i++) { + const button = buttons[i] + if (button.getAttribute('aria-pressed') === 'true') { + button.classList.add(ClassName.ACTIVE) + } else { + button.classList.remove(ClassName.ACTIVE) + } + } +}) + /** * ------------------------------------------------------------------------ * jQuery diff --git a/js/tests/unit/button.js b/js/tests/unit/button.js index 324e940113ef..e5b349363bc3 100644 --- a/js/tests/unit/button.js +++ b/js/tests/unit/button.js @@ -100,13 +100,73 @@ $(function () { assert.strictEqual($btn.attr('aria-pressed'), 'true', 'btn aria-pressed state is true') }) + QUnit.test('should assign active class on page load to buttons with aria-pressed="true"', function (assert) { + assert.expect(1) + var done = assert.async() + var $btn = $('') + $btn.appendTo('#qunit-fixture') + $(window).trigger($.Event('load')) + setTimeout(function () { + assert.ok($btn.hasClass('active'), 'button with aria-pressed="true" has been given class active') + done() + }, 5) + }) + + QUnit.test('should assign active class on page load to button checkbox with checked attribute', function (assert) { + assert.expect(1) + var done = assert.async() + var groupHTML = '
' + + '' + + '
' + var $group = $(groupHTML).appendTo('#qunit-fixture') + var $btn = $group.children().eq(0) + + $(window).trigger($.Event('load')) + setTimeout(function () { + assert.ok($btn.hasClass('active'), 'checked checkbox button has been given class active') + done() + }, 5) + }) + + QUnit.test('should remove active class on page load from buttons without aria-pressed="true"', function (assert) { + assert.expect(1) + var done = assert.async() + var $btn = $('') + $btn.appendTo('#qunit-fixture') + $(window).trigger($.Event('load')) + setTimeout(function () { + assert.ok(!$btn.hasClass('active'), 'button without aria-pressed="true" has had active class removed') + done() + }, 5) + }) + + QUnit.test('should remove active class on page load from button checkbox without checked attribute', function (assert) { + assert.expect(1) + var done = assert.async() + var groupHTML = '
' + + '' + + '
' + var $group = $(groupHTML).appendTo('#qunit-fixture') + var $btn = $group.children().eq(0) + + $(window).trigger($.Event('load')) + setTimeout(function () { + assert.ok(!$btn.hasClass('active'), 'unchecked checkbox button has had active class removed') + done() + }, 5) + }) + QUnit.test('should trigger input change event when toggled button has input field', function (assert) { assert.expect(1) var done = assert.async() var groupHTML = '
' + '' + '
' var $group = $(groupHTML).appendTo('#qunit-fixture') @@ -158,8 +218,8 @@ $(function () { QUnit.test('should not add aria-pressed on labels for radio/checkbox inputs in a data-toggle="buttons" group', function (assert) { assert.expect(2) var groupHTML = '
' + - '' + - '' + + '' + + '' + '
' var $group = $(groupHTML).appendTo('#qunit-fixture') @@ -177,7 +237,7 @@ $(function () { assert.expect(4) var groupHTML = '
' + '' + '
' var $group = $(groupHTML).appendTo('#qunit-fixture') @@ -196,7 +256,7 @@ $(function () { assert.expect(4) var groupHTML = '
' + '' + '
' var $group = $(groupHTML).appendTo('#qunit-fixture') @@ -215,7 +275,7 @@ $(function () { assert.expect(4) var groupHTML = '
' + '' + '
' var $group = $(groupHTML).appendTo('#qunit-fixture') @@ -234,7 +294,7 @@ $(function () { assert.expect(4) var groupHTML = '
' + '
' + - '' + + '' + '
' + '
' var $group = $(groupHTML).appendTo('#qunit-fixture') @@ -253,7 +313,7 @@ $(function () { assert.expect(4) var groupHTML = '
' + '' + '
' var $group = $(groupHTML).appendTo('#qunit-fixture') @@ -272,7 +332,7 @@ $(function () { assert.expect(2) var groupHTML = '
' + '' + '
' var $group = $(groupHTML).appendTo('#qunit-fixture') @@ -289,7 +349,7 @@ $(function () { assert.expect(2) var groupHTML = '
' + '' + '
' var $group = $(groupHTML).appendTo('#qunit-fixture') diff --git a/js/tests/visual/button.html b/js/tests/visual/button.html index b7ba7964d404..183a6ad2c67f 100644 --- a/js/tests/visual/button.html +++ b/js/tests/visual/button.html @@ -10,7 +10,7 @@

Button Bootstrap Visual Test

- @@ -19,13 +19,13 @@

Button Bootstrap Visual Test

@@ -33,13 +33,13 @@

Button Bootstrap Visual Test

diff --git a/site/docs/4.3/components/buttons.md b/site/docs/4.3/components/buttons.md index 118679752a8c..f26a54090b7e 100644 --- a/site/docs/4.3/components/buttons.md +++ b/site/docs/4.3/components/buttons.md @@ -117,7 +117,7 @@ Do more with buttons. Control button states or create groups of buttons for more Add `data-toggle="button"` to toggle a button's `active` state. If you're pre-toggling a button, you must manually add the `.active` class **and** `aria-pressed="true"` to the ` {% endcapture %} @@ -134,7 +134,7 @@ Note that pre-checked buttons require you to manually add the `.active` class to {% capture example %}
{% endcapture %} @@ -143,13 +143,13 @@ Note that pre-checked buttons require you to manually add the `.active` class to {% capture example %}
{% endcapture %} diff --git a/site/docs/4.3/components/progress.md b/site/docs/4.3/components/progress.md index e342b06d93cb..7c42aec3ed91 100644 --- a/site/docs/4.3/components/progress.md +++ b/site/docs/4.3/components/progress.md @@ -134,7 +134,7 @@ The striped gradient can also be animated. Add `.progress-bar-animated` to `.pro
-