Skip to content

Commit

Permalink
maint(pat navigationmarker): Simplify and improve the implementation.
Browse files Browse the repository at this point in the history
  • Loading branch information
thet committed Feb 13, 2023
1 parent a04c110 commit 5d46d28
Show file tree
Hide file tree
Showing 2 changed files with 211 additions and 39 deletions.
111 changes: 72 additions & 39 deletions src/pat/navigationmarker/navigationmarker.js
Original file line number Diff line number Diff line change
@@ -1,54 +1,87 @@
import $ from "jquery";
import Base from "@patternslib/patternslib/src/core/base";
import Parser from "@patternslib/patternslib/src/core/parser";

export const parser = new Parser("navigation");
parser.addArgument("item-wrapper", null);
parser.addArgument("in-path-class", "inPath");
parser.addArgument("current-class", "current");

export default Base.extend({
name: "navigationmarker",
trigger: ".pat-navigationmarker",
parser: "mockup",
init() {
const portal_url = document.body.dataset.portalUrl;
const href =
document.querySelector('head link[rel="canonical"]').href ||
window.location.href;
const hrefParts = href.split("/");
this.options = parser.parse(this.el, this.options);
this.mark_items();
},

mark_items(url) {
// Mark all navigation items that are in the path of the current url

const current_url = url || this.base_url();
const current_url_prepared = this.prepare_url(current_url);

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

for (const nav_item of nav_items) {
const navlink = nav_item.getAttribute("href", "").replace("/view", "");
if (href.indexOf(navlink) !== -1) {
const parent = $(nav_item).parent();

// check the input-openers within the path
const check = parent.find("> input");
if (check.length) {
check[0].checked = true;
}

// set "inPath" to all nav items which are within the current path
// check if parts of navlink are in canonical url parts
const navParts = navlink.split("/");
let inPath = false;
for (let i = 0, size = navParts.length; i < size; i++) {
// The last path-part must match.
inPath = false;
if (navParts[i] === hrefParts[i]) {
inPath = true;
}
}
if (navlink === portal_url && href !== portal_url) {
// Avoid marking "Home" with "inPath", when not actually there
inPath = false;
}
if (inPath) {
parent.addClass("inPath");
}

// set "current" to the current selected nav item, if it is in the navigation structure.
if (href === navlink) {
parent.addClass("current");
}
// Get the nav item's url and rebase it against the current url to
// make absolute or relative URLs FQDN URLs.
const nav_url = this.prepare_url(
new URL(nav_item.getAttribute("href", ""), current_url)?.href
);

const wrapper = this.options.itemWrapper
? nav_item.closest(this.options.itemWrapper)
: nav_item.parentNode;

if (nav_url === current_url_prepared) {
wrapper.classList.add(this.options.currentClass);
} else if (
// Compare the current navigation item url with a slash at the
// end - if it is "inPath" it must have a slash in it.
current_url_prepared.indexOf(`${nav_url}/`) === 0 &&
// Do not set inPath for the "Home" url, as this would always
// be in the path.
nav_url !== portal_url
) {
wrapper.classList.add(this.options.inPathClass);
} else {
// Not even in path.
continue;
}

// The path was at least found in the current url, so we need to
// check the input-openers within the path
// Find the first input which is the correct one, even if this
// navigation item has many children.
// These hidden checkboxes are used to open the navigation item for
// mobile navigation.
const check = wrapper.querySelector("input");
if (check) check.checked = true;
}
},

clear_items() {
// Clear all navigation items from the inPath and current classes

const items = this.el.querySelectorAll(
`.${this.options.inPathClass}, .${this.options.currentClass}`
);
for (const item of items) {
item.classList.remove(this.options.inPathClass);
item.classList.remove(this.options.currentClass);
}
},

prepare_url(url) {
return url?.replace("/view", "").replaceAll("@@", "").replace(/\/$/, "");
},

base_url() {
return this.prepare_url(
document.querySelector('head link[rel="canonical"]')?.href ||
window.location.href
);
},
});
139 changes: 139 additions & 0 deletions src/pat/navigationmarker/navigationmarker.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import Pattern from "./navigationmarker";

describe("pat-navigationmarker", () => {
let _window_location;

beforeEach(() => {
_window_location = global.window.location;
delete global.window.location;
document.body.innerHTML = "";
});

afterEach(() => {
global.window.location = _window_location;
});

const set_url = (url, portal_url) => {
global.window.location = {
href: url,
};

portal_url = portal_url || url;

document.body.dataset.portalUrl = portal_url;
};

it("navigationmarker roundtrip", () => {
document.body.innerHTML = `
<nav class="pat-navigationmarker">
<ul>
<li>
<a href="/">Home</a>
</li>
<li>
<a href="/path1">p1</a>
</li>
<li>
<a href="/path2">p2</a>
<ul>
<li>
<a href="/path2/path2.1">p2.1</a>
</li>
<li>
<a href="/path2/path2.2">p2.2</a>
<ul>
<li>
<a href="/path2/path2.2/path2.2.1">p2.2.1</a>
</li>
<li>
<a href="/path2/path2.2/path2.2.2">p2.2.2</a>
</li>
</ul>
</li>
<li>
<a href="../../path3">p1</a>
</li>
<li>
<a href="https://patternslib.com/path4">p1</a>
</li>
</ul>
</li>
</ul>
</nav>
`;

set_url("https://patternslib.com/");

const instance = new Pattern(document.querySelector(".pat-navigationmarker"));

const it0 = document.querySelector("a[href='/']");
const it1 = document.querySelector("a[href='/path1']");
const it2 = document.querySelector("a[href='/path2']");
const it21 = document.querySelector("a[href='/path2/path2.1']");
const it22 = document.querySelector("a[href='/path2/path2.2']");
const it221 = document.querySelector("a[href='/path2/path2.2/path2.2.1']");
const it222 = document.querySelector("a[href='/path2/path2.2/path2.2.2']");
const it3 = document.querySelector("a[href='../../path3']");
const it4 = document.querySelector("a[href='https://patternslib.com/path4']");

expect(document.querySelectorAll(".current").length).toBe(1);
expect(document.querySelectorAll(".inPath").length).toBe(0);
expect(document.querySelector(".current a")).toBe(it0);

instance.clear_items();
instance.mark_items("https://patternslib.com/path1");

expect(document.querySelectorAll(".current").length).toBe(1);
expect(document.querySelectorAll(".inPath").length).toBe(0);
expect(document.querySelector(".current a")).toBe(it1);

instance.clear_items();
instance.mark_items("https://patternslib.com/path2");

expect(document.querySelectorAll(".current").length).toBe(1);
expect(document.querySelectorAll(".inPath").length).toBe(0);
expect(document.querySelector(".current a")).toBe(it2);

instance.clear_items();
instance.mark_items("https://patternslib.com/path2/path2.1");

expect(document.querySelectorAll(".current").length).toBe(1);
expect(document.querySelectorAll(".inPath").length).toBe(1);
expect(document.querySelector(".current a")).toBe(it21);

instance.clear_items();
instance.mark_items("https://patternslib.com/path2/path2.2");

expect(document.querySelectorAll(".current").length).toBe(1);
expect(document.querySelectorAll(".inPath").length).toBe(1);
expect(document.querySelector(".current a")).toBe(it22);

instance.clear_items();
instance.mark_items("https://patternslib.com/path2/path2.2/path2.2.1");

expect(document.querySelectorAll(".current").length).toBe(1);
expect(document.querySelectorAll(".inPath").length).toBe(2);
expect(document.querySelector(".current a")).toBe(it221);

instance.clear_items();
instance.mark_items("https://patternslib.com/path2/path2.2/path2.2.2");

expect(document.querySelectorAll(".current").length).toBe(1);
expect(document.querySelectorAll(".inPath").length).toBe(2);
expect(document.querySelector(".current a")).toBe(it222);

instance.clear_items();
instance.mark_items("https://patternslib.com/path3");

expect(document.querySelectorAll(".current").length).toBe(1);
expect(document.querySelectorAll(".inPath").length).toBe(0);
expect(document.querySelector(".current a")).toBe(it3);

instance.clear_items();
instance.mark_items("https://patternslib.com/path4");

expect(document.querySelectorAll(".current").length).toBe(1);
expect(document.querySelectorAll(".inPath").length).toBe(0);
expect(document.querySelector(".current a")).toBe(it4);
});
});

0 comments on commit 5d46d28

Please sign in to comment.