/*
# cpanel - base/frontend/jupiter/_assets/breadcrumb.js
Copyright 2022 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/**
* @example
* Dispatch an event named "breadcrumbSetCrumbs" with the payload
*
* dispatchEvent(new CustomEvent('breadcrumbSetCrumbs', {
* bubbles: true,
* detail: {
* separator: ">",
* crumbs: [
* {
* displayName: "",
* longName: "",
* link: ""
* }
* ]
* }
* })
*
* Listen for event "breadcrumbNavigate" to pick up on user clicks of a breadcrumb element. Payload:
* {
* link: ""
* }
*/
(function() {
"use strict";
/**
* Breadcrumb class that handles all of logic for creating a breadcrumb DOM element
*/
function Breadcrumb() {
this.crumbs = [];
this.crumbId = -1;
}
Breadcrumb.prototype = {
/**
* Adds an id to the array of breadcrumbs that was in the action
* @param {Object[]} arrOfCrumbs Array of breadcrumbs to have an id added
*/
setBreadcrumbId: function(arrOfCrumbs) {
this.crumbs = [];
this.crumbs = arrOfCrumbs.map(function(crumb, i) {
this.crumbId++;
return {
id: "breadcrumbItem_" + i,
displayName: crumb.displayName,
longName: crumb.longName,
link: crumb.link,
};
}, this);
},
/**
* Creates the breadcrumb DOM fragment.
* @param {Object[]} arrOfCrumbs Array of breadcrumbs to have an id added
* @param {string} separator A string of characters to be used for separating each breadcrumb
* @throws Will throw an error when the container element is not present.
*/
buildBreadcrumbFragment: function(arrOfCrumbs, separator) {
var breadcrumbContainer = document.getElementById("cpanel-breadcrumbs");
if (!breadcrumbContainer) {
throw "Missing parent container for breadcrumbs";
}
separator = separator || "/";
this.setBreadcrumbId(arrOfCrumbs);
var breadcrumbDOMFragment = document.createDocumentFragment();
breadcrumbContainer.appendChild(breadcrumbDOMFragment);
for (var i = 0; i < this.crumbs.length; i++) {
var crumbsLength = this.crumbs.length;
var crumb = this.crumbs[i];
var element = this.createAnElement(crumb, i, crumbsLength);
var separatorElement = this.createSeparatorElement(separator);
breadcrumbDOMFragment.appendChild(element);
// Check that there are more than 1 breadcrumb and it's not on the last element before adding a separator element
if (
crumbsLength > 1 && i < crumbsLength - 1
) {
breadcrumbDOMFragment.appendChild(separatorElement);
}
}
breadcrumbContainer.textContent = "";
breadcrumbContainer.appendChild(breadcrumbDOMFragment);
},
/**
* The logic for deciding what type of element to create for the breadcrumb fragment.
* @param {Object} crumb A specific breadcrumb JS object
* @param {number} position The position of the breadcrumb JS object in the breadcrumb array
* @param {number} length The length of the breadcrumb array
*/
createAnElement: function(crumb, position, length) {
// One item make it only a span with no link
if (length === 1) {
return this.createParentSpanElement(crumb);
}
// Make an anchor element that shows on mobile for the 2nd to last element in list
if (position === (length - 2)) {
return this.createParentAnchorElement(crumb);
}
// Make the last element a span without an anchor tag. Different from above because it hides on mobile
if (position === (length - 1)) {
return this.createLastSpanElement(crumb);
}
return this.createMiddleAnchorElement(crumb);
},
/**
* Creates the seperator element for the breadcrumb fragment being created.
* @param {string} separatorString String of characters used to separate each breadcrumb element.
*/
createSeparatorElement: function(separatorString) {
var separatorElement = document.createElement("span");
separatorElement.setAttribute("class", "hidden-xs breadcrumb-separator");
separatorElement.textContent = separatorString;
return separatorElement;
},
/**
* Creates a span element without an anchor tag.
* @param {Object} crumb A specific breadcrumb JS object
*/
createParentSpanElement: function(crumb) {
var span = document.createElement("span");
span.setAttribute("id", crumb.id);
span.setAttribute("aria-current", "page");
span.textContent = crumb.displayName;
return span;
},
/**
* Creates a span element without an anchor tag that is hidden on a mobile view.
* @param {Object} crumb A specific breadcrumb JS object
*/
createLastSpanElement: function(crumb) {
var element = this.createParentSpanElement(crumb);
element.setAttribute("class", "hidden-xs");
return element;
},
/**
* Creates an anchor element that does not navigate and dispatches an event when clicked.
* @param {Object} crumb A specific breadcrumb JS object
*/
createParentAnchorElement: function(crumb) {
var anchor = document.createElement("a");
anchor.textContent = crumb.displayName;
// This stops the anchor tag from doing anything and allows angular to handle routing
anchor.setAttribute("href", "javascript:;");
anchor.setAttribute("id", crumb.id);
anchor.addEventListener("click", function(e) {
anchor.dispatchEvent(new CustomEvent("breadcrumbNavigate", {
bubbles: true,
detail: {
link: crumb.link,
},
}));
});
return anchor;
},
/**
* Creates an anchor element that also hides on mobile.
* @param {Object} crumb A specific breadcrumb JS object
*/
createMiddleAnchorElement: function(crumb) {
var element = this.createParentAnchorElement(crumb);
element.setAttribute("class", "hidden-xs");
return element;
},
};
var breadcrumb = new Breadcrumb();
/**
* Adds event listener to global window for setting breadcrumbs
*/
document.addEventListener("breadcrumbSetCrumbs", function(e) {
breadcrumb.buildBreadcrumbFragment(e.detail.crumbs, e.detail.separator);
});
})();