258 lines
5.6 KiB
JavaScript
258 lines
5.6 KiB
JavaScript
/*
|
|
Copyright (c) 2023-forever Douglas Malnati. All rights reserved.
|
|
|
|
See the /faq/tos page for details.
|
|
|
|
(If this generated header is stamped on a file which is a 3rd party file or under a different license or copyright, then ignore this copyright statement and use that file's terms.)
|
|
*/
|
|
|
|
import * as utl from '/js/Utl.js';
|
|
|
|
import { Timeline } from '/js/Timeline.js';
|
|
|
|
|
|
// fold this functionality back into the original Event.js later
|
|
class Event
|
|
{
|
|
static handlerList = [];
|
|
|
|
AddHandler(handler)
|
|
{
|
|
Event.handlerList.push(handler);
|
|
}
|
|
|
|
Emit(evt)
|
|
{
|
|
if (typeof(evt) == "string")
|
|
{
|
|
evt = {
|
|
type: evt
|
|
};
|
|
}
|
|
|
|
for (const evtHandler of Event.handlerList)
|
|
{
|
|
if (evtHandler.OnEvent)
|
|
{
|
|
evtHandler.OnEvent(evt);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
export class Base
|
|
extends Event
|
|
{
|
|
static globalDebugAnyway = false;
|
|
|
|
constructor(t)
|
|
{
|
|
super();
|
|
|
|
this.AddHandler(this);
|
|
|
|
// timeline
|
|
this.t = t ?? new Timeline();
|
|
|
|
|
|
//
|
|
// logging levels
|
|
//
|
|
|
|
// debug
|
|
this.debug = false;
|
|
|
|
// info
|
|
this.info = true;
|
|
}
|
|
|
|
// only to be called once, from main application, when everything is
|
|
// constructed and ready to go
|
|
Run()
|
|
{
|
|
urlStateProxy.OnPageLoad();
|
|
}
|
|
|
|
SetDebug(tf)
|
|
{
|
|
this.debug = tf;
|
|
}
|
|
|
|
SetGlobalDebug(tf)
|
|
{
|
|
Base.globalDebugAnyway = tf;
|
|
}
|
|
|
|
Debug(str)
|
|
{
|
|
if (this.debug || Base.globalDebugAnyway)
|
|
{
|
|
console.log(str);
|
|
}
|
|
}
|
|
|
|
DebugTable(val)
|
|
{
|
|
if (this.debug || Base.globalDebugAnyway)
|
|
{
|
|
console.table(val);
|
|
}
|
|
}
|
|
|
|
SetInfo(tf)
|
|
{
|
|
this.info = tf;
|
|
}
|
|
|
|
Info(str)
|
|
{
|
|
if (this.info)
|
|
{
|
|
console.log(str);
|
|
}
|
|
}
|
|
|
|
Err(from, str)
|
|
{
|
|
console.log(`ERR: ${from} - ${str}`);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// A class that does the work of helping components interact with the URL
|
|
// and the variables captured within the URL.
|
|
//
|
|
// Also manages browser history and gives the typical user experience of
|
|
// how history works, without page reloads.
|
|
class UrlStateProxy
|
|
extends Base
|
|
{
|
|
constructor()
|
|
{
|
|
super();
|
|
|
|
this.urlPrior = "";
|
|
|
|
window.addEventListener("popstate", (event) => {
|
|
this.DoUrlSet();
|
|
});
|
|
}
|
|
|
|
OnPageLoad()
|
|
{
|
|
this.DoUrlSet();
|
|
this.DoUrlGet();
|
|
}
|
|
|
|
DoUrlSet()
|
|
{
|
|
// capture current url and its search params, and parse
|
|
const url = new URL(window.location);
|
|
const params = new URLSearchParams(url.search);
|
|
|
|
// console.log(`window.location: ${window.location}`)
|
|
// console.log(`urlIn.origin: ${url.origin}`)
|
|
// console.log(`urlIn.pathname: ${url.pathname}`)
|
|
// console.log(`urlIn.search: ${url.search}`)
|
|
// console.log(`paramsIn : ${params.toString()}`);
|
|
|
|
// synchronously send to interested listeners
|
|
this.Emit({
|
|
type: "ON_URL_SET",
|
|
|
|
Get: (param, defaultValue) => {
|
|
return utl.GetSearchParam(param, defaultValue);
|
|
},
|
|
});
|
|
}
|
|
|
|
DoUrlGet()
|
|
{
|
|
// synchronously send to interested listeners
|
|
let paramsOut = new URLSearchParams(``);
|
|
let evt = {
|
|
type: "ON_URL_GET",
|
|
|
|
Set: (param, value) => {
|
|
paramsOut.set(param, value);
|
|
},
|
|
|
|
allowBlank: false,
|
|
};
|
|
this.Emit(evt);
|
|
|
|
const urlIn = new URL(window.location);
|
|
let paramsIn = new URLSearchParams(urlIn.search);
|
|
|
|
// filter out blank parameters unless requested not to
|
|
if (evt.allowBlank == false)
|
|
{
|
|
for (let [key, value] of Array.from(paramsOut.entries()))
|
|
{
|
|
if (value === "")
|
|
{
|
|
paramsOut.delete(key);
|
|
}
|
|
}
|
|
}
|
|
|
|
const urlOut = new URL(`${urlIn.origin}${urlIn.pathname}?${paramsOut.toString()}`);
|
|
|
|
// console.log("")
|
|
// console.log("")
|
|
// console.log(`old : ${urlIn.href}`);
|
|
// console.log(`params : ${paramsOut.toString()}`);
|
|
// console.log(`new : ${urlOut.href}`);
|
|
|
|
let paramsInSorted = new URLSearchParams(paramsIn.toString());
|
|
paramsInSorted.sort();
|
|
let pIn = paramsInSorted.toString();
|
|
|
|
let paramsOutSorted = new URLSearchParams(paramsOut.toString());
|
|
paramsOutSorted.sort();
|
|
let pOut = paramsOutSorted.toString();
|
|
|
|
let didNewHistoryEntry = false;
|
|
if (pIn != pOut)
|
|
{
|
|
// console.log("params are different, updating url and history")
|
|
// console.log(pIn)
|
|
// console.log(pOut)
|
|
|
|
history.pushState({}, "", urlOut.href);
|
|
|
|
didNewHistoryEntry = true;
|
|
}
|
|
|
|
return didNewHistoryEntry;
|
|
}
|
|
|
|
OnEvent(evt)
|
|
{
|
|
switch (evt.type) {
|
|
case "REQ_URL_GET": this.OnReqUrlGet(); break;
|
|
}
|
|
}
|
|
|
|
OnReqUrlGet()
|
|
{
|
|
// the purpose of this is to blow away the forward history when (say)
|
|
// a user clicks search, which triggers a URL re-evaluation.
|
|
let didNewHistoryEntry = this.DoUrlGet();
|
|
|
|
if (didNewHistoryEntry == false)
|
|
{
|
|
// force new history, request came in to re-evaluate, clearly
|
|
// a change has occurred.
|
|
history.pushState({}, "", window.location);
|
|
}
|
|
}
|
|
}
|
|
|
|
// global single instance
|
|
let urlStateProxy = new UrlStateProxy();
|
|
|
|
|