@ribajs/shopify
Version:
Shopify extension for Riba.js
258 lines (228 loc) • 6.75 kB
text/typescript
import { Component, ScopeBase } from "@ribajs/core";
import { EventDispatcher } from "@ribajs/events";
import { hasChildNodesTrim } from "@ribajs/utils/src/dom.js";
import { Linklist, LinklistLink } from "../../interfaces/index.js";
export interface Scope extends ScopeBase {
// Properties
/** The linklist as a json string */
linklist?: Linklist;
/** Sets the linklist by his name */
handle?: string;
/** If the navigation should be displayed as pills */
pills: boolean;
/** If the navigation should be displayed as vertically */
vertical: boolean;
/** Set this option to true if toggleable links should be automatically collapse when a page changes */
collapseOnNewPage: boolean;
/** Set this option to true if toggleable links should be automatically open when a child link is active */
showOnActiveChild: boolean;
// Methods
toggle: ShopifyLinklistComponent["toggle"];
collapse: ShopifyLinklistComponent["collapse"];
collapseAll: ShopifyLinklistComponent["collapseAll"];
show: ShopifyLinklistComponent["show"];
showAll: ShopifyLinklistComponent["showAll"];
}
export interface State {
url: string;
namespace?: string;
}
/**
* shopify-filter
*/
export class ShopifyLinklistComponent extends Component {
public static tagName = "shopify-linklist";
protected mainDispatcher = new EventDispatcher("main");
protected autobind = true;
static get observedAttributes(): string[] {
return [
"linklist",
"handle",
"name",
"pills",
"vertical",
"collapse-on-new-page",
"show-on-child-url",
];
}
public scope: Scope = {
// properties
pills: false,
vertical: false,
collapseOnNewPage: true,
showOnActiveChild: true,
// methods
toggle: this.toggle,
collapse: this.collapse,
collapseAll: this.collapseAll,
show: this.show,
showAll: this.showAll,
};
constructor() {
super();
this.mainDispatcher.on("newPageReady", this.onNewPageReady, this);
}
protected connectedCallback() {
super.connectedCallback();
this.init(ShopifyLinklistComponent.observedAttributes);
}
protected disconnectedCallback() {
super.disconnectedCallback();
this.mainDispatcher.off("newPageReady", this.onNewPageReady, this);
}
public toggle(link: LinklistLink) {
link.collapsed = !link.collapsed;
}
public collapse(link: LinklistLink) {
link.collapsed = true;
}
public show(link: LinklistLink) {
link.collapsed = false;
}
public showAll() {
if (this.scope.linklist) {
this.showAllByLinks(this.scope.linklist.links);
}
}
public collapseAll() {
if (this.scope.linklist && this.scope.linklist.links) {
this.collapseAllByLinks(this.scope.linklist.links);
}
}
/**
* Show (uncollapse) link by child url
*/
public showByChildUrl(url: string) {
if (this.scope.linklist && this.scope.linklist.links) {
for (const link of this.scope.linklist.links) {
for (const sublink of link.links) {
if (sublink.url === url) {
this.show(link);
break;
}
for (const subsublink of sublink.links) {
if (subsublink.url === url) {
this.show(link);
this.show(sublink);
break;
}
}
}
}
}
}
protected async attributeChangedCallback(
name: string,
oldValue: any,
newValue: any,
namespace: string | null,
) {
// injects the changed attributes to scope
super.attributeChangedCallback(name, oldValue, newValue, namespace);
// set linklist by handle
if (name === "handle" || name === "name") {
if (
(window as any).model &&
(window as any).model.system &&
(window as any).model.system.linklists &&
(window as any).model.system.linklists[newValue]
) {
this.scope.linklist = (window as any).model.system.linklists[newValue];
} else {
throw new Error(
`Linklist not found! \nNote: The linklist must be available under "window.model.system.linklists['${newValue}']" to set it using his handle.`,
);
}
}
if (name === "linklist") {
if (typeof newValue === "object") {
// if object is in form of "main-menu": {...}
if (Object.keys(newValue).length === 1) {
newValue = newValue[Object.keys(newValue)[0]];
}
}
this.scope.linklist = newValue;
}
}
protected collapseAllByLinks(links: LinklistLink[]) {
if (this.scope.linklist && this.scope.linklist.links) {
for (const link of links) {
if (link.collapseable) {
link.collapsed = true;
}
if (link.links) {
this.collapseAllByLinks(link.links);
}
}
}
}
protected showAllByLinks(links: LinklistLink[]) {
if (this.scope.linklist && this.scope.linklist.links) {
for (const link of links) {
if (link.collapseable) {
link.collapsed = false;
}
if (link.links) {
this.collapseAllByLinks(link.links);
}
}
}
}
protected onNewPageReady(
viewId: string,
currentStatus: State /*, prevStatus: State, container: HTMLElement, newPageRawHTML: string, dataset: any, isFirstPageLoad: boolean */,
) {
const url = currentStatus.url;
if (this.scope.collapseOnNewPage) {
this.collapseAll();
}
if (this.scope.showOnActiveChild) {
this.showByChildUrl(url);
}
}
protected async beforeBind(): Promise<any> {
await super.beforeBind();
if (this.scope.linklist && this.scope.linklist.links) {
this.transformLinks(this.scope.linklist.links);
if (this.scope.collapseOnNewPage) {
this.collapseAll();
}
}
if (this.scope.showOnActiveChild) {
this.showByChildUrl(window.location.pathname);
}
}
/**
* Checks if link are collapseable and set initializes corresponding attributes
*/
protected transformLinks(links: LinklistLink[]) {
for (const link of links) {
if (link.url === "#collapse") {
link.collapseable = true;
link.collapsed = true;
} else {
link.collapseable = false;
link.collapsed = false;
}
if (link.links) {
this.transformLinks(link.links);
}
}
}
protected requiredAttributes(): string[] {
return ["linklist"];
}
/**
* Only set the component template if there no childs already
*/
protected async template() {
if (hasChildNodesTrim(this)) {
return null;
} else {
const { default: template } = await import(
"./linklist.component.html?raw"
);
return template;
}
}
}