diff options
Diffstat (limited to 'web/src/jsx.tsx')
| -rw-r--r-- | web/src/jsx.tsx | 267 |
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; +} |
