From 124ef00f958aefde075d6325d3cfff3bb17bc979 Mon Sep 17 00:00:00 2001 From: Johannes Raggam Date: Wed, 25 Nov 2020 15:45:45 +0100 Subject: [PATCH 01/12] core dom: Add find_parents to find all parents of an element matching a CSS selector. --- CHANGES.md | 1 + src/core/dom.js | 15 +++++++++++++++ src/core/dom.test.js | 30 ++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 2cd14e7ca..79773dfdc 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -45,6 +45,7 @@ - core dom: Add ``querySelectorAllAndMe`` to do a querySelectorAll including the starter element. - core dom: Add ``wrap`` wrap an element with a wrapper element. - core dom: Add ``hide`` and ``show`` for DOM elements which retain the original display value. +- core dom: Add ``find_parents`` to find all parents of an element matching a CSS selector. - pat date picker: Support updating a date if it is before another dependent date. - pat tabs: Refactor based on ``ResizeObserver`` and fix problems calculating the with with transitions. - pat tabs: When clicking on the ``extra-tabs`` element, toggle between ``open`` and ``closed`` classes to allow opening/closing an extra-tabs menu via CSS. diff --git a/src/core/dom.js b/src/core/dom.js index 93e527b91..71c9b9065 100644 --- a/src/core/dom.js +++ b/src/core/dom.js @@ -52,12 +52,27 @@ const show = (el) => { delete el[DATA_STYLE_DISPLAY]; }; +const find_parents = (el, selector) => { + // Return all direct parents of ``el`` matching ``selector``. + // This matches against all parents but not the element itself. + // The order of elements is from the search starting point up to higher + // DOM levels. + let parent = el.parentNode?.closest(selector) || null; + const ret = []; + while (parent) { + ret.push(parent); + parent = parent.parentNode?.closest(selector) || null; + } + return ret; +}; + const dom = { toNodeArray: toNodeArray, querySelectorAllAndMe: querySelectorAllAndMe, wrap: wrap, hide: hide, show: show, + find_parents: find_parents, }; export default dom; diff --git a/src/core/dom.test.js b/src/core/dom.test.js index 59d871fa5..08e910531 100644 --- a/src/core/dom.test.js +++ b/src/core/dom.test.js @@ -4,6 +4,10 @@ import dom from "./dom"; describe("core.dom tests", () => { // Tests from the core.dom module + afterEach(() => { + document.body.innerHTML = ""; + }); + describe("toNodeArray tests", () => { it("returns an array of nodes, if a jQuery object was passed.", (done) => { const html = document.createElement("div"); @@ -146,4 +150,30 @@ describe("core.dom tests", () => { done(); }); }); + + describe("find_parents", () => { + it("it finds all parents matching a selector.", (done) => { + document.body.innerHTML = ` +
+
+
+
+
+
+
+
+ `; + const res = dom.find_parents( + document.querySelector(".starthere"), + ".findme" + ); + + // level4 is not found - it's about to find parents. + expect(res.length).toEqual(2); + expect(res[0]).toEqual(document.querySelector(".level3")); // inner dom levels first // prettier-ignore + expect(res[1]).toEqual(document.querySelector(".level1")); + + done(); + }); + }); }); From 566b4eda37ee242e249aaa1867e92a8a45318556 Mon Sep 17 00:00:00 2001 From: Johannes Raggam Date: Wed, 25 Nov 2020 16:04:15 +0100 Subject: [PATCH 02/12] core dom: Add ``find_scoped`` to search for elements matching the given selector within the current scope of the given element unless an ``id`` selector is given - in that case the search is done globally. --- CHANGES.md | 2 ++ src/core/dom.js | 9 +++++++++ src/core/dom.test.js | 47 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 79773dfdc..85d742d5b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -46,6 +46,8 @@ - core dom: Add ``wrap`` wrap an element with a wrapper element. - core dom: Add ``hide`` and ``show`` for DOM elements which retain the original display value. - core dom: Add ``find_parents`` to find all parents of an element matching a CSS selector. +- core dom: Add ``find_scoped`` to search for elements matching the given selector within the current scope of the given element + unless an ``id`` selector is given - in that case the search is done globally. - pat date picker: Support updating a date if it is before another dependent date. - pat tabs: Refactor based on ``ResizeObserver`` and fix problems calculating the with with transitions. - pat tabs: When clicking on the ``extra-tabs`` element, toggle between ``open`` and ``closed`` classes to allow opening/closing an extra-tabs menu via CSS. diff --git a/src/core/dom.js b/src/core/dom.js index 71c9b9065..64eeb5bf1 100644 --- a/src/core/dom.js +++ b/src/core/dom.js @@ -66,6 +66,14 @@ const find_parents = (el, selector) => { return ret; }; +const find_scoped = (el, selector) => { + // If the selector starts with an object id do a global search, + // otherwise do a local search. + return (selector.indexOf("#") === 0 ? document : el).querySelectorAll( + selector + ); +}; + const dom = { toNodeArray: toNodeArray, querySelectorAllAndMe: querySelectorAllAndMe, @@ -73,6 +81,7 @@ const dom = { hide: hide, show: show, find_parents: find_parents, + find_scoped: find_scoped, }; export default dom; diff --git a/src/core/dom.test.js b/src/core/dom.test.js index 08e910531..9b4d520b9 100644 --- a/src/core/dom.test.js +++ b/src/core/dom.test.js @@ -176,4 +176,51 @@ describe("core.dom tests", () => { done(); }); }); + + describe("find_scoped", () => { + it("Find all instances within the current structure.", (done) => { + document.body.innerHTML = ` +
+
+
+
+
+
+
+
+ `; + + const res = dom.find_scoped( + document.querySelector(".starthere"), + ".findme" + ); + + expect(res.length).toEqual(2); + expect(res[0]).toEqual(document.querySelector(".level3")); // outer dom levels first // prettier-ignore + expect(res[1]).toEqual(document.querySelector(".level4")); + + done(); + }); + + it("Find all instances within the current structure.", (done) => { + document.body.innerHTML = ` +
+
+
+
+
+
+ `; + + const res = dom.find_scoped( + document.querySelector(".starthere"), + "#findme" + ); + + expect(res.length).toEqual(1); + expect(res[0]).toEqual(document.querySelector(".level1")); + + done(); + }); + }); }); From 4a70cbb54b928bed39ebe2bc49adb50ca52bf783 Mon Sep 17 00:00:00 2001 From: Johannes Raggam Date: Wed, 25 Nov 2020 16:22:46 +0100 Subject: [PATCH 03/12] core dom: Add is_visible to check if an element is visible or not. tests skipped as jsDom doesn't update offsetHeight/Width properly. --- CHANGES.md | 1 + src/core/dom.js | 7 +++++++ src/core/dom.test.js | 26 ++++++++++++++++++++++++++ 3 files changed, 34 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 85d742d5b..d88843c6f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -47,6 +47,7 @@ - core dom: Add ``hide`` and ``show`` for DOM elements which retain the original display value. - core dom: Add ``find_parents`` to find all parents of an element matching a CSS selector. - core dom: Add ``find_scoped`` to search for elements matching the given selector within the current scope of the given element +- core dom: Add ``is_visible`` to check if an element is visible or not. unless an ``id`` selector is given - in that case the search is done globally. - pat date picker: Support updating a date if it is before another dependent date. - pat tabs: Refactor based on ``ResizeObserver`` and fix problems calculating the with with transitions. diff --git a/src/core/dom.js b/src/core/dom.js index 64eeb5bf1..3514a1ed0 100644 --- a/src/core/dom.js +++ b/src/core/dom.js @@ -74,6 +74,12 @@ const find_scoped = (el, selector) => { ); }; +const is_visible = (el) => { + // Check, if element is visible in DOM. + // https://stackoverflow.com/a/19808107/1337474 + return el.offsetWidth > 0 && el.offsetHeight > 0; +}; + const dom = { toNodeArray: toNodeArray, querySelectorAllAndMe: querySelectorAllAndMe, @@ -82,6 +88,7 @@ const dom = { show: show, find_parents: find_parents, find_scoped: find_scoped, + is_visible: is_visible, }; export default dom; diff --git a/src/core/dom.test.js b/src/core/dom.test.js index 9b4d520b9..baf851fa4 100644 --- a/src/core/dom.test.js +++ b/src/core/dom.test.js @@ -223,4 +223,30 @@ describe("core.dom tests", () => { done(); }); }); + + describe("is_visible", () => { + it.skip("checks, if an element is visible or not.", (done) => { + const div1 = document.createElement("div"); + div1.setAttribute("id", "div1"); + + const div2 = document.createElement("div"); + div2.setAttribute("id", "div2"); + + const div3 = document.createElement("div"); + div3.setAttribute("id", "div3"); + + div2.style.display = "none"; + div3.style.visibility = "hidden"; + + document.body.appendChild(div1); + document.body.appendChild(div2); + document.body.appendChild(div3); + + expect(dom.is_visible(document.querySelector("#div1"))).toBeTruthy(); // prettier-ignore + expect(dom.is_visible(document.querySelector("#div2"))).toBeFalsy(); + expect(dom.is_visible(document.querySelector("#div3"))).toBeFalsy(); + + done(); + }); + }); }); From a68753613c6f2aaa57483ddf80614e6e9e9c558c Mon Sep 17 00:00:00 2001 From: Johannes Raggam Date: Wed, 25 Nov 2020 15:42:54 +0100 Subject: [PATCH 04/12] pat checkbox changes need label polyfill. --- src/polyfills.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/polyfills.js b/src/polyfills.js index 0f71b259a..bcfbca57f 100644 --- a/src/polyfills.js +++ b/src/polyfills.js @@ -18,3 +18,5 @@ import { ResizeObserver as ResizeObserverPolyfill } from "@juggle/resize-observe if ("ResizeObserver" in window === false) { window.ResizeObserver = ResizeObserverPolyfill; } + +// input.labels polyfill From f80af5728061cf05dad0248efeb0bd84a0200c61 Mon Sep 17 00:00:00 2001 From: Johannes Raggam Date: Wed, 25 Nov 2020 15:42:42 +0100 Subject: [PATCH 05/12] pat checklist: Refactor code. --- src/pat/checklist/checklist.js | 302 +++++++++++---------------------- 1 file changed, 100 insertions(+), 202 deletions(-) diff --git a/src/pat/checklist/checklist.js b/src/pat/checklist/checklist.js index 348396711..0f02e890f 100644 --- a/src/pat/checklist/checklist.js +++ b/src/pat/checklist/checklist.js @@ -1,246 +1,144 @@ -/** - * Patterns checklist - Easily (un)check all checkboxes - * - * Copyright 2012-2013 Simplon B.V. - Wichert Akkerman - * Copyright 2012-2013 Florian Friesdorf - */ -import $ from "jquery"; +import Base from "../../core/base"; import Parser from "../../core/parser"; -import registry from "../../core/registry"; +import dom from "../../core/dom"; import utils from "../../core/utils"; import "../../core/jquery-ext"; -var parser = new Parser("checklist"); +const parser = new Parser("checklist"); parser.addArgument("select", ".select-all"); parser.addArgument("deselect", ".deselect-all"); -var _ = { +export default Base.extend({ name: "checklist", trigger: ".pat-checklist", jquery_plugin: true, + all_selects: [], + all_deselects: [], + all_checkboxes: [], + all_radios: [], + + init() { + this.options = parser.parse(this.el, this.options, false); + this.$el.on("patterns-injected", this._init.bind(this)); + this._init(); + }, + + _init() { + this.all_checkboxes = this.el.querySelectorAll("input[type=checkbox]"); + this.all_radios = this.el.querySelectorAll("input[type=radio]"); - init: function ($el, opts) { - function _init() { - return $el.each(function () { - var $trigger = $(this), - options = parser.parse($trigger, opts, false); - - $trigger.data("patternChecklist", options); - $trigger - .scopedFind(options.select) - .on( - "click.pat-checklist", - { trigger: $trigger }, - _.onSelectAll - ); - $trigger - .scopedFind(options.deselect) - .on( - "click.pat-checklist", - { trigger: $trigger }, - _.onDeselectAll - ); - - $trigger.on("change", () => _.onChange($trigger)); - // update select/deselect button status - _.onChange($trigger); - - $trigger - .find("input[type=checkbox]") - .each(_._onChangeCheckbox) - .on("change.pat-checklist", _._onChangeCheckbox); - - $trigger - .find("input[type=radio]") - .each(_._initRadio) - .on("change.pat-checklist", _._onChangeRadio); + this.all_selects = dom.find_scoped(this.el, this.options.select); + for (const btn of this.all_selects) { + btn.addEventListener("click", (e) => { + e.preventDefault(); + this.select_all(e); + }); + } + + this.all_deselects = dom.find_scoped(this.el, this.options.deselect); + for (const btn of this.all_deselects) { + btn.addEventListener("click", (e) => { + e.preventDefault(); + this.deselect_all(e); }); } - $el.on("patterns-injected", _init); - return _init(); - }, - destroy: function ($el) { - return $el.each(function () { - var $trigger = $(this), - options = $trigger.data("patternChecklist"); - $trigger.scopedFind(options.select).off(".pat-checklist"); - $trigger.scopedFind(options.deselect).off(".pat-checklist"); - $trigger.off(".pat-checklist", "input[type=checkbox]"); - $trigger.data("patternChecklist", null); + // update select/deselect button status + this.el.addEventListener("change", () => { + utils.debounce(() => this.change_buttons(), 50)(); + utils.debounce(() => this.change_checked(), 50)(); }); + this.change_buttons(); + this.change_checked(); }, - _findSiblings: function (elem, sel) { - // Looks for the closest elements that match the `sel` selector - var checkbox_children, $parent; - var parents = $(elem).parents(); - for (var i = 0; i < parents.length; i++) { - $parent = $(parents[i]); - checkbox_children = $parent.find(sel); - if (checkbox_children.length != 0) { - return checkbox_children; - } - if ($parent.hasClass("pat-checklist")) { - // we reached the top node and did not find any match, - // return an empty match - return $([]); + destroy($el) { + // TODO, destroy also on patterns-injected. + this.$el.scopedFind(this.options.select).off(".pat-checklist"); + this.$el.scopedFind(this.options.deselect).off(".pat-checklist"); + this.$el.off(".pat-checklist", "input[type=checkbox]"); + }, + + find_siblings(el, sel) { + // Looks for the closest elements within the `el` tree that match the + // `sel` selector + let res; + let parent = el.parentNode; + while (parent) { + res = parent.querySelectorAll(sel); + if (res.length || parent === this.el) { + // return if results were found or we reached the pattern top + return res; } + parent = parent.parentNode; } - // This should not happen because because we expect `elem` to have - // a .pat-checklist parent - return $([]); }, - onChange: function (trigger) { - const $trigger = $(trigger); - const options = $trigger.data("patternChecklist"); - let siblings; - - let all_selects = $trigger.find(options.select); - if (all_selects.length === 0) { - all_selects = $(options.select); - } - let all_deselects = $trigger.find(options.deselect); - if (all_deselects.length === 0) { - all_deselects = $(options.deselect); - } - for (const select of all_selects) { - siblings = _._findSiblings(select, "input[type=checkbox]:visible"); - if (siblings && siblings.filter(":not(:checked)").length === 0) { - select.disabled = true; - } else { - select.disabled = false; - } + + change_buttons() { + console.log("change buttons"); + let chkbxs; + for (const btn of this.all_selects) { + chkbxs = this.find_siblings(btn, "input[type=checkbox]"); + btn.disabled = [...chkbxs] + .map((el) => el.matches(":checked")) + .every((it) => it === true); } - for (const deselect of all_deselects) { - siblings = _._findSiblings( - deselect, - "input[type=checkbox]:visible" - ); - if (siblings && siblings.filter(":checked").length === 0) { - deselect.disabled = true; - } else { - deselect.disabled = false; - } + for (const btn of this.all_deselects) { + chkbxs = this.find_siblings(btn, "input[type=checkbox]"); + btn.disabled = [...chkbxs] + .map((el) => el.matches(":checked")) + .every((it) => it === false); } }, - onSelectAll: function (event) { - event.preventDefault(); - - /* look up checkboxes which are related to my button by going up one parent - at a time until I find some for the first time */ - const checkbox_siblings = _._findSiblings( - event.currentTarget, + select_all(e) { + const chbxs = this.find_siblings( + e.target, "input[type=checkbox]:not(:checked)" ); - for (const box of checkbox_siblings) { + for (const box of chbxs) { box.checked = true; - $(box).trigger("change"); - box.dispatchEvent(new Event("change")); + box.dispatchEvent(new Event("change", { bubbles: true })); } }, - onDeselectAll: function (event) { - event.preventDefault(); - - /* look up checkboxes which are related to my button by going up one parent - at a time until I find some for the first time */ - const checkbox_siblings = _._findSiblings( - event.currentTarget, + deselect_all(e) { + const chbxs = this.find_siblings( + e.target, "input[type=checkbox]:checked" ); - for (const box of checkbox_siblings) { + for (const box of chbxs) { box.checked = false; - $(box).trigger("change"); - box.dispatchEvent(new Event("change")); + box.dispatchEvent(new Event("change", { bubbles: true })); } }, - /* The following methods are moved here from pat-checked-flag, which is being deprecated */ - _getLabelAndFieldset: function (el) { - var result = new Set(); - result.add($(utils.findLabel(el))); - result.add($(el).closest("fieldset")); - return result; - }, - - _getSiblingsWithLabelsAndFieldsets: function (el) { - var selector = 'input[name="' + el.name + '"]', - $related = el.form === null ? $(selector) : $(selector, el.form); - var result = new Set(); - var label_and_fieldset; - $related = $related.not(el); - $related.each(function (idx, item) { - result.add(item); - label_and_fieldset = _._getLabelAndFieldset(item); - label_and_fieldset.forEach(function (item) { - result.add(item); - }); - }); - return result; - }, - - _onChangeCheckbox: function () { - var $el = $(this), - $label = $(utils.findLabel(this)), - $fieldset = $el.closest("fieldset"); - - if ($el.closest("ul.radioList").length) { - $label = $label.add($el.closest("li")); - } - - if (this.checked) { - $label.add($fieldset).removeClass("unchecked").addClass("checked"); - } else { - $label.addClass("unchecked").removeClass("checked"); - if ($fieldset.find("input:checked").length) { - $fieldset.removeClass("unchecked").addClass("checked"); - } else $fieldset.addClass("unchecked").removeClass("checked"); - } - }, - - _initRadio: function () { - _._updateRadio(this, false); - }, - - _onChangeRadio: function () { - _._updateRadio(this, true); - }, - - _updateRadio: function (input, update_siblings) { - var $el = $(input), - $label = $(utils.findLabel(input)), - $fieldset = $el.closest("fieldset"), - siblings = _._getSiblingsWithLabelsAndFieldsets(input); - if ($el.closest("ul.radioList").length) { - $label = $label.add($el.closest("li")); - var newset = new Set(); - siblings.forEach(function (sibling) { - newset.add($(sibling).closest("li")); - }); - siblings = newset; + change_checked() { + console.log("change checked"); + for (const it of [...this.all_checkboxes].concat([ + ...this.all_radios, + ])) { + for (const label of it.labels) { + label.classList.remove("unchecked"); + label.classList.remove("checked"); + label.classList.add(it.checked ? "checked" : "unchecked"); + } } - if (update_siblings) { - siblings.forEach(function (sibling) { - $(sibling).removeClass("checked").addClass("unchecked"); - }); - } - if (input.checked) { - $label.add($fieldset).removeClass("unchecked").addClass("checked"); - } else { - $label.addClass("unchecked").removeClass("checked"); - if ($fieldset.find("input:checked").length) { - $fieldset.removeClass("unchecked").addClass("checked"); + for (const fieldset of dom.querySelectorAllAndMe(this.el, "fieldset")) { + if ( + fieldset.querySelectorAll( + "input[type=checkbox]:checked, input[type=radio]:checked" + ).length + ) { + fieldset.classList.remove("unchecked"); + fieldset.classList.add("checked"); } else { - $fieldset.addClass("unchecked").removeClass("checked"); + fieldset.classList.remove("checked"); + fieldset.classList.add("unchecked"); } } }, -}; -registry.register(_); - -export default _; +}); From 7014542e7d34e4fd596c79365b0ac2bc71c0ca1a Mon Sep 17 00:00:00 2001 From: Johannes Raggam Date: Thu, 26 Nov 2020 11:27:50 +0100 Subject: [PATCH 06/12] pat-checklist: Use nested checkboxes example with nested fieldsets --- src/pat/checklist/index.html | 237 ++++++----------------------------- 1 file changed, 37 insertions(+), 200 deletions(-) diff --git a/src/pat/checklist/index.html b/src/pat/checklist/index.html index 21c6ffd3b..37ff62943 100644 --- a/src/pat/checklist/index.html +++ b/src/pat/checklist/index.html @@ -9,6 +9,11 @@ type="text/javascript" charset="utf-8" > +
@@ -80,217 +85,49 @@

Hierarchy of Checkboxes

Clicking select all / deselect all only affects parent of buttons and its children.

-
-
- - -
-
    -
  • -

    - Customer Experience - - -

    -
      -
    • -

      - Airport Experience & Servicing - - -

      -
        -
      • -

        - 1. Automation - - -

        -
          -
        • - -
        • -
        • - -
        • -
        -
      • -
      • -

        - 2. Baggage - - -

        -
          -
        • - -
        • -
        • - -
        • -
        -
      • -
      -
    • -
    • -

      - Benefits & Recognition - - -

      -
        -
      • -

        - 1. Benefits - - -

        -
          -
        • - -
        • -
        • - -
        • -
        -
      • -
      -
    • -
    -
  • -
-
-
From 93084d15dcdeb196f8060c887b9e6d6af26535b9 Mon Sep 17 00:00:00 2001 From: Johannes Raggam Date: Thu, 26 Nov 2020 11:48:08 +0100 Subject: [PATCH 07/12] pat-checklist: For global de/select buttons, do not change any other checkboxes than the ones the de/select button belongs to. --- CHANGES.md | 1 + src/pat/checklist/checklist.js | 26 ++++++++++++++++---------- src/pat/checklist/index.html | 24 +++++++++++++----------- 3 files changed, 30 insertions(+), 21 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index d88843c6f..f73ada77b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -93,6 +93,7 @@ - pat autofocus: Implement documented behavior to not focus on prefilled element, if there is another autofocus element which is empty. - pat autofocus: Instead of calling autofocus for each element call it only once. - pat autofocus: Register event handler only once. +- pat-checklist: For global de/select buttons, do not change any other checkboxes than the ones the de/select button belongs to. ## 3.0.0-dev - unreleased diff --git a/src/pat/checklist/checklist.js b/src/pat/checklist/checklist.js index 0f02e890f..75bd3a990 100644 --- a/src/pat/checklist/checklist.js +++ b/src/pat/checklist/checklist.js @@ -74,17 +74,26 @@ export default Base.extend({ } }, + find_checkboxes(ref_el, sel) { + let chkbxs = []; + if (this.options.select.indexOf("#") === 0) { + chkbxs = this.el.querySelectorAll(sel); + } else { + chkbxs = this.find_siblings(ref_el, sel); + } + return chkbxs; + }, + change_buttons() { - console.log("change buttons"); let chkbxs; for (const btn of this.all_selects) { - chkbxs = this.find_siblings(btn, "input[type=checkbox]"); + chkbxs = this.find_checkboxes(btn, "input[type=checkbox]"); btn.disabled = [...chkbxs] .map((el) => el.matches(":checked")) .every((it) => it === true); } for (const btn of this.all_deselects) { - chkbxs = this.find_siblings(btn, "input[type=checkbox]"); + chkbxs = this.find_checkboxes(btn, "input[type=checkbox]"); btn.disabled = [...chkbxs] .map((el) => el.matches(":checked")) .every((it) => it === false); @@ -92,31 +101,28 @@ export default Base.extend({ }, select_all(e) { - const chbxs = this.find_siblings( + const chkbxs = this.find_checkboxes( e.target, "input[type=checkbox]:not(:checked)" ); - - for (const box of chbxs) { + for (const box of chkbxs) { box.checked = true; box.dispatchEvent(new Event("change", { bubbles: true })); } }, deselect_all(e) { - const chbxs = this.find_siblings( + const chkbxs = this.find_checkboxes( e.target, "input[type=checkbox]:checked" ); - - for (const box of chbxs) { + for (const box of chkbxs) { box.checked = false; box.dispatchEvent(new Event("change", { bubbles: true })); } }, change_checked() { - console.log("change checked"); for (const it of [...this.all_checkboxes].concat([ ...this.all_radios, ])) { diff --git a/src/pat/checklist/index.html b/src/pat/checklist/index.html index 37ff62943..9f17a4ca9 100644 --- a/src/pat/checklist/index.html +++ b/src/pat/checklist/index.html @@ -16,9 +16,19 @@ +
+

Select / Deselect all for exmaple 2

+ + +
+
-

Checkboxes

+

Example 1: Checkboxes

-

Buttons outside of .pat-checklist element

+

Example 2: Buttons outside of .pat-checklist element

The buttons to select or deselect the checklist may lie outside of the .pat-checklist element. However, to avoid @@ -56,14 +66,6 @@

Buttons outside of .pat-checklist element

starting with an object id. In other words, the selector needs to start with #elementId.

-
- - -
Buttons outside of .pat-checklist element
-

Hierarchy of Checkboxes

+

Example 3: Hierarchy of Checkboxes

Clicking select all / deselect all only affects parent of buttons and its children. From 554b7615986c3b480a1ae0bc677ea8505cd81150 Mon Sep 17 00:00:00 2001 From: Johannes Raggam Date: Mon, 30 Nov 2020 13:49:33 +0100 Subject: [PATCH 08/12] pat-checklist: Fix ``desctroy`` function --- src/pat/checklist/checklist.js | 36 ++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/src/pat/checklist/checklist.js b/src/pat/checklist/checklist.js index 75bd3a990..0b584a707 100644 --- a/src/pat/checklist/checklist.js +++ b/src/pat/checklist/checklist.js @@ -29,34 +29,34 @@ export default Base.extend({ this.all_selects = dom.find_scoped(this.el, this.options.select); for (const btn of this.all_selects) { - btn.addEventListener("click", (e) => { - e.preventDefault(); - this.select_all(e); - }); + btn.addEventListener("click", this.select_all.bind(this)); } this.all_deselects = dom.find_scoped(this.el, this.options.deselect); for (const btn of this.all_deselects) { - btn.addEventListener("click", (e) => { - e.preventDefault(); - this.deselect_all(e); - }); + btn.addEventListener("click", this.deselect_all.bind(this)); } // update select/deselect button status - this.el.addEventListener("change", () => { - utils.debounce(() => this.change_buttons(), 50)(); - utils.debounce(() => this.change_checked(), 50)(); - }); + this.el.addEventListener("change", this._handler_change.bind(this)); this.change_buttons(); this.change_checked(); }, - destroy($el) { - // TODO, destroy also on patterns-injected. - this.$el.scopedFind(this.options.select).off(".pat-checklist"); - this.$el.scopedFind(this.options.deselect).off(".pat-checklist"); - this.$el.off(".pat-checklist", "input[type=checkbox]"); + _handler_change() { + utils.debounce(() => this.change_buttons(), 50)(); + utils.debounce(() => this.change_checked(), 50)(); + }, + + destroy() { + for (const it of this.all_selects) { + it.removeEventListener("click", this.select_all); + } + for (const it of this.all_deselects) { + it.removeEventListener("click", this.deselect_all); + } + this.el.removeEventListener("change", this._handler_change); + this.$el.off("patterns_injected"); }, find_siblings(el, sel) { @@ -101,6 +101,7 @@ export default Base.extend({ }, select_all(e) { + e.preventDefault(); const chkbxs = this.find_checkboxes( e.target, "input[type=checkbox]:not(:checked)" @@ -112,6 +113,7 @@ export default Base.extend({ }, deselect_all(e) { + e.preventDefault(); const chkbxs = this.find_checkboxes( e.target, "input[type=checkbox]:checked" From 5f15695abf8ea68b39e4502aca9bc0b9e30e2219 Mon Sep 17 00:00:00 2001 From: Johannes Raggam Date: Thu, 26 Nov 2020 11:52:32 +0100 Subject: [PATCH 09/12] pat-checklist: tests. --- src/pat/checklist/checklist.test.js | 788 +++++++++++++++++++--------- 1 file changed, 553 insertions(+), 235 deletions(-) diff --git a/src/pat/checklist/checklist.test.js b/src/pat/checklist/checklist.test.js index 39d157055..ac424c74f 100644 --- a/src/pat/checklist/checklist.test.js +++ b/src/pat/checklist/checklist.test.js @@ -1,246 +1,564 @@ -import "./checklist"; -import $ from "jquery"; +import Pattern from "./checklist"; +import registry from "../../core/registry"; +import utils from "../../core/utils"; -describe("pat-checklist", function () { - beforeEach(function () { - $("

", { id: "lab" }).appendTo(document.body); - }); - afterEach(function () { - $("#lab").remove(); +describe("pat-checklist", () => { + afterEach(() => { + document.body.innerHTML = ""; }); - var utils = { - createCheckList: function () { - $("#lab").html("
"); - var $fieldset = $("fieldset.pat-checklist"); - // The ordering of elements here is important (due to :nth-child selector criteria being used). - $fieldset.append( - $("") - ); - $fieldset.append( - $("") - ); - $fieldset.append( - $("") - ); - $fieldset.append( - $("") - ); - $fieldset.append( - $("") - ); - $("#lab fieldset.pat-checklist").patternChecklist(); - }, - fakeInjectCheckBox: function () { - var $fieldset = $("fieldset.pat-checklist"); - $fieldset.append($("")); - $fieldset.trigger("patterns-injected"); - }, - removeCheckList: function () { - $("#lab").children("fieldset.pat-checklist").remove(); - }, - checkAllBoxes: function () { - for (const el of document.querySelectorAll( - "fieldset.pat-checklist input[type=checkbox]" - )) { - el.checked = true; - $(el).trigger("change"); - } - }, - uncheckAllBoxes: function () { - for (const el of document.querySelectorAll( - "fieldset.pat-checklist input[type=checkbox]" - )) { - el.checked = false; - $(el).trigger("change"); - } - }, - checkBox: function (idx) { - const box = $( - "fieldset.pat-checklist input[type=checkbox]:nth-child(" + - (idx || 1) + - ")" - ); - box[0].checked = true; - box.trigger("change"); - }, - uncheckBox: function (idx) { - const box = $( - "fieldset.pat-checklist input[type=checkbox]:nth-child(" + - (idx || 1) + - ")" - ); - box[0].checked = false; - box.trigger("change"); - }, - }; - - describe("Initialization via jQuery", function () { - it("can be configured by passing in arguments to the jQuery plugin method", function () { - $("#lab").html("
"); - $("#lab div").patternChecklist({ - select: ".one", - deselect: ".two", - }); - var $trigger = $("#lab div"); - expect($trigger.data("patternChecklist")).toEqual({ - select: ".one", - deselect: ".two", - }); - }); - it("can be configured via DOM element data- attributes", function () { - $("#lab").html("
"); - $("#lab div").patternChecklist(); - var $trigger = $("#lab div"); - expect($trigger.data("patternChecklist")).toEqual({ - select: ".one", - deselect: ".two", - }); - }); + it("Initializes on checkboxes with default options", async (done) => { + document.body.innerHTML = ` +
+ + + + + + +
+ `; + Pattern.init(document.querySelector(".pat-checklist")); + + const [f1] = document.querySelectorAll("fieldset"); + const [b1, b2] = document.querySelectorAll("button"); + const [l1, l2, l3] = document.querySelectorAll("label"); + + expect(f1.classList.contains("checked")).toEqual(true); + expect(l1.classList.contains("checked")).toEqual(true); + expect(l2.classList.contains("unchecked")).toEqual(true); + expect(l3.classList.contains("unchecked")).toEqual(true); + expect(b1.hasAttribute("disabled")).toEqual(false); + expect(b2.hasAttribute("disabled")).toEqual(false); + + b1.click(); + await utils.timeout(100); + + expect(f1.classList.contains("checked")).toEqual(true); + expect(l1.classList.contains("checked")).toEqual(true); + expect(l2.classList.contains("checked")).toEqual(true); + expect(l3.classList.contains("checked")).toEqual(true); + expect(b1.hasAttribute("disabled")).toEqual(true); + expect(b2.hasAttribute("disabled")).toEqual(false); + + b2.click(); + await utils.timeout(100); + + expect(f1.classList.contains("unchecked")).toEqual(true); + expect(l1.classList.contains("unchecked")).toEqual(true); + expect(l2.classList.contains("unchecked")).toEqual(true); + expect(l3.classList.contains("unchecked")).toEqual(true); + expect(b1.hasAttribute("disabled")).toEqual(false); + expect(b2.hasAttribute("disabled")).toEqual(true); + + l2.click(); + await utils.timeout(100); + + expect(f1.classList.contains("checked")).toEqual(true); + expect(l1.classList.contains("unchecked")).toEqual(true); + expect(l2.classList.contains("checked")).toEqual(true); + expect(l3.classList.contains("unchecked")).toEqual(true); + expect(b1.hasAttribute("disabled")).toEqual(false); + expect(b2.hasAttribute("disabled")).toEqual(false); + + done(); }); - describe("Deselect All Button", function () { - beforeEach(function () { - utils.createCheckList(); - }); - afterEach(function () { - utils.removeCheckList(); - }); - - it("is disabled when all checkboxes are unchecked", function () { - utils.uncheckAllBoxes(); - expect(document.querySelector(".deselect-all").disabled).toBe(true); - }); - - it("is enabled when all checkboxes are checked", function () { - utils.checkAllBoxes(); - expect(document.querySelector(".deselect-all").disabled).toBe( - false - ); - }); - - it("is enabled when at least one checkbox is checked", function () { - utils.uncheckAllBoxes(); - expect(document.querySelector(".deselect-all").disabled).toBe(true); - utils.checkBox(); - expect(document.querySelector(".deselect-all").disabled).toBe( - false - ); - }); - - it("unchecks all checkboxes if it is clicked", function () { - // Test with all boxes ticked - utils.checkAllBoxes(); - expect( - $("fieldset.pat-checklist input[type=checkbox]:checked").length - ).toBe(3); - $(".deselect-all").click(); - expect( - $("fieldset.pat-checklist input[type=checkbox]:checked").length - ).toBe(0); - // Test with one box ticked - utils.checkBox(); - expect( - $("fieldset.pat-checklist input[type=checkbox]:checked").length - ).toBe(1); - $(".deselect-all").click(); - expect( - $("fieldset.pat-checklist input[type=checkbox]:checked").length - ).toBe(0); - }); - - it("becomes disabled when the last checked checkbox is unchecked", function () { - utils.checkAllBoxes(); - expect(document.querySelector(".deselect-all").disabled).toBe( - false - ); - utils.uncheckBox(); - expect(document.querySelector(".deselect-all").disabled).toBe( - false - ); - utils.uncheckBox(2); - expect(document.querySelector(".deselect-all").disabled).toBe( - false - ); - utils.uncheckBox(3); - expect(document.querySelector(".deselect-all").disabled).toBe(true); - }); + it("Global de/select buttons only change the associated pat-checklist instance.", async (done) => { + document.body.innerHTML = ` + + + +
+ + + +
+ +
+ + + +
+ `; + registry.scan(document.body); + + const [f1, f2] = document.querySelectorAll("fieldset"); + const [b1, b2] = document.querySelectorAll("button"); + const [l1, l2, l3] = document.querySelectorAll(".f1 label"); + const [l4, l5, l6] = document.querySelectorAll(".f2 label"); + + expect(f1.classList.contains("checked")).toEqual(true); + expect(l1.classList.contains("checked")).toEqual(true); + expect(l2.classList.contains("unchecked")).toEqual(true); + expect(l3.classList.contains("unchecked")).toEqual(true); + expect(b1.hasAttribute("disabled")).toEqual(false); + expect(b2.hasAttribute("disabled")).toEqual(false); + + expect(f2.classList.contains("unchecked")).toEqual(true); + expect(l4.classList.contains("unchecked")).toEqual(true); + expect(l5.classList.contains("unchecked")).toEqual(true); + expect(l6.classList.contains("unchecked")).toEqual(true); + + b1.click(); + await utils.timeout(100); + + expect(f1.classList.contains("checked")).toEqual(true); + expect(l1.classList.contains("checked")).toEqual(true); + expect(l2.classList.contains("checked")).toEqual(true); + expect(l3.classList.contains("checked")).toEqual(true); + expect(b1.hasAttribute("disabled")).toEqual(true); + expect(b2.hasAttribute("disabled")).toEqual(false); + + expect(f2.classList.contains("unchecked")).toEqual(true); + expect(l4.classList.contains("unchecked")).toEqual(true); + expect(l5.classList.contains("unchecked")).toEqual(true); + expect(l6.classList.contains("unchecked")).toEqual(true); + + b2.click(); + await utils.timeout(100); + + expect(f1.classList.contains("unchecked")).toEqual(true); + expect(l1.classList.contains("unchecked")).toEqual(true); + expect(l2.classList.contains("unchecked")).toEqual(true); + expect(l3.classList.contains("unchecked")).toEqual(true); + expect(b1.hasAttribute("disabled")).toEqual(false); + expect(b2.hasAttribute("disabled")).toEqual(true); + + expect(f2.classList.contains("unchecked")).toEqual(true); + expect(l4.classList.contains("unchecked")).toEqual(true); + expect(l5.classList.contains("unchecked")).toEqual(true); + expect(l6.classList.contains("unchecked")).toEqual(true); + + l2.click(); + await utils.timeout(100); + + expect(f1.classList.contains("checked")).toEqual(true); + expect(l1.classList.contains("unchecked")).toEqual(true); + expect(l2.classList.contains("checked")).toEqual(true); + expect(l3.classList.contains("unchecked")).toEqual(true); + expect(b1.hasAttribute("disabled")).toEqual(false); + expect(b2.hasAttribute("disabled")).toEqual(false); + + expect(f2.classList.contains("unchecked")).toEqual(true); + expect(l4.classList.contains("unchecked")).toEqual(true); + expect(l5.classList.contains("unchecked")).toEqual(true); + expect(l6.classList.contains("unchecked")).toEqual(true); + + done(); }); - describe("Select All Button", function () { - beforeEach(function () { - utils.createCheckList(); - }); - afterEach(function () { - utils.removeCheckList(); - }); - - it("is disabled when all boxes are checked", function () { - utils.checkAllBoxes(); - expect(document.querySelector(".select-all").disabled).toBe(true); - }); - - it("is enabled when at least one box is unchecked", function () { - utils.checkAllBoxes(); - expect(document.querySelector(".select-all").disabled).toBe(true); - utils.uncheckBox(); - expect(document.querySelector(".select-all").disabled).toBe(false); - }); - - it("checks all boxes if it is clicked", function () { - // Test with zero boxes ticked - utils.uncheckAllBoxes(); - expect( - $("fieldset.pat-checklist input[type=checkbox]:checked").length - ).toBe(0); - $(".select-all").click(); - expect( - $("fieldset.pat-checklist input[type=checkbox]:checked").length - ).toBe(3); - // Test with one box ticked - utils.uncheckAllBoxes(); - utils.checkBox(); - expect( - $("fieldset.pat-checklist input[type=checkbox]:checked").length - ).toBe(1); - $(".select-all").click(); - expect( - $("fieldset.pat-checklist input[type=checkbox]:checked").length - ).toBe(3); - }); - - it("becomes enabled when the first checked box is unchecked", function () { - utils.checkAllBoxes(); - expect(document.querySelector(".select-all").disabled).toBe(true); - utils.uncheckBox(); - expect(document.querySelector(".select-all").disabled).toBe(false); - }); - it("understands injection", function () { - expect(document.querySelector(".select-all").disabled).toBe(true); - utils.fakeInjectCheckBox(); - expect(document.querySelector(".select-all").disabled).toBe(false); - $("[name=primates]").prop("checked", true).change(); - expect(document.querySelector(".select-all").disabled).toBe(true); - }); + it("Nested checklist.", async (done) => { + document.body.innerHTML = ` +
+ + +
+ + +
+ + + + +
+
+ + + + +
+
+
+
+ + + + +
+
+
+ `; + registry.scan(document.body); + + const [f1, f2, f3, f4, f5, f6] = document.querySelectorAll("fieldset"); + const [b1, b2, b3, b4, b5, b6, b7, b8, b9, b10] = document.querySelectorAll("button"); // prettier-ignore + const [l1, l2, l3, l4, l5, l6] = document.querySelectorAll("label"); + + // + expect(f1.classList.contains("unchecked")).toEqual(true); + expect(f2.classList.contains("unchecked")).toEqual(true); + expect(f3.classList.contains("unchecked")).toEqual(true); + expect(f4.classList.contains("unchecked")).toEqual(true); + expect(f5.classList.contains("unchecked")).toEqual(true); + expect(f6.classList.contains("unchecked")).toEqual(true); + + expect(l1.classList.contains("unchecked")).toEqual(true); + expect(l2.classList.contains("unchecked")).toEqual(true); + expect(l3.classList.contains("unchecked")).toEqual(true); + expect(l4.classList.contains("unchecked")).toEqual(true); + expect(l5.classList.contains("unchecked")).toEqual(true); + expect(l6.classList.contains("unchecked")).toEqual(true); + + expect(b1.hasAttribute("disabled")).toEqual(false); + expect(b2.hasAttribute("disabled")).toEqual(true); + expect(b3.hasAttribute("disabled")).toEqual(false); + expect(b4.hasAttribute("disabled")).toEqual(true); + expect(b5.hasAttribute("disabled")).toEqual(false); + expect(b6.hasAttribute("disabled")).toEqual(true); + expect(b7.hasAttribute("disabled")).toEqual(false); + expect(b8.hasAttribute("disabled")).toEqual(true); + expect(b9.hasAttribute("disabled")).toEqual(false); + expect(b10.hasAttribute("disabled")).toEqual(true); + + // + b1.click(); + await utils.timeout(100); + + expect(f1.classList.contains("checked")).toEqual(true); + expect(f2.classList.contains("checked")).toEqual(true); + expect(f3.classList.contains("checked")).toEqual(true); + expect(f4.classList.contains("checked")).toEqual(true); + expect(f5.classList.contains("checked")).toEqual(true); + expect(f6.classList.contains("checked")).toEqual(true); + + expect(l1.classList.contains("checked")).toEqual(true); + expect(l2.classList.contains("checked")).toEqual(true); + expect(l3.classList.contains("checked")).toEqual(true); + expect(l4.classList.contains("checked")).toEqual(true); + expect(l5.classList.contains("checked")).toEqual(true); + expect(l6.classList.contains("checked")).toEqual(true); + + expect(b1.hasAttribute("disabled")).toEqual(true); + expect(b2.hasAttribute("disabled")).toEqual(false); + expect(b3.hasAttribute("disabled")).toEqual(true); + expect(b4.hasAttribute("disabled")).toEqual(false); + expect(b5.hasAttribute("disabled")).toEqual(true); + expect(b6.hasAttribute("disabled")).toEqual(false); + expect(b7.hasAttribute("disabled")).toEqual(true); + expect(b8.hasAttribute("disabled")).toEqual(false); + expect(b9.hasAttribute("disabled")).toEqual(true); + expect(b10.hasAttribute("disabled")).toEqual(false); + + // + b2.click(); + await utils.timeout(100); + + expect(f1.classList.contains("unchecked")).toEqual(true); + expect(f2.classList.contains("unchecked")).toEqual(true); + expect(f3.classList.contains("unchecked")).toEqual(true); + expect(f4.classList.contains("unchecked")).toEqual(true); + expect(f5.classList.contains("unchecked")).toEqual(true); + expect(f6.classList.contains("unchecked")).toEqual(true); + + expect(l1.classList.contains("unchecked")).toEqual(true); + expect(l2.classList.contains("unchecked")).toEqual(true); + expect(l3.classList.contains("unchecked")).toEqual(true); + expect(l4.classList.contains("unchecked")).toEqual(true); + expect(l5.classList.contains("unchecked")).toEqual(true); + expect(l6.classList.contains("unchecked")).toEqual(true); + + expect(b1.hasAttribute("disabled")).toEqual(false); + expect(b2.hasAttribute("disabled")).toEqual(true); + expect(b3.hasAttribute("disabled")).toEqual(false); + expect(b4.hasAttribute("disabled")).toEqual(true); + expect(b5.hasAttribute("disabled")).toEqual(false); + expect(b6.hasAttribute("disabled")).toEqual(true); + expect(b7.hasAttribute("disabled")).toEqual(false); + expect(b8.hasAttribute("disabled")).toEqual(true); + expect(b9.hasAttribute("disabled")).toEqual(false); + expect(b10.hasAttribute("disabled")).toEqual(true); + + // + b3.click(); + await utils.timeout(100); + + expect(f1.classList.contains("checked")).toEqual(true); + expect(f2.classList.contains("checked")).toEqual(true); + expect(f3.classList.contains("checked")).toEqual(true); + expect(f4.classList.contains("checked")).toEqual(true); + expect(f5.classList.contains("unchecked")).toEqual(true); + expect(f6.classList.contains("unchecked")).toEqual(true); + + expect(l1.classList.contains("checked")).toEqual(true); + expect(l2.classList.contains("checked")).toEqual(true); + expect(l3.classList.contains("checked")).toEqual(true); + expect(l4.classList.contains("checked")).toEqual(true); + expect(l5.classList.contains("unchecked")).toEqual(true); + expect(l6.classList.contains("unchecked")).toEqual(true); + + expect(b1.hasAttribute("disabled")).toEqual(false); + expect(b2.hasAttribute("disabled")).toEqual(false); + expect(b3.hasAttribute("disabled")).toEqual(true); + expect(b4.hasAttribute("disabled")).toEqual(false); + expect(b5.hasAttribute("disabled")).toEqual(true); + expect(b6.hasAttribute("disabled")).toEqual(false); + expect(b7.hasAttribute("disabled")).toEqual(true); + expect(b8.hasAttribute("disabled")).toEqual(false); + expect(b9.hasAttribute("disabled")).toEqual(false); + expect(b10.hasAttribute("disabled")).toEqual(true); + + // + b4.click(); + await utils.timeout(100); + + expect(f1.classList.contains("unchecked")).toEqual(true); + expect(f2.classList.contains("unchecked")).toEqual(true); + expect(f3.classList.contains("unchecked")).toEqual(true); + expect(f4.classList.contains("unchecked")).toEqual(true); + expect(f5.classList.contains("unchecked")).toEqual(true); + expect(f6.classList.contains("unchecked")).toEqual(true); + + expect(l1.classList.contains("unchecked")).toEqual(true); + expect(l2.classList.contains("unchecked")).toEqual(true); + expect(l3.classList.contains("unchecked")).toEqual(true); + expect(l4.classList.contains("unchecked")).toEqual(true); + expect(l5.classList.contains("unchecked")).toEqual(true); + expect(l6.classList.contains("unchecked")).toEqual(true); + + expect(b1.hasAttribute("disabled")).toEqual(false); + expect(b2.hasAttribute("disabled")).toEqual(true); + expect(b3.hasAttribute("disabled")).toEqual(false); + expect(b4.hasAttribute("disabled")).toEqual(true); + expect(b5.hasAttribute("disabled")).toEqual(false); + expect(b6.hasAttribute("disabled")).toEqual(true); + expect(b7.hasAttribute("disabled")).toEqual(false); + expect(b8.hasAttribute("disabled")).toEqual(true); + expect(b9.hasAttribute("disabled")).toEqual(false); + expect(b10.hasAttribute("disabled")).toEqual(true); + + // + b5.click(); + await utils.timeout(100); + + expect(f1.classList.contains("checked")).toEqual(true); + expect(f2.classList.contains("checked")).toEqual(true); + expect(f3.classList.contains("checked")).toEqual(true); + expect(f4.classList.contains("unchecked")).toEqual(true); + expect(f5.classList.contains("unchecked")).toEqual(true); + expect(f6.classList.contains("unchecked")).toEqual(true); + + expect(l1.classList.contains("checked")).toEqual(true); + expect(l2.classList.contains("checked")).toEqual(true); + expect(l3.classList.contains("unchecked")).toEqual(true); + expect(l4.classList.contains("unchecked")).toEqual(true); + expect(l5.classList.contains("unchecked")).toEqual(true); + expect(l6.classList.contains("unchecked")).toEqual(true); + + expect(b1.hasAttribute("disabled")).toEqual(false); + expect(b2.hasAttribute("disabled")).toEqual(false); + expect(b3.hasAttribute("disabled")).toEqual(false); + expect(b4.hasAttribute("disabled")).toEqual(false); + expect(b5.hasAttribute("disabled")).toEqual(true); + expect(b6.hasAttribute("disabled")).toEqual(false); + expect(b7.hasAttribute("disabled")).toEqual(false); + expect(b8.hasAttribute("disabled")).toEqual(true); + expect(b9.hasAttribute("disabled")).toEqual(false); + expect(b10.hasAttribute("disabled")).toEqual(true); + + // + b6.click(); + await utils.timeout(100); + + expect(f1.classList.contains("unchecked")).toEqual(true); + expect(f2.classList.contains("unchecked")).toEqual(true); + expect(f3.classList.contains("unchecked")).toEqual(true); + expect(f4.classList.contains("unchecked")).toEqual(true); + expect(f5.classList.contains("unchecked")).toEqual(true); + expect(f6.classList.contains("unchecked")).toEqual(true); + + expect(l1.classList.contains("unchecked")).toEqual(true); + expect(l2.classList.contains("unchecked")).toEqual(true); + expect(l3.classList.contains("unchecked")).toEqual(true); + expect(l4.classList.contains("unchecked")).toEqual(true); + expect(l5.classList.contains("unchecked")).toEqual(true); + expect(l6.classList.contains("unchecked")).toEqual(true); + + expect(b1.hasAttribute("disabled")).toEqual(false); + expect(b2.hasAttribute("disabled")).toEqual(true); + expect(b3.hasAttribute("disabled")).toEqual(false); + expect(b4.hasAttribute("disabled")).toEqual(true); + expect(b5.hasAttribute("disabled")).toEqual(false); + expect(b6.hasAttribute("disabled")).toEqual(true); + expect(b7.hasAttribute("disabled")).toEqual(false); + expect(b8.hasAttribute("disabled")).toEqual(true); + expect(b9.hasAttribute("disabled")).toEqual(false); + expect(b10.hasAttribute("disabled")).toEqual(true); + + // + b7.click(); + await utils.timeout(100); + + expect(f1.classList.contains("checked")).toEqual(true); + expect(f2.classList.contains("checked")).toEqual(true); + expect(f3.classList.contains("unchecked")).toEqual(true); + expect(f4.classList.contains("checked")).toEqual(true); + expect(f5.classList.contains("unchecked")).toEqual(true); + expect(f6.classList.contains("unchecked")).toEqual(true); + + expect(l1.classList.contains("unchecked")).toEqual(true); + expect(l2.classList.contains("unchecked")).toEqual(true); + expect(l3.classList.contains("checked")).toEqual(true); + expect(l4.classList.contains("checked")).toEqual(true); + expect(l5.classList.contains("unchecked")).toEqual(true); + expect(l6.classList.contains("unchecked")).toEqual(true); + + expect(b1.hasAttribute("disabled")).toEqual(false); + expect(b2.hasAttribute("disabled")).toEqual(false); + expect(b3.hasAttribute("disabled")).toEqual(false); + expect(b4.hasAttribute("disabled")).toEqual(false); + expect(b5.hasAttribute("disabled")).toEqual(false); + expect(b6.hasAttribute("disabled")).toEqual(true); + expect(b7.hasAttribute("disabled")).toEqual(true); + expect(b8.hasAttribute("disabled")).toEqual(false); + expect(b9.hasAttribute("disabled")).toEqual(false); + expect(b10.hasAttribute("disabled")).toEqual(true); + + // + b8.click(); + await utils.timeout(100); + + expect(f1.classList.contains("unchecked")).toEqual(true); + expect(f2.classList.contains("unchecked")).toEqual(true); + expect(f3.classList.contains("unchecked")).toEqual(true); + expect(f4.classList.contains("unchecked")).toEqual(true); + expect(f5.classList.contains("unchecked")).toEqual(true); + expect(f6.classList.contains("unchecked")).toEqual(true); + + expect(l1.classList.contains("unchecked")).toEqual(true); + expect(l2.classList.contains("unchecked")).toEqual(true); + expect(l3.classList.contains("unchecked")).toEqual(true); + expect(l4.classList.contains("unchecked")).toEqual(true); + expect(l5.classList.contains("unchecked")).toEqual(true); + expect(l6.classList.contains("unchecked")).toEqual(true); + + expect(b1.hasAttribute("disabled")).toEqual(false); + expect(b2.hasAttribute("disabled")).toEqual(true); + expect(b3.hasAttribute("disabled")).toEqual(false); + expect(b4.hasAttribute("disabled")).toEqual(true); + expect(b5.hasAttribute("disabled")).toEqual(false); + expect(b6.hasAttribute("disabled")).toEqual(true); + expect(b7.hasAttribute("disabled")).toEqual(false); + expect(b8.hasAttribute("disabled")).toEqual(true); + expect(b9.hasAttribute("disabled")).toEqual(false); + expect(b10.hasAttribute("disabled")).toEqual(true); + + // + b9.click(); + await utils.timeout(100); + + expect(f1.classList.contains("checked")).toEqual(true); + expect(f2.classList.contains("unchecked")).toEqual(true); + expect(f3.classList.contains("unchecked")).toEqual(true); + expect(f4.classList.contains("unchecked")).toEqual(true); + expect(f5.classList.contains("checked")).toEqual(true); + expect(f6.classList.contains("checked")).toEqual(true); + + expect(l1.classList.contains("unchecked")).toEqual(true); + expect(l2.classList.contains("unchecked")).toEqual(true); + expect(l3.classList.contains("unchecked")).toEqual(true); + expect(l4.classList.contains("unchecked")).toEqual(true); + expect(l5.classList.contains("checked")).toEqual(true); + expect(l6.classList.contains("checked")).toEqual(true); + + expect(b1.hasAttribute("disabled")).toEqual(false); + expect(b2.hasAttribute("disabled")).toEqual(false); + expect(b3.hasAttribute("disabled")).toEqual(false); + expect(b4.hasAttribute("disabled")).toEqual(true); + expect(b5.hasAttribute("disabled")).toEqual(false); + expect(b6.hasAttribute("disabled")).toEqual(true); + expect(b7.hasAttribute("disabled")).toEqual(false); + expect(b8.hasAttribute("disabled")).toEqual(true); + expect(b9.hasAttribute("disabled")).toEqual(true); + expect(b10.hasAttribute("disabled")).toEqual(false); + + // + b10.click(); + await utils.timeout(100); + + expect(f1.classList.contains("unchecked")).toEqual(true); + expect(f2.classList.contains("unchecked")).toEqual(true); + expect(f3.classList.contains("unchecked")).toEqual(true); + expect(f4.classList.contains("unchecked")).toEqual(true); + expect(f5.classList.contains("unchecked")).toEqual(true); + expect(f6.classList.contains("unchecked")).toEqual(true); + + expect(l1.classList.contains("unchecked")).toEqual(true); + expect(l2.classList.contains("unchecked")).toEqual(true); + expect(l3.classList.contains("unchecked")).toEqual(true); + expect(l4.classList.contains("unchecked")).toEqual(true); + expect(l5.classList.contains("unchecked")).toEqual(true); + expect(l6.classList.contains("unchecked")).toEqual(true); + + expect(b1.hasAttribute("disabled")).toEqual(false); + expect(b2.hasAttribute("disabled")).toEqual(true); + expect(b3.hasAttribute("disabled")).toEqual(false); + expect(b4.hasAttribute("disabled")).toEqual(true); + expect(b5.hasAttribute("disabled")).toEqual(false); + expect(b6.hasAttribute("disabled")).toEqual(true); + expect(b7.hasAttribute("disabled")).toEqual(false); + expect(b8.hasAttribute("disabled")).toEqual(true); + expect(b9.hasAttribute("disabled")).toEqual(false); + expect(b10.hasAttribute("disabled")).toEqual(true); + + // + l4.click(); + await utils.timeout(100); + + expect(f1.classList.contains("checked")).toEqual(true); + expect(f2.classList.contains("checked")).toEqual(true); + expect(f3.classList.contains("unchecked")).toEqual(true); + expect(f4.classList.contains("checked")).toEqual(true); + expect(f5.classList.contains("unchecked")).toEqual(true); + expect(f6.classList.contains("unchecked")).toEqual(true); + + expect(l1.classList.contains("unchecked")).toEqual(true); + expect(l2.classList.contains("unchecked")).toEqual(true); + expect(l3.classList.contains("unchecked")).toEqual(true); + expect(l4.classList.contains("checked")).toEqual(true); + expect(l5.classList.contains("unchecked")).toEqual(true); + expect(l6.classList.contains("unchecked")).toEqual(true); + + expect(b1.hasAttribute("disabled")).toEqual(false); + expect(b2.hasAttribute("disabled")).toEqual(false); + expect(b3.hasAttribute("disabled")).toEqual(false); + expect(b4.hasAttribute("disabled")).toEqual(false); + expect(b5.hasAttribute("disabled")).toEqual(false); + expect(b6.hasAttribute("disabled")).toEqual(true); + expect(b7.hasAttribute("disabled")).toEqual(false); + expect(b8.hasAttribute("disabled")).toEqual(false); + expect(b9.hasAttribute("disabled")).toEqual(false); + expect(b10.hasAttribute("disabled")).toEqual(true); + + done(); }); - describe("The function _findSiblings", function () { - it("has a scope limited to the current form", function () { - utils.createCheckList(); - // Duplicate the check list with all the items checked - $("#lab").append($("#lab").find(".pat-checklist").clone()); - $(".pat-checklist").last().patternChecklist(); - expect($(".pat-checklist").first().find(":checked").length).toBe(3); - expect($(".pat-checklist").last().find(":checked").length).toBe(3); - - // Click the last form deselect all button - $(".pat-checklist").last().find(".deselect-all").click(); - expect($(".pat-checklist").first().find(":checked").length).toBe(3); - expect($(".pat-checklist").last().find(":checked").length).toBe(0); - // Clicking again does not touch the selected checkboxes in the other fieldset - $(".pat-checklist").last().find(".deselect-all").click(); - expect($(".pat-checklist").first().find(":checked").length).toBe(3); - expect($(".pat-checklist").last().find(":checked").length).toBe(0); - }); + it("Initializes on radio buttons", async (done) => { + document.body.innerHTML = ` +
+ + + +
+ `; + Pattern.init(document.querySelector(".pat-checklist")); + + const [f1] = document.querySelectorAll("fieldset"); + const [l1, l2, l3] = document.querySelectorAll("label"); + + expect(f1.classList.contains("checked")).toEqual(true); + expect(l1.classList.contains("unchecked")).toEqual(true); + expect(l2.classList.contains("checked")).toEqual(true); + expect(l3.classList.contains("unchecked")).toEqual(true); + + l3.click(); + await utils.timeout(100); + + expect(f1.classList.contains("checked")).toEqual(true); + expect(l1.classList.contains("unchecked")).toEqual(true); + expect(l2.classList.contains("unchecked")).toEqual(true); + expect(l3.classList.contains("checked")).toEqual(true); + + done(); }); }); From c43276b2015d47b005df57038695cf5ad022120a Mon Sep 17 00:00:00 2001 From: Johannes Raggam Date: Mon, 30 Nov 2020 13:49:51 +0100 Subject: [PATCH 10/12] package.json: Upgrade underscore --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 54bc45de7..4c7f44f07 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "spectrum-colorpicker": "^1.8.0", "stickyfilljs": "git+https://github.com/syslabcom/stickyfill.git", "tippy.js": "^6.2.7", - "underscore": "^1.11.0", + "underscore": "^1.12.0", "url-polyfill": "^1.1.9", "validate.js": "^0.13.1", "whatwg-fetch": "^3.4.0" From 230442dadcd15bdf213e134cfc91000a3d59df54 Mon Sep 17 00:00:00 2001 From: Johannes Raggam Date: Mon, 30 Nov 2020 13:50:06 +0100 Subject: [PATCH 11/12] yarn install --- yarn.lock | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index c6f4c240e..8878259cb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7885,11 +7885,16 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -underscore@1.11.0, underscore@^1.11.0: +underscore@1.11.0: version "1.11.0" resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.11.0.tgz#dd7c23a195db34267186044649870ff1bab5929e" integrity sha512-xY96SsN3NA461qIRKZ/+qox37YXPtSBswMGfiNptr+wrt6ds4HaMw23TP612fEyGekRE6LNRiLYr/aqbHXNedw== +underscore@^1.12.0: + version "1.12.0" + resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.12.0.tgz#4814940551fc80587cef7840d1ebb0f16453be97" + integrity sha512-21rQzss/XPMjolTiIezSu3JAjgagXKROtNrYFEOWK109qY1Uv2tVjPTZ1ci2HgvQDA16gHYSthQIJfB+XId/rQ== + unicode-canonical-property-names-ecmascript@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818" From 6d10e1ea27e61f984f68ccf27fb80eeb1ccdaf6c Mon Sep 17 00:00:00 2001 From: Johannes Raggam Date: Wed, 2 Dec 2020 10:50:09 +0100 Subject: [PATCH 12/12] Initialize and use GitHub Actions --- .github/workflows/test.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 000000000..b215ac2c2 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,13 @@ +name: test +on: [push] +jobs: + build: + name: test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: '12' + - run: npm install yarn + - run: make check