From a04c110698b50aaa22386250d8630f27e3315848 Mon Sep 17 00:00:00 2001 From: Johannes Raggam Date: Thu, 8 Sep 2022 16:10:30 +0200 Subject: [PATCH 1/3] maint(pat navigationmarker): Modernize code. --- src/pat/navigationmarker/navigationmarker.js | 24 +++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/pat/navigationmarker/navigationmarker.js b/src/pat/navigationmarker/navigationmarker.js index effd3be06..d9d5c32d1 100644 --- a/src/pat/navigationmarker/navigationmarker.js +++ b/src/pat/navigationmarker/navigationmarker.js @@ -5,29 +5,31 @@ export default Base.extend({ name: "navigationmarker", trigger: ".pat-navigationmarker", parser: "mockup", - init: function () { + init() { const portal_url = document.body.dataset.portalUrl; - var href = + const href = document.querySelector('head link[rel="canonical"]').href || window.location.href; + const hrefParts = href.split("/"); - $("a", this.$el).each(function () { - var navlink = this.href.replace("/view", ""); + 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) { - var parent = $(this).parent(); + const parent = $(nav_item).parent(); // check the input-openers within the path - var check = parent.find("> input"); + 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 - var hrefParts = href.split("/"); - var navParts = navlink.split("/"); - var inPath = false; - for (var i = 0, size = navParts.length; i < size; i++) { + 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]) { @@ -47,6 +49,6 @@ export default Base.extend({ parent.addClass("current"); } } - }); + } }, }); From 5d46d2854dbb0cc9d89f1ebfdb6e1955f4f1830a Mon Sep 17 00:00:00 2001 From: Johannes Raggam Date: Thu, 8 Sep 2022 18:09:46 +0200 Subject: [PATCH 2/3] maint(pat navigationmarker): Simplify and improve the implementation. --- src/pat/navigationmarker/navigationmarker.js | 111 +++++++++----- .../navigationmarker/navigationmarker.test.js | 139 ++++++++++++++++++ 2 files changed, 211 insertions(+), 39 deletions(-) create mode 100644 src/pat/navigationmarker/navigationmarker.test.js diff --git a/src/pat/navigationmarker/navigationmarker.js b/src/pat/navigationmarker/navigationmarker.js index d9d5c32d1..604811148 100644 --- a/src/pat/navigationmarker/navigationmarker.js +++ b/src/pat/navigationmarker/navigationmarker.js @@ -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 + ); + }, }); diff --git a/src/pat/navigationmarker/navigationmarker.test.js b/src/pat/navigationmarker/navigationmarker.test.js new file mode 100644 index 000000000..2e4c5581d --- /dev/null +++ b/src/pat/navigationmarker/navigationmarker.test.js @@ -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 = ` + + `; + + 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); + }); +}); From ef0bfb113a1626939def7b8efab1026e16aeaa2b Mon Sep 17 00:00:00 2001 From: Johannes Raggam Date: Tue, 13 Sep 2022 09:38:10 +0200 Subject: [PATCH 3/3] feat(pat navigationmarker): Also mark the anchor with current and in-path classes. Previously only the wrapper was marked. --- src/pat/navigationmarker/navigationmarker.js | 2 ++ .../navigationmarker/navigationmarker.test.js | 26 +++++++++---------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/pat/navigationmarker/navigationmarker.js b/src/pat/navigationmarker/navigationmarker.js index 604811148..3ffd13e9a 100644 --- a/src/pat/navigationmarker/navigationmarker.js +++ b/src/pat/navigationmarker/navigationmarker.js @@ -36,6 +36,7 @@ export default Base.extend({ : 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 @@ -45,6 +46,7 @@ export default Base.extend({ // 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. diff --git a/src/pat/navigationmarker/navigationmarker.test.js b/src/pat/navigationmarker/navigationmarker.test.js index 2e4c5581d..91435da89 100644 --- a/src/pat/navigationmarker/navigationmarker.test.js +++ b/src/pat/navigationmarker/navigationmarker.test.js @@ -76,63 +76,63 @@ describe("pat-navigationmarker", () => { 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(".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(1); + 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(1); + 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(1); - expect(document.querySelectorAll(".inPath").length).toBe(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(1); - expect(document.querySelectorAll(".inPath").length).toBe(1); + 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(1); - expect(document.querySelectorAll(".inPath").length).toBe(2); + 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(1); - expect(document.querySelectorAll(".inPath").length).toBe(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(1); + 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(1); + expect(document.querySelectorAll(".current").length).toBe(2); expect(document.querySelectorAll(".inPath").length).toBe(0); expect(document.querySelector(".current a")).toBe(it4); });