diff --git a/src/core/basepattern.js b/src/core/basepattern.js
index 9e6394a07..cf7975626 100644
--- a/src/core/basepattern.js
+++ b/src/core/basepattern.js
@@ -8,6 +8,7 @@
*/
import events from "./events";
import logging from "./logging";
+import create_uuid from "./uuid";
const log = logging.getLogger("basepattern");
@@ -35,6 +36,7 @@ class BasePattern {
el = el[0];
}
this.el = el;
+ this.uuid = create_uuid();
// Notify pre-init
this.el.dispatchEvent(
diff --git a/src/core/basepattern.test.js b/src/core/basepattern.test.js
index 63c61c497..4b3a43f1f 100644
--- a/src/core/basepattern.test.js
+++ b/src/core/basepattern.test.js
@@ -35,6 +35,10 @@ describe("Basepattern class tests", function () {
expect(pat.name).toBe("example");
expect(pat.trigger).toBe(".example");
expect(typeof pat.parser.parse).toBe("function");
+
+ // Test more attributes
+ expect(pat.el).toBe(el);
+ expect(pat.uuid).toMatch(/^[0-9a-f\-]*$/);
});
it("1.2 - Options are created with grouping per default.", async function () {
diff --git a/src/core/dom.js b/src/core/dom.js
index 01529d838..80bd3e6ad 100644
--- a/src/core/dom.js
+++ b/src/core/dom.js
@@ -1,11 +1,14 @@
/* Utilities for DOM traversal or navigation */
import logging from "./logging";
+import create_uuid from "./uuid";
const logger = logging.getLogger("core dom");
const DATA_PREFIX = "__patternslib__data_prefix__";
const DATA_STYLE_DISPLAY = "__patternslib__style__display";
+const INPUT_SELECTOR = "input, select, textarea, button";
+
/**
* Return an array of DOM nodes.
*
@@ -539,19 +542,7 @@ const escape_css_id = (id) => {
*/
const element_uuid = (el) => {
if (!get_data(el, "uuid", false)) {
- let uuid;
- if (window.crypto.randomUUID) {
- // Create a real UUID
- // window.crypto.randomUUID does only exist in browsers with secure
- // context.
- // See: https://developer.mozilla.org/en-US/docs/Web/API/Crypto/randomUUID
- uuid = window.crypto.randomUUID();
- } else {
- // Create a sufficiently unique ID
- const array = new Uint32Array(4);
- uuid = window.crypto.getRandomValues(array).join("");
- }
- set_data(el, "uuid", uuid);
+ set_data(el, "uuid", create_uuid());
}
return get_data(el, "uuid");
};
@@ -571,17 +562,25 @@ const find_form = (el) => {
const form =
el.closest(".pat-subform") || // Special Patternslib subform concept has precedence.
el.form ||
- el.querySelector("input, select, textarea, button")?.form ||
+ el.querySelector(INPUT_SELECTOR)?.form ||
el.closest("form");
return form;
};
+/**
+ * Find any input type.
+ */
+const find_inputs = (el) => {
+ return querySelectorAllAndMe(el, INPUT_SELECTOR);
+};
+
const dom = {
toNodeArray: toNodeArray,
querySelectorAllAndMe: querySelectorAllAndMe,
wrap: wrap,
hide: hide,
show: show,
+ find_inputs: find_inputs,
find_parents: find_parents,
find_scoped: find_scoped,
get_parents: get_parents,
diff --git a/src/core/dom.test.js b/src/core/dom.test.js
index 1283e8e29..caa17cbcb 100644
--- a/src/core/dom.test.js
+++ b/src/core/dom.test.js
@@ -1017,3 +1017,44 @@ describe("find_form", function () {
expect(dom.find_form(el)).toBe(subform);
});
});
+
+describe("find_inputs", () => {
+ it("finds an input within a node structure.", (done) => {
+ const wrapper = document.createElement("div");
+ wrapper.innerHTML = `
+
hello
+
+
+
+
+
+ 1
+ 2
+
+
+
+ Click me!
+ `;
+ const inputs = dom.find_inputs(wrapper);
+ const input_types = inputs.map((node) => node.nodeName);
+
+ expect(inputs.length).toBe(4);
+ expect(input_types.includes("INPUT")).toBeTruthy();
+ expect(input_types.includes("SELECT")).toBeTruthy();
+ expect(input_types.includes("TEXTAREA")).toBeTruthy();
+ expect(input_types.includes("BUTTON")).toBeTruthy();
+
+ done();
+ });
+
+ it("finds the input on the node itself.", (done) => {
+ const wrapper = document.createElement("input");
+ const inputs = dom.find_inputs(wrapper);
+ const input_types = inputs.map((node) => node.nodeName);
+
+ expect(inputs.length).toBe(1);
+ expect(input_types.includes("INPUT")).toBeTruthy();
+
+ done();
+ });
+});
diff --git a/src/core/uuid.js b/src/core/uuid.js
new file mode 100644
index 000000000..9eb2a7d1e
--- /dev/null
+++ b/src/core/uuid.js
@@ -0,0 +1,21 @@
+/**
+ * Get a universally unique id (uuid).
+ *
+ * @returns {String} - The uuid.
+ */
+const create_uuid = () => {
+ let uuid;
+ if (window.crypto.randomUUID) {
+ // Create a real UUID
+ // window.crypto.randomUUID does only exist in browsers with secure
+ // context.
+ // See: https://developer.mozilla.org/en-US/docs/Web/API/Crypto/randomUUID
+ uuid = window.crypto.randomUUID();
+ } else {
+ // Create a sufficiently unique ID
+ const array = new Uint32Array(4);
+ uuid = window.crypto.getRandomValues(array).join("");
+ }
+ return uuid;
+};
+export default create_uuid;
diff --git a/src/core/uuid.test.js b/src/core/uuid.test.js
new file mode 100644
index 000000000..d70c337a0
--- /dev/null
+++ b/src/core/uuid.test.js
@@ -0,0 +1,22 @@
+import create_uuid from "./uuid";
+
+describe("uuid", function () {
+ it("returns a UUIDv4", function () {
+ const uuid = create_uuid();
+ expect(uuid).toMatch(
+ /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/
+ );
+ });
+
+ it("returns a sufficiently unique id", function () {
+ // Mock window.crypto.randomUUID not existing, like in browser with
+ // non-secure context.
+ const orig_randomUUID = window.crypto.randomUUID;
+ window.crypto.randomUUID = undefined;
+
+ const uuid = create_uuid();
+ expect(uuid).toMatch(/^[0-9]*$/);
+
+ window.crypto.randomUUID = orig_randomUUID;
+ });
+});
diff --git a/src/lib/dependshandler.js b/src/lib/dependshandler.js
index 770ad487d..299b2b204 100644
--- a/src/lib/dependshandler.js
+++ b/src/lib/dependshandler.js
@@ -1,59 +1,87 @@
-import $ from "jquery";
import parser from "./depends_parse";
-function DependsHandler($el, expression) {
- var $context = $el.closest("form");
- if (!$context.length) $context = $(document);
- this.$el = $el;
- this.$context = $context;
- this.ast = parser.parse(expression); // TODO: handle parse exceptions here
-}
+class DependsHandler {
+ constructor(el, expression) {
+ this.el = el;
+ this.context = el.closest("form") || document;
+ this.ast = parser.parse(expression); // TODO: handle parse exceptions here
+ }
-DependsHandler.prototype = {
- _findInputs: function (name) {
- var $input = this.$context.find(":input[name='" + name + "']");
- if (!$input.length) $input = $("#" + name);
- return $input;
- },
+ _findInputs(name) {
+ // In case of radio buttons, there might be multiple inputs.
+ // "name" in parentheses, because it can be any value. Common is:
+ // `somename:list` for a radio input list.
+ let inputs = this.context.querySelectorAll(`
+ input[name="${name}"],
+ select[name="${name}"],
+ textarea[name="${name}"],
+ button[name="${name}"]
+ `);
+ if (!inputs.length) {
+ // This should really only find one instance.
+ inputs = document.querySelectorAll(`#${name}`);
+ }
+ return inputs;
+ }
- _getValue: function (name) {
- var $input = this._findInputs(name);
- if (!$input.length) return null;
+ _getValue(name) {
+ let inputs = this._findInputs(name);
- if ($input.attr("type") === "radio" || $input.attr("type") === "checkbox")
- return $input.filter(":checked").val() || null;
- else return $input.val();
- },
+ inputs = [...inputs].filter((input) => {
+ if (input.type === "radio" && input.checked === false) {
+ return false;
+ }
+ if (input.type === "checkbox" && input.checked === false) {
+ return false;
+ }
+ if (input.disabled) {
+ return false;
+ }
+ return true;
+ });
- getAllInputs: function () {
- var todo = [this.ast],
- $inputs = $(),
- node;
+ if (inputs.length === 0) {
+ return null;
+ }
+
+ return inputs[0].value;
+ }
+
+ getAllInputs() {
+ const todo = [this.ast];
+ const all_inputs = new Set();
while (todo.length) {
- node = todo.shift();
- if (node.input) $inputs = $inputs.add(this._findInputs(node.input));
- if (node.children && node.children.length)
+ const node = todo.shift();
+ if (node.input) {
+ const inputs = this._findInputs(node.input);
+ for (const input of inputs) {
+ all_inputs.add(input);
+ }
+ }
+ if (node.children && node.children.length) {
todo.push.apply(todo, node.children);
+ }
}
- return $inputs;
- },
+ return [...all_inputs];
+ }
- _evaluate: function (node) {
- var value = node.input ? this._getValue(node.input) : null,
- i;
+ _evaluate(node) {
+ const value = node.input ? this._getValue(node.input) : null;
switch (node.type) {
case "NOT":
return !this._evaluate(node.children[0]);
- case "AND":
- for (i = 0; i < node.children.length; i++)
- if (!this._evaluate(node.children[i])) return false;
- return true;
- case "OR":
- for (i = 0; i < node.children.length; i++)
- if (this._evaluate(node.children[i])) return true;
- return false;
+ case "AND": {
+ // As soon as one child evaluates to false, the AND expression is false.
+ const is_false = node.children.some((child) => !this._evaluate(child));
+ return !is_false;
+ }
+ case "OR": {
+ // As soon as one child evaluates to true, the OR expression is true.
+ const is_true = node.children.some((child) => this._evaluate(child));
+ return is_true;
+ }
case "comparison":
switch (node.operator) {
case "=":
@@ -69,21 +97,25 @@ DependsHandler.prototype = {
case ">=":
return value >= node.value;
case "~=":
- if (value === null) return false;
+ if (value === null) {
+ return false;
+ }
return value.indexOf(node.value) != -1;
case "=~":
- if (value === null || !node.value) return false;
+ if (value === null || !node.value) {
+ return false;
+ }
return node.value.indexOf(value) != -1;
}
break;
case "truthy":
return !!value;
}
- },
+ }
- evaluate: function () {
+ evaluate() {
return this._evaluate(this.ast);
- },
-};
+ }
+}
export default DependsHandler;
diff --git a/src/lib/dependshandler.test.js b/src/lib/dependshandler.test.js
index fc347d61c..5f03c4f74 100644
--- a/src/lib/dependshandler.test.js
+++ b/src/lib/dependshandler.test.js
@@ -1,106 +1,252 @@
-import $ from "jquery";
import DependsHandler from "./dependshandler";
describe("pat-dependshandler", function () {
- beforeEach(function () {
- $("
", { id: "lab" }).appendTo(document.body);
- });
+ describe("Basic tests", function () {
+ it("uses a form as context, if there is one.", function () {
+ document.body.innerHTML = `
+
+ `;
+
+ const lab = document.getElementById("lab");
+ const handler = new DependsHandler(lab, "lab");
+
+ expect(handler.context).toBe(document.querySelector("form"));
+ });
- afterEach(function () {
- $("#lab").remove();
+ it("uses the document as context, if there is no form", function () {
+ document.body.innerHTML = `
+
+
+
+ `;
+
+ const lab = document.getElementById("lab");
+ const handler = new DependsHandler(lab, "lab");
+
+ expect(handler.context).toBe(document);
+ });
});
describe("_findInputs", function () {
- it("Reference to unknown input", function () {
- var handler = new DependsHandler($("#lab"), "foo");
+ it("no input, nothing found", function () {
+ document.body.innerHTML = `
+
+ `;
+ const lab = document.getElementById("lab");
+ const handler = new DependsHandler(lab, "foo");
+
expect(handler._findInputs("foo").length).toBe(0);
});
- it("Reference to input name", function () {
- var $lab = $("#lab"),
- handler = new DependsHandler($("#lab"), "foo");
- $lab.append(' ');
+ it("find input by name", function () {
+ document.body.innerHTML = `
+
+
+
+ `;
+ const lab = document.getElementById("lab");
+ const foo = document.querySelector("[name=foo]");
+ const handler = new DependsHandler(lab, "foo");
+
expect(handler._findInputs("foo").length).toBe(1);
+ expect(handler._findInputs("foo")[0]).toBe(foo);
});
- it("Reference to input id", function () {
- var $lab = $("#lab"),
- handler = new DependsHandler($("#lab"), "foo");
- $lab.append(' ');
+ it("find input by id", function () {
+ document.body.innerHTML = `
+
+
+
+ `;
+ const lab = document.getElementById("lab");
+ const bar = document.getElementById("bar");
+ const handler = new DependsHandler(lab, "bar");
+
expect(handler._findInputs("bar").length).toBe(1);
+ expect(handler._findInputs("bar")[0]).toBe(bar);
});
it("Restrict searches to current form", function () {
- $("#lab").html(
- [
- ' ',
- "",
- ].join("\n")
- );
- var handler = new DependsHandler($("#context"), "foo"),
- $inputs = handler._findInputs("foo");
- expect($inputs.length).toBe(1);
- expect($inputs[0].type).toBe("radio");
+ document.body.innerHTML = `
+
+
+ `;
+ const lab = document.getElementById("lab");
+ const foo1 = document.getElementById("foo1");
+ const handler = new DependsHandler(lab, "foo");
+
+ expect(handler._findInputs("foo").length).toBe(1);
+ expect(handler._findInputs("foo")[0]).toBe(foo1);
+ });
+
+ it("find multiple inputs of the same name, e.g. for radio buttons", function () {
+ document.body.innerHTML = `
+
+
+
+
+
+ `;
+ const lab = document.getElementById("lab");
+ const foos = document.querySelectorAll("[name='foo:list']");
+ const handler = new DependsHandler(lab, "foo:list");
+ expect(handler._findInputs("foo:list").length).toBe(3);
+ expect(handler._findInputs("foo:list")[0]).toBe(foos[0]);
+ expect(handler._findInputs("foo:list")[1]).toBe(foos[1]);
+ expect(handler._findInputs("foo:list")[2]).toBe(foos[2]);
});
});
describe("_getValue", function () {
- it("Unchecked checkbox", function () {
- $("#lab").append(' ');
- var handler = new DependsHandler($("#lab"), "foo");
- expect(handler._getValue("foo")).toBeNull();
- });
+ it("Just get the text value.", function () {
+ document.body.innerHTML = `
+
+
+
+ `;
+ const lab = document.getElementById("lab");
+ const handler = new DependsHandler(lab, "foo");
- it("Checked checkbox", function () {
- $("#lab").append(
- ' '
- );
- var handler = new DependsHandler($("#lab"), "foo");
expect(handler._getValue("foo")).toBe("bar");
});
- it("Unchecked radio button", function () {
- $("#lab").append(' ');
- var handler = new DependsHandler($("#lab"), "foo");
+ it("Get an empty value from an empty input.", function () {
+ document.body.innerHTML = `
+
+
+
+ `;
+ const lab = document.getElementById("lab");
+ const handler = new DependsHandler(lab, "foo");
+
+ expect(handler._getValue("foo")).toBe("");
+ });
+
+ it("Get null from a disabled input.", function () {
+ document.body.innerHTML = `
+
+
+
+ `;
+ const lab = document.getElementById("lab");
+ const handler = new DependsHandler(lab, "foo");
+
+ expect(handler._getValue("foo")).toBe(null);
+ });
+
+ it("Unchecked checkbox returns no value", function () {
+ document.body.innerHTML = `
+
+
+
+ `;
+ const lab = document.getElementById("lab");
+ const handler = new DependsHandler(lab, "foo");
+
expect(handler._getValue("foo")).toBeNull();
});
- it("Unchecked radio button", function () {
- $("#lab").append(' ');
- var handler = new DependsHandler($("#lab"), "foo");
+ it("Checked checkbox returns value", function () {
+ document.body.innerHTML = `
+
+
+
+ `;
+ const lab = document.getElementById("lab");
+ const handler = new DependsHandler(lab, "foo");
+
+ expect(handler._getValue("foo")).toBe("bar");
+ });
+
+ it("Unchecked radio button returns no value", function () {
+ document.body.innerHTML = `
+
+
+
+ `;
+ const lab = document.getElementById("lab");
+ const handler = new DependsHandler(lab, "foo");
+
expect(handler._getValue("foo")).toBeNull();
});
it("Checked radio button", function () {
- $("#lab").append(
- ' '
- );
- var handler = new DependsHandler($("#lab"), "foo");
+ document.body.innerHTML = `
+
+
+
+ `;
+ const lab = document.getElementById("lab");
+ const handler = new DependsHandler(lab, "foo");
+
expect(handler._getValue("foo")).toBe("bar");
});
+
+ it("Returns the value of the checked radio buttons in a list of multiple radio buttons.", function () {
+ document.body.innerHTML = `
+
+
+
+
+
+
+ `;
+ const lab = document.getElementById("lab");
+ const handler = new DependsHandler(lab, "foo:list");
+
+ expect(handler._getValue("foo:list")).toBe("fuzz");
+ });
+
+ it("Returns no value in a list of multiple radio buttons of no one is checked.", function () {
+ document.body.innerHTML = `
+
+
+
+
+
+
+ `;
+ const lab = document.getElementById("lab");
+ const handler = new DependsHandler(lab, "foo:list");
+
+ expect(handler._getValue("foo:list")).toBe(null);
+ });
+
});
describe("getAllInputs", function () {
it("Simple expression", function () {
- $("#lab").append(' ');
- var handler = new DependsHandler($("#lab"), "foo"),
- inputs = handler.getAllInputs();
+ document.body.innerHTML = `
+
+
+
+
+ `;
+ const lab = document.getElementById("lab");
+ const handler = new DependsHandler(lab, "foo");
+ const inputs = handler.getAllInputs();
+
expect(inputs.length).toBe(1);
- expect(inputs[0].type).toBe("radio");
+ expect(inputs[0].name).toBe("foo");
});
it("Nested expression", function () {
- $("#lab").html(
- [
- ' ',
- ' ',
- ].join("\n")
- );
- var handler = new DependsHandler($("#lab"), "foo or (foo and buz)"),
- inputs = handler.getAllInputs();
+ document.body.innerHTML = `
+
+
+
+
+
+ `;
+ const lab = document.getElementById("lab");
+ const handler = new DependsHandler(lab, "foo or (foo and buz)");
+ const inputs = handler.getAllInputs();
+
expect(inputs.length).toBe(2);
expect(inputs[0].name).toBe("foo");
expect(inputs[1].name).toBe("buz");
@@ -110,102 +256,196 @@ describe("pat-dependshandler", function () {
describe("evaluate", function () {
describe("truthy", function () {
it("Text input with value", function () {
- $("#lab").append(' ');
- var handler = new DependsHandler($("#lab"), "foo");
+ document.body.innerHTML = `
+
+
+
+ `;
+ const lab = document.getElementById("lab");
+ const handler = new DependsHandler(lab, "foo");
+
expect(handler.evaluate()).toBe(true);
});
it("Text input without value", function () {
- $("#lab").append(' ');
- var handler = new DependsHandler($("#lab"), "foo");
+ document.body.innerHTML = `
+
+
+
+ `;
+ const lab = document.getElementById("lab");
+ const handler = new DependsHandler(lab, "foo");
+
expect(handler.evaluate()).toBe(false);
});
});
- describe("Comparison", function () {
- it("Positive number input below value", function () {
- $("#lab").append(' ');
- var handler = new DependsHandler($("#lab"), "foo<15");
+ describe("Number comparisons", function () {
+ it("Number below expression value", function () {
+ document.body.innerHTML = `
+
+
+
+ `;
+ const lab = document.getElementById("lab");
+ const handler = new DependsHandler(lab, "foo<15");
+
expect(handler.evaluate()).toBe(true);
});
- it("Negative number input below value", function () {
- $("#lab").append(' ');
- var handler = new DependsHandler($("#lab"), "foo<15");
+ it("Number above expression value", function () {
+ document.body.innerHTML = `
+
+
+
+ `;
+ const lab = document.getElementById("lab");
+ const handler = new DependsHandler(lab, "foo<15");
+
expect(handler.evaluate()).toBe(false);
});
- it("Positive equal to value", function () {
- $("#lab").append(' ');
- var handler = new DependsHandler($("#lab"), "foo=bar");
+ it("Negative number below expression value", function () {
+ document.body.innerHTML = `
+
+
+
+ `;
+ const lab = document.getElementById("lab");
+ const handler = new DependsHandler(lab, "foo<15");
+
+ expect(handler.evaluate()).toBe(true);
+ });
+
+ describe.skip("Negative expression values not yet supported.", function () {
+ it("Negative number above negative expression value", function () {
+ document.body.innerHTML = `
+
+
+
+ `;
+ const lab = document.getElementById("lab");
+ const handler = new DependsHandler(lab, "foo<-15");
+
+ expect(handler.evaluate()).toBe(false);
+ });
+ });
+
+ it("Number equal to expression value", function () {
+ document.body.innerHTML = `
+
+
+
+ `;
+ const lab = document.getElementById("lab");
+ const handler = new DependsHandler(lab, "foo<=15");
+
+ expect(handler.evaluate()).toBe(true);
+ });
+
+ it("Text equal to expression value", function () {
+ document.body.innerHTML = `
+
+
+
+ `;
+ const lab = document.getElementById("lab");
+ const handler = new DependsHandler(lab, "foo=buz");
+
expect(handler.evaluate()).toBe(true);
});
- it("Negative equal to value", function () {
- $("#lab").append(' ');
- var handler = new DependsHandler($("#lab"), "foo=bar");
+ it("Text not equal to expression value", function () {
+ document.body.innerHTML = `
+
+
+
+ `;
+ const lab = document.getElementById("lab");
+ const handler = new DependsHandler(lab, "foo=buz");
+
expect(handler.evaluate()).toBe(false);
});
});
- describe("Negate test", function () {
- it("Text input with value", function () {
- $("#lab").append(' ');
- var handler = new DependsHandler($("#lab"), "not foo");
+ describe("NOT expressions", function () {
+ it("Text input with value where expressions expects no value", function () {
+ document.body.innerHTML = `
+
+
+
+ `;
+ const lab = document.getElementById("lab");
+ const handler = new DependsHandler(lab, "not foo");
+
expect(handler.evaluate()).toBe(false);
});
- it("Text input without value", function () {
- $("#lab").append(' ');
- var handler = new DependsHandler($("#lab"), "not foo");
+ it("Text input without value where expression expects no value", function () {
+ document.body.innerHTML = `
+
+
+
+ `;
+ const lab = document.getElementById("lab");
+ const handler = new DependsHandler(lab, "not foo");
+
expect(handler.evaluate()).toBe(true);
});
});
- describe("AND multiple expressions", function () {
+ describe("AND expressions", function () {
it("All options true", function () {
- $("#lab").html(
- [
- ' ',
- ' ',
- ].join("\n")
- );
- var handler = new DependsHandler($("#lab"), "one and two");
+ document.body.innerHTML = `
+
+
+
+
+ `;
+ const lab = document.getElementById("lab");
+ const handler = new DependsHandler(lab, "one and two");
+
expect(handler.evaluate()).toBe(true);
});
it("Not all options true", function () {
- $("#lab").html(
- [
- ' ',
- ' ',
- ].join("\n")
- );
- var handler = new DependsHandler($("#lab"), "one and two");
+ document.body.innerHTML = `
+
+
+
+
+ `;
+ const lab = document.getElementById("lab");
+ const handler = new DependsHandler(lab, "one and two");
+
expect(handler.evaluate()).toBe(false);
});
});
- describe("OR multiple expressions", function () {
+ describe("OR expressions", function () {
it("No options true", function () {
- $("#lab").html(
- [
- ' ',
- ' ',
- ].join("\n")
- );
- var handler = new DependsHandler($("#lab"), "one or two");
+ document.body.innerHTML = `
+
+
+
+
+ `;
+ const lab = document.getElementById("lab");
+ const handler = new DependsHandler(lab, "one or two");
+
expect(handler.evaluate()).toBe(false);
});
it("One option true", function () {
- $("#lab").html(
- [
- ' ',
- ' ',
- ].join("\n")
- );
- var handler = new DependsHandler($("#lab"), "one or two");
+ document.body.innerHTML = `
+
+
+
+
+ `;
+ const lab = document.getElementById("lab");
+ const handler = new DependsHandler(lab, "one or two");
+
expect(handler.evaluate()).toBe(true);
});
});
diff --git a/src/pat/depends/depends.js b/src/pat/depends/depends.js
index 2cc2a5e8c..43c62f427 100644
--- a/src/pat/depends/depends.js
+++ b/src/pat/depends/depends.js
@@ -1,8 +1,11 @@
import $ from "jquery";
-import Base from "../../core/base";
-import utils from "../../core/utils";
+import { BasePattern } from "@patternslib/patternslib/src/core/basepattern";
+import events from "../../core/events";
+import dom from "../../core/dom";
import logging from "../../core/logging";
-import Parser from "../../core/parser";
+import Parser from "@patternslib/patternslib/src/core/parser";
+import registry from "@patternslib/patternslib/src/core/registry";
+import utils from "../../core/utils";
const log = logging.getLogger("depends");
@@ -13,127 +16,109 @@ parser.addArgument("transition", "none", ["none", "css", "fade", "slide"]);
parser.addArgument("effect-duration", "fast");
parser.addArgument("effect-easing", "swing");
-export default Base.extend({
- name: "depends",
- trigger: ".pat-depends",
- jquery_plugin: true,
+class Pattern extends BasePattern {
+ static name = "depends";
+ static trigger = ".pat-depends";
+ static parser = parser;
- async init($el, opts) {
+ async init() {
+ this.$el = $(this.el);
const DependsHandler = (await import("../../lib/dependshandler")).default; // prettier-ignore
- const dependent = this.$el[0];
- const options = parser.parse(this.$el, opts);
- this.$modal = this.$el.parents(".pat-modal");
-
- let handler;
try {
- handler = new DependsHandler(this.$el, options.condition);
+ this.handler = new DependsHandler(this.el, this.options.condition);
} catch (e) {
- log.error("Invalid condition: " + e.message, dependent);
+ log.error("Invalid condition: " + e.message, this.el);
return;
}
- let state = handler.evaluate();
- switch (options.action) {
- case "show":
- utils.hideOrShow($el, state, options, this.name);
- this.updateModal();
- break;
- case "enable":
- if (state) this.enable();
- else this.disable();
- break;
- case "both":
- if (state) {
- utils.hideOrShow($el, state, options, this.name);
- this.updateModal();
- this.enable();
- } else {
- utils.hideOrShow($el, state, options, this.name);
- this.updateModal();
- this.disable();
- }
- break;
- }
+ // Initialize
+ this.set_state();
- const data = {
- handler: handler,
- options: options,
- dependent: dependent,
- };
+ for (const input of this.handler.getAllInputs()) {
+ events.add_event_listener(
+ input,
+ "change",
+ `pat-depends--change--${this.uuid}`, // We need to support multiple events per dependant ...
+ this.set_state.bind(this)
+ );
+ events.add_event_listener(
+ input,
+ "keyup",
+ `pat-depends--keyup--${this.uuid}`, // ... therefore we need to add a uuid to the event id ...
+ this.set_state.bind(this)
+ );
- for (let input of handler.getAllInputs()) {
if (input.form) {
- let $form = $(input.form);
- let dependents = $form.data("patDepends.dependents");
- if (!dependents) {
- dependents = [data];
- $form.on("reset.pat-depends", () => this.onReset);
- } else if (dependents.indexOf(data) === -1) dependents.push(data);
- $form.data("patDepends.dependents", dependents);
+ events.add_event_listener(
+ input.form,
+ "reset",
+ `pat-depends--reset--${this.uuid}`, // ... to not override previously set event handlers.
+ async () => {
+ // TODO: note sure, what this timeout is for.
+ await utils.timeout(50);
+ this.set_state.bind(this);
+ }
+ );
}
- $(input).on("change.pat-depends", null, data, this.onChange.bind(this));
- $(input).on("keyup.pat-depends", null, data, this.onChange.bind(this));
}
- },
+ }
- async onReset(event) {
- const dependents = $(event.target).data("patDepends.dependents");
- await utils.timeout(50);
- for (let dependent of dependents) {
- event.data = dependent;
- this.onChange(event);
- }
- },
+ update_modal() {
+ const modal = this.el.closest(".pat-modal");
- updateModal() {
// If we're in a modal, make sure that it gets resized.
- if (this.$modal.length) {
+ if (modal) {
$(document).trigger("pat-update", { pattern: "depends" });
}
- },
+ }
enable() {
- if (this.$el.is(":input")) {
- this.$el[0].disabled = null;
- } else if (this.$el.is("a")) {
- this.$el.off("click.patternDepends");
+ const inputs = dom.find_inputs(this.el);
+ for (const input of inputs) {
+ input.disabled = false;
+ // Trigger the change event after disabling so that any other bound actions can react on that.
+ input.dispatchEvent(events.change_event());
}
- this.$el.removeClass("disabled");
+ if (this.el.tagName === "A") {
+ events.remove_event_listener(this.el, "pat-depends--click");
+ }
+ this.el.classList.remove("disabled");
this.$el.trigger("pat-update", {
pattern: "depends",
action: "attribute-changed",
- dom: this.$el[0],
+ dom: this.el,
enabled: true,
});
- },
+ }
disable() {
- if (this.$el.is(":input")) {
- this.$el[0].disabled = "disabled";
- } else if (this.$el.is("a")) {
- this.$el.on("click.patternDepends", (e) => e.preventDefault());
+ const inputs = dom.find_inputs(this.el);
+ for (const input of inputs) {
+ input.disabled = true;
+ // Trigger the change event after disabling so that any other bound actions can react on that.
+ input.dispatchEvent(events.change_event());
+ }
+ if (this.el.tagName === "A") {
+ events.add_event_listener(this.el, "click", "pat-depends--click", (e) =>
+ e.preventDefault()
+ );
}
- this.$el.addClass("disabled");
+ this.el.classList.add("disabled");
this.$el.trigger("pat-update", {
pattern: "depends",
action: "attribute-changed",
- dom: this.$el[0],
+ dom: this.el,
enabled: false,
});
- },
-
- onChange(event) {
- const handler = event.data.handler;
- const options = event.data.options;
- const dependent = event.data.dependent;
- const $depdendent = $(dependent);
- const state = handler.evaluate();
+ }
- switch (options.action) {
+ set_state() {
+ const state = this.handler.evaluate();
+ switch (this.options.action) {
case "show":
- utils.hideOrShow($depdendent, state, options, this.name);
- this.updateModal();
+ utils.hideOrShow(this.el, state, this.options, this.name);
+ this.update_modal();
break;
case "enable":
if (state) {
@@ -143,8 +128,8 @@ export default Base.extend({
}
break;
case "both":
- utils.hideOrShow($depdendent, state, options, this.name);
- this.updateModal();
+ utils.hideOrShow(this.el, state, this.options, this.name);
+ this.update_modal();
if (state) {
this.enable();
} else {
@@ -152,5 +137,11 @@ export default Base.extend({
}
break;
}
- },
-});
+ }
+}
+
+// Register Pattern class in the global pattern registry
+registry.register(Pattern);
+
+// Make it available
+export default Pattern;
diff --git a/src/pat/depends/depends.test.js b/src/pat/depends/depends.test.js
index 1b9832398..433846ef4 100644
--- a/src/pat/depends/depends.test.js
+++ b/src/pat/depends/depends.test.js
@@ -1,4 +1,5 @@
import $ from "jquery";
+import dom from "../../core/dom";
import pattern from "./depends";
import utils from "../../core/utils";
@@ -19,11 +20,12 @@ describe("pat-depends", function () {
'
',
].join("\n")
);
- var $dependent = $("#dependent");
- pattern.init($dependent, { condition: "control" });
+
+ const el = document.querySelector(".pat-depends");
+ new pattern(el, { condition: "control" });
await utils.timeout(1); // wait a tick for async to settle.
- expect($dependent.css("display")).toBe("none");
+ expect($(el).css("display")).toBe("none");
});
it("Show if condition is not met initially", async function () {
@@ -33,10 +35,12 @@ describe("pat-depends", function () {
'
',
].join("\n")
);
- var $dependent = $("#dependent");
- pattern.init($dependent, { condition: "control" });
+
+ const el = document.querySelector(".pat-depends");
+ new pattern(el, { condition: "control" });
await utils.timeout(1); // wait a tick for async to settle.
- expect($dependent.css("display")).not.toBe("none");
+
+ expect($(el).css("display")).not.toBe("none");
});
});
@@ -56,14 +60,14 @@ describe("pat-depends", function () {
'Click me ',
].join("\n")
);
- var pat = pattern.init($(".pat-depends"), {
- condition: "control",
- });
+
+ const el = document.querySelector(".pat-depends");
+ const instance = new pattern(el, { condition: "control" });
await utils.timeout(1); // wait a tick for async to settle.
- var $dependent = $("#dependent");
- pat.disable();
- expect($dependent[0].disabled).toBeTruthy();
- expect($dependent.hasClass("disabled")).toBe(true);
+
+ instance.disable();
+ expect(el.disabled).toBeTruthy();
+ expect(el.classList.contains("disabled")).toBe(true);
});
it("Anchor", async function () {
@@ -73,14 +77,13 @@ describe("pat-depends", function () {
'Click me ',
].join("\n")
);
- var pat = pattern.init($(".pat-depends"), { condition: "control" });
+
+ const el = document.querySelector(".pat-depends");
+ const instance = new pattern(el, { condition: "control" });
await utils.timeout(1); // wait a tick for async to settle.
- var $dependent = $("#lab a");
- pat.disable();
- var events = $._data($dependent[0]).events;
- expect($dependent.hasClass("disabled")).toBe(true);
- expect(events.click).toBeDefined();
- expect(events.click[0].namespace).toBe("patternDepends");
+
+ instance.disable();
+ expect(el.classList.contains("disabled")).toBe(true);
});
});
@@ -100,14 +103,14 @@ describe("pat-depends", function () {
'Click me ',
].join("\n")
);
- var pat = pattern.init($(".pat-depends"), {
- condition: "control",
- });
+
+ const el = document.querySelector(".pat-depends");
+ const instance = new pattern(el, { condition: "control" });
await utils.timeout(1); // wait a tick for async to settle.
- var $dependent = $("#lab button");
- pat.enable();
- expect($dependent[0].disabled).toBeFalsy();
- expect($dependent.hasClass("disabled")).toBe(false);
+
+ instance.enable();
+ expect(el.disabled).toBeFalsy();
+ expect(el.classList.contains("disabled")).toBe(false);
});
it("Anchor", async function () {
@@ -117,15 +120,13 @@ describe("pat-depends", function () {
'Click me ',
].join("\n")
);
- var pat = pattern.init($(".pat-depends"), {
- condition: "control",
- });
+
+ const el = document.querySelector(".pat-depends");
+ const instance = new pattern(el, { condition: "control" });
await utils.timeout(1); // wait a tick for async to settle.
- var $dependent = $("#lab a");
- $dependent.on("click.patternDepends", false);
- pat.enable();
- expect($dependent.hasClass("disabled")).toBe(false);
- expect($._data($dependent[0]).events).toBe(undefined);
+
+ instance.enable();
+ expect(el.classList.contains("disabled")).toBe(false);
});
});
@@ -187,4 +188,73 @@ describe("pat-depends", function () {
expect(data.enabled).toBe(false);
});
});
+
+ describe("5 - Support pat-depends within a pat-depends controlled tree.", function () {
+
+ it("Also updates pat-depends within a pat-depends controlled tree", async function () {
+
+ document.body.innerHTML = `
+
+ `;
+
+ const dep1 = document.querySelector(".dep1");
+ new pattern(dep1);
+ await utils.timeout(1); // wait a tick for async to settle.
+ const dep2 = document.querySelector(".dep2");
+ new pattern(dep2);
+ await utils.timeout(1); // wait a tick for async to settle.
+
+ const button1 = document.querySelector("[name=show-tree]");
+ const button2 = document.querySelector("[name=extra]");
+
+ button1.checked = true;
+ button1.dispatchEvent(new Event("change"));
+ button2.checked = true;
+ button2.dispatchEvent(new Event("change"));
+
+ expect(dom.is_visible(dep1)).toBe(true);
+ expect(dom.is_visible(dep2)).toBe(true);
+
+
+ // Even though button2 is still checked, the visibility of dep2 is
+ // hidden.
+ button1.checked = false;
+ button1.dispatchEvent(new Event("change"));
+
+ expect(dom.is_visible(dep1)).toBe(false);
+ expect(dom.is_visible(dep2)).toBe(false);
+ });
+
+ });
});
diff --git a/src/pat/depends/index.html b/src/pat/depends/index.html
index 2f0df129c..c7e6095aa 100644
--- a/src/pat/depends/index.html
+++ b/src/pat/depends/index.html
@@ -2,139 +2,193 @@
Depends demo
-
+
-
- pat-depends with checkboxes, radiobuttons and multiselects
-
+
-
-
-
-
-
+