Skip to content

Commit e2fb089

Browse files
authored
Merge pull request #826 from Patternslib/core-fixes
Patternslib different enhancements and additions
2 parents 384ef56 + dfc9411 commit e2fb089

File tree

13 files changed

+346
-33
lines changed

13 files changed

+346
-33
lines changed

CHANGES.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030
### Features
3131

32+
- core registry: Do not scan patterns within trees with attribute ``hidden`` or class ``cant-touch-this``.
3233
- Implenent lazy loading for external libraries via dynamic imports. Leads to significantly reduced bundle sizes.
3334
- Upgrade pat-calendar to use latest fullcalendar version (5.3.0).
3435
- pat calendar: Add fullcalendar list views.
@@ -48,6 +49,7 @@
4849
- Allow overriding the public path from outside via the definition of a ``window.__patternslib_public_path__`` global variable.
4950
- Introduce new ``core/dom`` module for DOM manipulation and traversing.
5051
``core/dom`` includes methods which help transition from jQuery to the JavaScript DOM API.
52+
- core dom: Add ``get_parents`` to return all parent elements from a given DOM node.
5153
- core dom: Add ``toNodeArray`` to return an array of DOM nodes if a NodeList, single DOM node or a jQuery object was passed.
5254
- core dom: Add ``querySelectorAllAndMe`` to do a querySelectorAll including the starter element.
5355
- core dom: Add ``wrap`` wrap an element with a wrapper element.
@@ -72,6 +74,9 @@
7274

7375
### Technical
7476

77+
- core polyfills: Add polyfill for Node.closest method.
78+
- Core Base: ``await`` for initalization in the base class constructor, so that the ``init`` event is really thrown after initialization is done.
79+
- pat calendar: Explicitly import JavaScript language files to avoid missing Webpack TypeScript loader errors.
7580
- Use Babel for all files, allowing latest JavaScript features everywhere.
7681
- Add example `minimalpattern`.
7782
- Replace `slave` terminology with `dependent`.
@@ -101,6 +106,7 @@
101106

102107
### Fixes
103108

109+
- core dom is_visible: Mock in tests to check only for hidden to avoid unavailable offsetWidth/offsetHeight in Jest.
104110
- pat calendar, pat checklist, pat datetime-picker: Dispatch DOM events with bubbling and canceling features enabled, as real DOM events do.
105111
Fixes a problem where calendar categories did not show their initial state correctly.
106112
- pat inject: Make sure that nested pat-inject element have the correct context for target ``self``. Fixes: https://github.com/quaive/ploneintranet.prototype/issues/1164

src/core/base.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
* Older Patternslib patterns on the other hand have a single global scope for
1212
* all DOM elements.
1313
*/
14-
14+
import "regenerator-runtime/runtime"; // needed for ``await`` support
1515
import $ from "jquery";
1616
import Registry from "./registry";
1717
import logging from "./logging";
@@ -41,14 +41,14 @@ const initBasePattern = function ($el, options, trigger) {
4141
return pattern;
4242
};
4343

44-
const Base = function ($el, options, trigger) {
44+
const Base = async function ($el, options, trigger) {
4545
if (!$el.jquery) {
4646
$el = $($el);
4747
}
4848
this.$el = $el;
4949
this.el = $el[0];
5050
this.options = $.extend(true, {}, this.defaults || {}, options || {});
51-
this.init($el, options, trigger);
51+
await this.init($el, options, trigger);
5252
this.emit("init");
5353
};
5454

src/core/base.test.js

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import registry from "./registry";
22
import $ from "jquery";
33
import Base from "./base";
4+
import utils from "./utils";
45
import _ from "underscore";
56

67
describe("pat-base: The Base class for patterns", function () {
@@ -17,7 +18,6 @@ describe("pat-base: The Base class for patterns", function () {
1718
it("can be extended and used in similar way as classes", function () {
1819
var Tmp = Base.extend({
1920
name: "example",
20-
trigger: "pat-example",
2121
some: "thing",
2222
init: function () {
2323
expect(this.$el.hasClass("pat-example")).toEqual(true);
@@ -35,7 +35,6 @@ describe("pat-base: The Base class for patterns", function () {
3535
it("Accepts jQuery objects on initialization", function () {
3636
const Tmp = Base.extend({
3737
name: "example",
38-
trigger: "pat-example",
3938
init: () => {},
4039
});
4140
const $el = $('<div class="pat-example"/>');
@@ -48,7 +47,6 @@ describe("pat-base: The Base class for patterns", function () {
4847
it("Accepts plain DOM nodes on initialization", function () {
4948
const Tmp = Base.extend({
5049
name: "example",
51-
trigger: "pat-example",
5250
init: () => {},
5351
});
5452
const node = document.createElement("div");
@@ -108,7 +106,6 @@ describe("pat-base: The Base class for patterns", function () {
108106
it("can be extended multiple times", function () {
109107
var Tmp1 = Base.extend({
110108
name: "thing",
111-
trigger: "pat-thing",
112109
something: "else",
113110
init: function () {
114111
expect(this.some).toEqual("thing3");
@@ -117,7 +114,6 @@ describe("pat-base: The Base class for patterns", function () {
117114
});
118115
var Tmp2 = Tmp1.extend({
119116
name: "thing",
120-
trigger: "pat-thing",
121117
some: "thing2",
122118
init: function () {
123119
expect(this.some).toEqual("thing3");
@@ -129,7 +125,6 @@ describe("pat-base: The Base class for patterns", function () {
129125
});
130126
var Tmp3 = Tmp2.extend({
131127
name: "thing",
132-
trigger: "pat-thing",
133128
some: "thing3",
134129
init: function () {
135130
expect(this.some).toEqual("thing3");
@@ -157,4 +152,30 @@ describe("pat-base: The Base class for patterns", function () {
157152
})
158153
);
159154
});
155+
156+
it("triggers the init event after init has finished.", async function (done) {
157+
const Tmp = Base.extend({
158+
name: "example",
159+
init: async function () {
160+
// await to actually give the Base constructor a chance to
161+
// throw it's event before we throw it here.
162+
await utils.timeout(1);
163+
this.el.dispatchEvent(new Event("init_done"));
164+
},
165+
});
166+
const node = document.createElement("div");
167+
node.setAttribute("class", "pat-example");
168+
const event_list = [];
169+
node.addEventListener("init_done", () => event_list.push("pat init"));
170+
$(node).on("init.example.patterns", () => event_list.push("base init"));
171+
new Tmp(node);
172+
173+
// await until all asyncs are settled. 1 event loop should be enough.
174+
await utils.timeout(1);
175+
176+
expect(event_list[0]).toBe("pat init");
177+
expect(event_list[1]).toBe("base init");
178+
179+
done();
180+
});
160181
});

src/core/dom.js

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,11 @@ const find_parents = (el, selector) => {
5757
// This matches against all parents but not the element itself.
5858
// The order of elements is from the search starting point up to higher
5959
// DOM levels.
60-
let parent =
61-
(el?.parentNode?.closest && el.parentNode.closest(selector)) || null;
6260
const ret = [];
61+
let parent = el?.parentNode?.closest?.(selector);
6362
while (parent) {
6463
ret.push(parent);
65-
parent = parent.parentNode?.closest(selector) || null;
64+
parent = parent.parentNode?.closest?.(selector);
6665
}
6766
return ret;
6867
};
@@ -75,6 +74,19 @@ const find_scoped = (el, selector) => {
7574
);
7675
};
7776

77+
const get_parents = (el) => {
78+
// Return all HTMLElement parents of el, starting from the direct parent of el.
79+
// The document itself is excluded because it's not a real DOM node.
80+
const parents = [];
81+
let parent = el?.parentNode;
82+
while (parent) {
83+
parents.push(parent);
84+
parent = parent?.parentNode;
85+
parent = parent instanceof HTMLElement ? parent : null;
86+
}
87+
return parents;
88+
};
89+
7890
const is_visible = (el) => {
7991
// Check, if element is visible in DOM.
8092
// https://stackoverflow.com/a/19808107/1337474
@@ -96,6 +108,7 @@ const dom = {
96108
show: show,
97109
find_parents: find_parents,
98110
find_scoped: find_scoped,
111+
get_parents: get_parents,
99112
is_visible: is_visible,
100113
create_from_string: create_from_string,
101114
};

src/core/dom.test.js

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,6 @@ describe("core.dom tests", () => {
184184
it("don't break with DocumentFragment without a parent.", (done) => {
185185
const el = new DocumentFragment();
186186
el.innerHTML = `<div class="starthere"></div>`;
187-
console.log(el.parentNode);
188187
const res = dom.find_parents(
189188
el.querySelector(".starthere"),
190189
".findme"
@@ -242,6 +241,46 @@ describe("core.dom tests", () => {
242241
});
243242
});
244243

244+
describe("get_parents", () => {
245+
it("it finds all parents of an element except document itself.", (done) => {
246+
document.body.innerHTML = `
247+
<div class="level1">
248+
<div class="level2">
249+
<div class="level3">
250+
<div class="level4">
251+
</div>
252+
</div>
253+
</div>
254+
</div>
255+
`;
256+
const res = dom.get_parents(document.querySelector(".level4"));
257+
258+
expect(res.length).toEqual(5);
259+
expect(res[0]).toEqual(document.querySelector(".level3"));
260+
expect(res[1]).toEqual(document.querySelector(".level2"));
261+
expect(res[2]).toEqual(document.querySelector(".level1"));
262+
expect(res[3]).toEqual(document.body);
263+
expect(res[4]).toEqual(document.body.parentNode); // html
264+
265+
done();
266+
});
267+
it("don't break with no element.", (done) => {
268+
const res = dom.get_parents(null);
269+
expect(res.length).toEqual(0);
270+
271+
done();
272+
});
273+
it("don't break with DocumentFragment without a parent.", (done) => {
274+
const el = new DocumentFragment();
275+
el.innerHTML = `<div class="starthere"></div>`;
276+
console.log(el.parentNode);
277+
const res = dom.get_parents(el.querySelector(".starthere"));
278+
expect(res.length).toEqual(0);
279+
280+
done();
281+
});
282+
});
283+
245284
describe("is_visible", () => {
246285
it.skip("checks, if an element is visible or not.", (done) => {
247286
const div1 = document.createElement("div");

src/core/registry.js

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -145,11 +145,16 @@ const registry = {
145145
selectors.map((it) => it.trim().replace(/,$/, "")).join(",")
146146
);
147147
matches = matches.filter((el) => {
148-
// Filter out code examples wrapped in <pre> elements.
149-
// Also filter special class ``.cant-touch-this``
148+
// Filter out patterns:
149+
// - with class ``.cant-touch-this``
150+
// - wrapped in ``.cant-touch-this`` elements
151+
// - wrapped in ``hidden`` elements
152+
// - wrapped in ``<pre>`` elements
150153
return (
151-
dom.find_parents(el, "pre").length === 0 &&
152-
!el.matches(".cant-touch-this")
154+
!el.matches(".cant-touch-this") &&
155+
!el?.parentNode?.closest?.(".cant-touch-this") &&
156+
!el?.parentNode?.closest?.("[hidden]") &&
157+
!el?.parentNode?.closest?.("pre")
153158
);
154159
});
155160

src/core/registry.test.js

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import Base from "./base";
2+
import registry from "./registry";
3+
4+
describe("pat-registry: The registry for patterns", function () {
5+
const patterns = registry.patterns;
6+
7+
beforeEach(function () {
8+
registry.clear();
9+
});
10+
11+
afterEach(function () {
12+
registry.patterns = patterns;
13+
});
14+
15+
it("Does initialize a simple pattern when scanning a DOM tree", function () {
16+
// Base extend also registers the pattern.
17+
Base.extend({
18+
name: "example",
19+
trigger: ".pat-example",
20+
init: function () {
21+
this.el.innerHTML = "initialized";
22+
},
23+
});
24+
25+
const tree = document.createElement("div");
26+
tree.setAttribute("class", "pat-example");
27+
registry.scan(tree);
28+
expect(tree.textContent).toBe("initialized");
29+
});
30+
31+
it("Does initialize a tree of simple patterns when scanning a DOM tree", function () {
32+
Base.extend({
33+
name: "example1",
34+
trigger: ".pat-example1",
35+
init: function () {
36+
this.el.innerHTML = "initialized1";
37+
},
38+
});
39+
40+
Base.extend({
41+
name: "example2",
42+
trigger: ".pat-example2",
43+
init: function () {
44+
this.el.innerHTML = "initialized2";
45+
},
46+
});
47+
48+
const tree = document.createElement("div");
49+
tree.innerHTML = `
50+
<div class="e1 pat-example1"></div>
51+
<div class="e2 pat-example2"></div>
52+
<div class="e3 pat-example1"></div>
53+
<div class="e4 pat-example2"></div>
54+
`;
55+
registry.scan(tree);
56+
expect(tree.querySelector(".e1").textContent).toBe("initialized1");
57+
expect(tree.querySelector(".e2").textContent).toBe("initialized2");
58+
expect(tree.querySelector(".e3").textContent).toBe("initialized1");
59+
expect(tree.querySelector(".e4").textContent).toBe("initialized2");
60+
});
61+
62+
it("Does not initialize patterns matching a filter", function () {
63+
Base.extend({
64+
name: "example",
65+
trigger: ".pat-example",
66+
init: function () {
67+
this.el.innerHTML = "initialized";
68+
},
69+
});
70+
71+
const tree = document.createElement("div");
72+
tree.innerHTML = `
73+
<div class="e1 pat-example"></div>
74+
<div class="e2 cant-touch-this pat-example"></div>
75+
<div class="cant-touch-this">
76+
<div class="e3 pat-example"></div>
77+
</div>
78+
<div hidden>
79+
<div class="e4 pat-example"></div>
80+
</div>
81+
<div hidden="hidden">
82+
<div class="e5 pat-example"></div>
83+
</div>
84+
<pre>
85+
<div>
86+
<div class="e6 pat-example"></div>
87+
</div>
88+
</pre>
89+
`;
90+
registry.scan(tree);
91+
92+
expect(tree.querySelector(".e1").textContent).toBe("initialized");
93+
expect(tree.querySelector(".e2").textContent).toBe("");
94+
expect(tree.querySelector(".e3").textContent).toBe("");
95+
expect(tree.querySelector(".e4").textContent).toBe("");
96+
expect(tree.querySelector(".e5").textContent).toBe("");
97+
expect(tree.querySelector(".e6").textContent).toBe("");
98+
});
99+
});

src/pat/calendar/calendar.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,9 @@ export default Base.extend({
165165
// we don't support any country-specific language variants, always use first 2 letters
166166
lang = lang.substr(0, 2).toLowerCase();
167167
if (lang !== "en") {
168-
const locale = await import(`@fullcalendar/core/locales/${lang}`);
168+
const locale = await import(
169+
`@fullcalendar/core/locales/${lang}.js`
170+
);
169171
config.locale = locale.default;
170172
console.log("loaded cal locale for " + lang);
171173
}

0 commit comments

Comments
 (0)