gd-bs
Version:
Bootstrap JavaScript, TypeScript and Web Components library.
359 lines (311 loc) • 13.5 kB
text/typescript
import { INavbar, INavbarProps } from "./types";
import { Base } from "../base";
import { ButtonClassNames } from "../button";
import { appendContent } from "../common";
import { NavbarItem } from "./item";
import { HTML } from "./templates";
/**
* Navbar Types
*/
export enum NavbarTypes {
Dark = 1,
Light = 2,
Primary = 3
}
/**
* Navbar
*/
class _Navbar extends Base<INavbarProps> implements INavbar {
private _btnSearch: HTMLElement;
private _items: Array<NavbarItem> = null;
// Constructor
constructor(props: INavbarProps, template: string = HTML, itemTemplate?: string) {
super(template, props);
// Configure the collapse
this.configure(itemTemplate);
// Configure search
this.configureSearch();
// Configure the events
this.configureEvents();
// Configure the parent
this.configureParent();
}
// Configure the card group
private configure(itemTemplate: string) {
// See if we are applying a brand
if (this.props.brand) {
let elBrand = this.el.querySelector("span.navbar-brand") as HTMLSpanElement;
let elBrandLink = this.el.querySelector("a.navbar-brand") as HTMLAnchorElement;
// See if we are using a link
if (this.props.brandUrl || this.props.onClickBrand) {
// Remove the element
elBrand ? elBrand.parentElement.removeChild(elBrand) : null;
// Update the brand
elBrandLink ? elBrandLink.href = this.props.brandUrl : null;
// Append the content
appendContent(elBrandLink, this.props.brand);
} else {
// Remove the link element
elBrandLink ? elBrandLink.parentElement.removeChild(elBrandLink) : null;
// Append the content
appendContent(elBrand, this.props.brand);
}
} else {
// Remove the brand link
let elBrandLink = this.el.querySelector("a.navbar-brand");
if (elBrandLink) { elBrandLink.parentNode.removeChild(elBrandLink); }
// Remove the brand element
let elBrand = this.el.querySelector("span.navbar-brand");
if (elBrand) { elBrand.parentNode.removeChild(elBrand); }
}
// Update the nav bar
let navbar = this.el.querySelector(".navbar-collapse");
if (navbar) {
navbar.id = this.props.id || "navbar_content";
}
// Set the toggle
let toggler = this.el.querySelector(".navbar-toggler");
if (toggler) {
toggler.setAttribute("aria-controls", navbar.id);
toggler.setAttribute("data-bs-target", "#" + navbar.id);
}
// Set the scroll
let nav = this.el.querySelector(".navbar-nav") as HTMLElement;
if (nav && this.props.enableScrolling) {
// Add the class
nav.classList.add("navbar-nav-scroll");
}
// Add the classes based on the type
this._btnSearch = this.el.querySelector("button[type='submit']") as HTMLButtonElement;
// Set the type
this.setType(this.props.type);
// Render the items
this.renderItems(itemTemplate);
}
// Configure the events
private configureEvents() {
let props = this.props.searchBox || {};
// See if the brand element and click event exist
let brand = this.el.querySelector(".navbar-brand") as HTMLAnchorElement;
if (brand && this.props.onClickBrand) {
// Set the click event
brand.addEventListener("click", ev => {
// Call the event
this.props.onClickBrand(brand, ev);
});
}
// See if search events exist
let searchbox = this.el.querySelector("form input") as HTMLInputElement;
if (searchbox) {
// Set a keydown event to catch the "Enter" key being pressed
searchbox.addEventListener("keydown", ev => {
// See if the "Enter" key was pressed
if (ev.key == "Enter") {
// Disable the postback
ev.preventDefault();
// See if there is a search event
if (props.onSearch) {
// Call the event
props.onSearch(searchbox.value, ev);
}
}
});
// See if a change event exists
if (props.onChange) {
// Add an input event
searchbox.addEventListener("input", ev => {
// Call the event
props.onChange(searchbox.value, ev);
});
// Add a clear event
searchbox.addEventListener("clear", ev => {
// Call the event
props.onChange(searchbox.value, ev);
});
// Edge has a bug where the clear event isn't triggered
// See if this is the Edge browser
if (window.navigator.userAgent.indexOf("Edge") > 0) {
// Detect the mouse click event
searchbox.addEventListener("mouseup", () => {
let currentValue = searchbox.value;
// Set a timeout to see if the value is cleared
setTimeout(() => {
// Compare the values
if (currentValue != searchbox.value) {
// Call the event
props.onChange(searchbox.value);
}
}, 1);
});
}
}
}
// See if a search event exists
let button = this.el.querySelector("form button") as HTMLButtonElement;
if (button && props.onSearch) {
// Add a click event
button.addEventListener("click", ev => {
// Prevent the page from moving to the top
ev.preventDefault();
// Call the event
props.onSearch(searchbox.value);
});
}
// See if the toggle exists
let btnToggle = this.el.querySelector(".navbar-toggler") as HTMLButtonElement;
if (btnToggle) {
// Add a click event
btnToggle.addEventListener("click", ev => {
let elNav = this.el.querySelector(".navbar-collapse") as HTMLElement;
// See if it's visible
if (!btnToggle.classList.contains("collapsed") && elNav.classList.contains("show")) {
// Start the animation
elNav.style.height = elNav.getBoundingClientRect()["height"] + "px";
setTimeout(() => {
elNav.classList.add("collapsing");
elNav.classList.remove("collapse");
elNav.classList.remove("show");
elNav.style.height = "";
btnToggle.classList.add("collapsed");
}, 10);
// Wait for the animation to complete
setTimeout(() => {
elNav.classList.remove("collapsing");
elNav.classList.add("collapse");
}, 250);
} else {
// Start the animation
elNav.classList.remove("collapse");
elNav.classList.add("collapsing");
elNav.style.height = this.el.scrollHeight + "px";
btnToggle.classList.remove("collapsed");
// Wait for the animation to complete
setTimeout(() => {
elNav.classList.remove("collapsing");
elNav.classList.add("collapse");
elNav.classList.add("show");
elNav.style.height = "";
}, 250);
}
});
}
// Execute the event(s)
this.props.onRendered ? this.props.onRendered(this.el) : null;
}
// Configures search
private configureSearch() {
// See if we are rendering a search box
let search = this.el.querySelector("form") as HTMLElement;
if (search) {
if (this.props.enableSearch != false && this.props.searchBox) {
let props = this.props.searchBox || {};
// Update the searchbox
let searchbox = search.querySelector("input");
searchbox.placeholder = props.placeholder || searchbox.placeholder;
searchbox.value = props.value || "";
props.btnText ? searchbox.setAttribute("aria-label", props.btnText) : null;
// See if we are rendering a button
let button = search.querySelector("button");
if (props.hideButton == true) {
// Remove the button
search.removeChild(button);
} else {
// Set the button type class name
let className = ButtonClassNames.getByType(props.btnType);
className ? button.classList.add(className) : null;
}
} else {
// Remove the searchbox
search.parentNode.removeChild(search);
}
}
}
// Render the items
private renderItems(itemTemplate: string) {
// Clear the list
this._items = [];
// Create the navbar list
let list = this.el.querySelector("ul.navbar-nav") as HTMLElement;
if (list) {
// Parse the items
let items = this.props.items || [];
for (let i = 0; i < items.length; i++) {
// Create the item
let item = new NavbarItem(items[i], this.props, itemTemplate);
this._items.push(item);
list.appendChild(item.el);
// Call the render events
this.props.onItemRendered ? this.props.onItemRendered(item.el, items[i]) : null;
}
}
// Create the navbar right list
list = this.el.querySelectorAll("ul.navbar-nav")[1] as HTMLElement;
if (list) {
// See if no items exist
let items = this.props.itemsEnd || [];
if (items.length == 0) {
// Remove the element
list.remove();
} else {
// Parse the items
for (let i = 0; i < items.length; i++) {
// Create the item
let item = new NavbarItem(items[i], this.props, itemTemplate);
this._items.push(item);
list.appendChild(item.el);
}
}
}
}
/**
* Public Methods
*/
// Returns the current search value
getSearchValue() {
// Get the searchbox element
let searchbox = this.el.querySelector("form input") as HTMLInputElement;
if (searchbox) {
// Return the value
return searchbox.value;
}
// Return nothing by default
return "";
}
// Updates the navbar template type
setType(navbarType: number) {
// Remove the classes
this.el.classList.remove("navbar-dark");
this.el.classList.remove("navbar-light");
this.el.classList.remove("bg-dark");
this.el.classList.remove("bg-light");
this.el.classList.remove("bg-primary");
this._btnSearch.classList.remove("btn-outline-info");
this._btnSearch.classList.remove("btn-outline-light");
this._btnSearch.classList.remove("btn-outline-primary");
// See which classes to add
switch (navbarType) {
// Dark
case NavbarTypes.Dark:
// Add the class
this.el.classList.add("navbar-dark");
this.el.classList.add("bg-dark");
this._btnSearch.classList.add("btn-outline-info")
break;
// Default - Light
case NavbarTypes.Light:
// Add the class
this.el.classList.add("navbar-light");
this.el.classList.add("bg-light");
this._btnSearch.classList.add("btn-outline-primary")
break;
// Default - Primary
default:
// Add the class
this.el.classList.add("navbar-dark");
this.el.classList.add("bg-primary");
this._btnSearch.classList.add("btn-outline-light")
break;
}
}
}
export const Navbar = (props: INavbarProps, template?: string, itemTemplate?: string): INavbar => { return new _Navbar(props, template, itemTemplate); }