summaryrefslogtreecommitdiff
path: root/web/src/jsx.tsx
diff options
context:
space:
mode:
authornavewindre <boneyaard@gmail.com>2025-11-11 08:11:24 +0100
committernavewindre <boneyaard@gmail.com>2025-11-11 08:11:24 +0100
commitf5e29189f70c5c8532916504a1a22f8c586f6e73 (patch)
tree9bf42144e608260527766e128268b380231ed95b /web/src/jsx.tsx
parent6442494822d12c23cdd609031c4039d3309b64f6 (diff)
new web
Diffstat (limited to 'web/src/jsx.tsx')
-rw-r--r--web/src/jsx.tsx267
1 files changed, 267 insertions, 0 deletions
diff --git a/web/src/jsx.tsx b/web/src/jsx.tsx
new file mode 100644
index 0000000..98ca440
--- /dev/null
+++ b/web/src/jsx.tsx
@@ -0,0 +1,267 @@
+import $ from 'jquery';
+const assetAttributeNames = new Set( ['data', 'srcset', 'src', 'href'] );
+
+export interface Route {
+ path: string,
+ component: Function,
+ wildcard: boolean
+};
+
+const routes: Route[] = [];
+let err404page = "/";
+let rootId = "moneyjsx-root";
+let onprenavigate: Function = () => {};
+let onpostnavigate: Function = () => {};
+
+function routeForPath( route: string ) : Function | null {
+ if( !routes[route] ) {
+ for( let key of Object.keys( routes ) ) {
+ const r = routes[key];
+
+ if( r.wildcard ) {
+ if( route.slice( 0, r.path.length ) == r.path ) {
+ return r.component;
+ }
+ }
+ }
+
+ return null;
+ } else return routes[route].component;
+}
+
+/**
+ * sets the id of the element to be replaced by the navigator
+ **/
+export function setRootId( rootId: string ) {
+ rootId = rootId;
+}
+
+/**
+ * adds a route component to the routes list
+ * the component function must return either a jquery or a DOM element
+ **/
+export function addRoute( name: string, component: Function ) {
+ let path = name;
+ let wildcard = false;
+ if( path[path.length - 1] === "*" ) {
+ console.log( "name" );
+ path = path.substring( 0, path.length - 1 );
+ wildcard = true;
+ }
+
+ routes[path] = { path, component, wildcard };
+}
+
+/**
+ * sets the route for a 404 page
+ **/
+export function set404Route( name: string ) {
+ err404page = name;
+}
+
+/**
+ * sets the callback that will get called when a route changes
+ **/
+export function onPreNavigate( callback: Function ) {
+ onprenavigate = callback;
+}
+
+/**
+ * sets the callback that will get called when a route changes
+ **/
+export function onPostNavigate( callback: Function ) {
+ onpostnavigate = callback;
+}
+
+/**
+ * replaces the root element with the route component
+ **/
+export function navigate( route: string ) {
+ console.log( route );
+ let url = new URL( window.location.href );
+ let cb = routeForPath( route );
+ url.pathname = route;
+ if( !cb )
+ return navigate( err404page );
+
+ window.history.pushState( {}, null, url.href );
+
+ onprenavigate();
+ const el = $( cb() );
+
+ $( `#${rootId}` ).children().remove();
+ $( `#${rootId}` ).append( el );
+ onpostnavigate();
+}
+
+/**
+ * navigate with params. see: navigate
+ **/
+export function navigateParams( route: string, params: any ) {
+ let url = new URL( window.location.href );
+ let uparams = new URLSearchParams( params );
+ url.pathname = route;
+ url.search = uparams.toString();
+ let cb = routeForPath( route );
+ if( !cb )
+ return navigate( err404page );
+
+ window.history.pushState( {}, null, url.href );
+
+ onprenavigate();
+ const el = $( cb() );
+
+ $( `#${rootId}` ).children().remove();
+ $( `#${rootId}` ).append( el );
+ onpostnavigate();
+}
+
+/**
+ * wrapper for history.pushState
+ **/
+export function pushParams( params: any ) {
+ const url = new URL( window.location.href );
+ url.search = new URLSearchParams( params ).toString();
+
+ window.history.pushState( {}, null, url.href );
+}
+
+/**
+ * navigates without adding a history entry
+ * useful for e.g. re-rendering a page after waiting for a data callback
+**/
+export function navigateParamsSilent( route: string, params: any ) {
+ let url = new URL( window.location.href );
+ let uparams = new URLSearchParams( params );
+ url.pathname = route;
+ url.search = uparams.toString();
+ let cb = routeForPath( route );
+ if( !cb )
+ return navigateSilent( err404page );
+
+ onprenavigate();
+ const el = $( cb() );
+
+ $( `#${rootId}` ).children().remove();
+ $( `#${rootId}` ).append( el );
+ onpostnavigate();
+}
+
+/**
+ * see: navigateParamsSilent
+ **/
+export function navigateSilent( route: string ) {
+ let url = new URL( window.location.href );
+ url.pathname = route;
+ let cb = routeForPath( route );
+ if( !cb )
+ return navigateSilent( err404page );
+
+ onprenavigate();
+ const el = $( cb() );
+
+ $( `#${rootId}` ).children().remove();
+ $( `#${rootId}` ).append( el );
+ onpostnavigate();
+}
+
+/**
+ * action when the back button is pressed
+**/
+export function onPopState() {
+ let url = new URL( window.location.href );
+ let uparams = new URLSearchParams( url.searchParams );
+ url.search = uparams.toString();
+ let cb = routeForPath( url.pathname );
+ if( !cb )
+ return navigateSilent( err404page );
+
+ onprenavigate();
+ const el = $( cb() );
+
+ $( `#${rootId}` ).children().remove();
+ $( `#${rootId}` ).append( el );
+ onpostnavigate();
+}
+
+export function getRoutes() : Route[] {
+ return routes;
+}
+
+
+// internal stuff below
+
+const originalAppendChild = Element.prototype.appendChild;
+Element.prototype.appendChild = function( child: any ) {
+ if( Array.isArray( child ) ) {
+ for( const childArrayMember of child )
+ this.appendChild( childArrayMember );
+
+ return child;
+ }
+ else if( typeof child === 'string' ) {
+ return originalAppendChild.call( this, document.createTextNode( child ) );
+ }
+ else if( child ) {
+ return originalAppendChild.call( this, child );
+ }
+};
+
+export function createElement( tag: any, props: any, ...children: any ) {
+ props = props || {};
+
+ if( typeof tag === "function" ) {
+ props.children = children;
+ return tag( props );
+ }
+
+ if( tag === 'raw-content' ) {
+ const dummy = document.createElement( 'div' );
+ dummy.innerHTML = props.content;
+ return [...dummy.children];
+ }
+
+ const element = document.createElement( tag );
+
+ for( const [name, value] of Object.entries( props ) ) {
+ if( name.startsWith( 'on' ) ) {
+ const lowercaseName = name.toLowerCase();
+
+ if( lowercaseName in window ) {
+ element.addEventListener( lowercaseName.substring(2 ), value );
+ continue;
+ }
+ }
+
+ if( name == 'ref' ) {
+ ( value as any ).current = element;
+ continue;
+ }
+
+ if( value === false )
+ continue;
+
+ if( value === true ) {
+ element.setAttribute( name, '' );
+ continue;
+ }
+
+ if( assetAttributeNames.has( name ) ) {
+ if( typeof value === 'string' ) {
+ element.setAttribute( name, value );
+ }
+ continue;
+ }
+
+ element.setAttribute( name, value );
+ };
+
+ for( const child of children )
+ element.appendChild( child );
+
+ return element;
+}
+
+export function createFragment( props: any ) {
+ return props.children;
+}