Skip to content

Commit 5d46d28

Browse files
committed
maint(pat navigationmarker): Simplify and improve the implementation.
1 parent a04c110 commit 5d46d28

File tree

2 files changed

+211
-39
lines changed

2 files changed

+211
-39
lines changed
Lines changed: 72 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,87 @@
1-
import $ from "jquery";
21
import Base from "@patternslib/patternslib/src/core/base";
2+
import Parser from "@patternslib/patternslib/src/core/parser";
3+
4+
export const parser = new Parser("navigation");
5+
parser.addArgument("item-wrapper", null);
6+
parser.addArgument("in-path-class", "inPath");
7+
parser.addArgument("current-class", "current");
38

49
export default Base.extend({
510
name: "navigationmarker",
611
trigger: ".pat-navigationmarker",
712
parser: "mockup",
813
init() {
9-
const portal_url = document.body.dataset.portalUrl;
10-
const href =
11-
document.querySelector('head link[rel="canonical"]').href ||
12-
window.location.href;
13-
const hrefParts = href.split("/");
14+
this.options = parser.parse(this.el, this.options);
15+
this.mark_items();
16+
},
17+
18+
mark_items(url) {
19+
// Mark all navigation items that are in the path of the current url
20+
21+
const current_url = url || this.base_url();
22+
const current_url_prepared = this.prepare_url(current_url);
1423

24+
const portal_url = this.prepare_url(document.body.dataset?.portalUrl);
1525
const nav_items = this.el.querySelectorAll("a");
1626

1727
for (const nav_item of nav_items) {
18-
const navlink = nav_item.getAttribute("href", "").replace("/view", "");
19-
if (href.indexOf(navlink) !== -1) {
20-
const parent = $(nav_item).parent();
21-
22-
// check the input-openers within the path
23-
const check = parent.find("> input");
24-
if (check.length) {
25-
check[0].checked = true;
26-
}
27-
28-
// set "inPath" to all nav items which are within the current path
29-
// check if parts of navlink are in canonical url parts
30-
const navParts = navlink.split("/");
31-
let inPath = false;
32-
for (let i = 0, size = navParts.length; i < size; i++) {
33-
// The last path-part must match.
34-
inPath = false;
35-
if (navParts[i] === hrefParts[i]) {
36-
inPath = true;
37-
}
38-
}
39-
if (navlink === portal_url && href !== portal_url) {
40-
// Avoid marking "Home" with "inPath", when not actually there
41-
inPath = false;
42-
}
43-
if (inPath) {
44-
parent.addClass("inPath");
45-
}
46-
47-
// set "current" to the current selected nav item, if it is in the navigation structure.
48-
if (href === navlink) {
49-
parent.addClass("current");
50-
}
28+
// Get the nav item's url and rebase it against the current url to
29+
// make absolute or relative URLs FQDN URLs.
30+
const nav_url = this.prepare_url(
31+
new URL(nav_item.getAttribute("href", ""), current_url)?.href
32+
);
33+
34+
const wrapper = this.options.itemWrapper
35+
? nav_item.closest(this.options.itemWrapper)
36+
: nav_item.parentNode;
37+
38+
if (nav_url === current_url_prepared) {
39+
wrapper.classList.add(this.options.currentClass);
40+
} else if (
41+
// Compare the current navigation item url with a slash at the
42+
// end - if it is "inPath" it must have a slash in it.
43+
current_url_prepared.indexOf(`${nav_url}/`) === 0 &&
44+
// Do not set inPath for the "Home" url, as this would always
45+
// be in the path.
46+
nav_url !== portal_url
47+
) {
48+
wrapper.classList.add(this.options.inPathClass);
49+
} else {
50+
// Not even in path.
51+
continue;
5152
}
53+
54+
// The path was at least found in the current url, so we need to
55+
// check the input-openers within the path
56+
// Find the first input which is the correct one, even if this
57+
// navigation item has many children.
58+
// These hidden checkboxes are used to open the navigation item for
59+
// mobile navigation.
60+
const check = wrapper.querySelector("input");
61+
if (check) check.checked = true;
5262
}
5363
},
64+
65+
clear_items() {
66+
// Clear all navigation items from the inPath and current classes
67+
68+
const items = this.el.querySelectorAll(
69+
`.${this.options.inPathClass}, .${this.options.currentClass}`
70+
);
71+
for (const item of items) {
72+
item.classList.remove(this.options.inPathClass);
73+
item.classList.remove(this.options.currentClass);
74+
}
75+
},
76+
77+
prepare_url(url) {
78+
return url?.replace("/view", "").replaceAll("@@", "").replace(/\/$/, "");
79+
},
80+
81+
base_url() {
82+
return this.prepare_url(
83+
document.querySelector('head link[rel="canonical"]')?.href ||
84+
window.location.href
85+
);
86+
},
5487
});
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
import Pattern from "./navigationmarker";
2+
3+
describe("pat-navigationmarker", () => {
4+
let _window_location;
5+
6+
beforeEach(() => {
7+
_window_location = global.window.location;
8+
delete global.window.location;
9+
document.body.innerHTML = "";
10+
});
11+
12+
afterEach(() => {
13+
global.window.location = _window_location;
14+
});
15+
16+
const set_url = (url, portal_url) => {
17+
global.window.location = {
18+
href: url,
19+
};
20+
21+
portal_url = portal_url || url;
22+
23+
document.body.dataset.portalUrl = portal_url;
24+
};
25+
26+
it("navigationmarker roundtrip", () => {
27+
document.body.innerHTML = `
28+
<nav class="pat-navigationmarker">
29+
<ul>
30+
<li>
31+
<a href="/">Home</a>
32+
</li>
33+
<li>
34+
<a href="/path1">p1</a>
35+
</li>
36+
<li>
37+
<a href="/path2">p2</a>
38+
<ul>
39+
<li>
40+
<a href="/path2/path2.1">p2.1</a>
41+
</li>
42+
<li>
43+
<a href="/path2/path2.2">p2.2</a>
44+
<ul>
45+
<li>
46+
<a href="/path2/path2.2/path2.2.1">p2.2.1</a>
47+
</li>
48+
<li>
49+
<a href="/path2/path2.2/path2.2.2">p2.2.2</a>
50+
</li>
51+
</ul>
52+
</li>
53+
<li>
54+
<a href="../../path3">p1</a>
55+
</li>
56+
<li>
57+
<a href="https://patternslib.com/path4">p1</a>
58+
</li>
59+
</ul>
60+
</li>
61+
</ul>
62+
</nav>
63+
`;
64+
65+
set_url("https://patternslib.com/");
66+
67+
const instance = new Pattern(document.querySelector(".pat-navigationmarker"));
68+
69+
const it0 = document.querySelector("a[href='/']");
70+
const it1 = document.querySelector("a[href='/path1']");
71+
const it2 = document.querySelector("a[href='/path2']");
72+
const it21 = document.querySelector("a[href='/path2/path2.1']");
73+
const it22 = document.querySelector("a[href='/path2/path2.2']");
74+
const it221 = document.querySelector("a[href='/path2/path2.2/path2.2.1']");
75+
const it222 = document.querySelector("a[href='/path2/path2.2/path2.2.2']");
76+
const it3 = document.querySelector("a[href='../../path3']");
77+
const it4 = document.querySelector("a[href='https://patternslib.com/path4']");
78+
79+
expect(document.querySelectorAll(".current").length).toBe(1);
80+
expect(document.querySelectorAll(".inPath").length).toBe(0);
81+
expect(document.querySelector(".current a")).toBe(it0);
82+
83+
instance.clear_items();
84+
instance.mark_items("https://patternslib.com/path1");
85+
86+
expect(document.querySelectorAll(".current").length).toBe(1);
87+
expect(document.querySelectorAll(".inPath").length).toBe(0);
88+
expect(document.querySelector(".current a")).toBe(it1);
89+
90+
instance.clear_items();
91+
instance.mark_items("https://patternslib.com/path2");
92+
93+
expect(document.querySelectorAll(".current").length).toBe(1);
94+
expect(document.querySelectorAll(".inPath").length).toBe(0);
95+
expect(document.querySelector(".current a")).toBe(it2);
96+
97+
instance.clear_items();
98+
instance.mark_items("https://patternslib.com/path2/path2.1");
99+
100+
expect(document.querySelectorAll(".current").length).toBe(1);
101+
expect(document.querySelectorAll(".inPath").length).toBe(1);
102+
expect(document.querySelector(".current a")).toBe(it21);
103+
104+
instance.clear_items();
105+
instance.mark_items("https://patternslib.com/path2/path2.2");
106+
107+
expect(document.querySelectorAll(".current").length).toBe(1);
108+
expect(document.querySelectorAll(".inPath").length).toBe(1);
109+
expect(document.querySelector(".current a")).toBe(it22);
110+
111+
instance.clear_items();
112+
instance.mark_items("https://patternslib.com/path2/path2.2/path2.2.1");
113+
114+
expect(document.querySelectorAll(".current").length).toBe(1);
115+
expect(document.querySelectorAll(".inPath").length).toBe(2);
116+
expect(document.querySelector(".current a")).toBe(it221);
117+
118+
instance.clear_items();
119+
instance.mark_items("https://patternslib.com/path2/path2.2/path2.2.2");
120+
121+
expect(document.querySelectorAll(".current").length).toBe(1);
122+
expect(document.querySelectorAll(".inPath").length).toBe(2);
123+
expect(document.querySelector(".current a")).toBe(it222);
124+
125+
instance.clear_items();
126+
instance.mark_items("https://patternslib.com/path3");
127+
128+
expect(document.querySelectorAll(".current").length).toBe(1);
129+
expect(document.querySelectorAll(".inPath").length).toBe(0);
130+
expect(document.querySelector(".current a")).toBe(it3);
131+
132+
instance.clear_items();
133+
instance.mark_items("https://patternslib.com/path4");
134+
135+
expect(document.querySelectorAll(".current").length).toBe(1);
136+
expect(document.querySelectorAll(".inPath").length).toBe(0);
137+
expect(document.querySelector(".current a")).toBe(it4);
138+
});
139+
});

0 commit comments

Comments
 (0)