Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework navigationmarker #1217

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 80 additions & 43 deletions src/pat/navigationmarker/navigationmarker.js
Original file line number Diff line number Diff line change
@@ -1,52 +1,89 @@
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: function () {
const portal_url = document.body.dataset.portalUrl;
var href =
document.querySelector('head link[rel="canonical"]').href ||
window.location.href;

$("a", this.$el).each(function () {
var navlink = this.href.replace("/view", "");
if (href.indexOf(navlink) !== -1) {
var parent = $(this).parent();

// check the input-openers within the path
var 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
var hrefParts = href.split("/");
var navParts = navlink.split("/");
var inPath = false;
for (var 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");
}
init() {
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) {
// 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) {
nav_item.classList.add(this.options.currentClass);
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
) {
nav_item.classList.add(this.options.inPathClass);
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(2);
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(2);
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(2);
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(2);
expect(document.querySelectorAll(".inPath").length).toBe(2);
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(2);
expect(document.querySelectorAll(".inPath").length).toBe(2);
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(2);
expect(document.querySelectorAll(".inPath").length).toBe(4);
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(2);
expect(document.querySelectorAll(".inPath").length).toBe(4);
expect(document.querySelector(".current a")).toBe(it222);

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

expect(document.querySelectorAll(".current").length).toBe(2);
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(2);
expect(document.querySelectorAll(".inPath").length).toBe(0);
expect(document.querySelector(".current a")).toBe(it4);
});
});