diff --git a/src/ims/application/_main.py b/src/ims/application/_main.py index 521e6da1f..4725ac866 100644 --- a/src/ims/application/_main.py +++ b/src/ims/application/_main.py @@ -29,7 +29,8 @@ import ims.element from ims.config import Configuration, URLs from ims.dms import DutyManagementSystem -from ims.ext.klein import KleinRenderable +from ims.ext.json import jsonTextFromObject +from ims.ext.klein import ContentType, HeaderName, KleinRenderable, static from ._api import APIApplication from ._auth import AuthApplication @@ -118,7 +119,8 @@ def __del__(self) -> None: # @router.route(URLs.root, methods=("HEAD", "GET")) - def rootResource(self, request: IRequest) -> KleinRenderable: + @static + def rootEndpoint(self, request: IRequest) -> KleinRenderable: """ Server root page. @@ -128,15 +130,42 @@ def rootResource(self, request: IRequest) -> KleinRenderable: @router.route(URLs.static, branch=True) - def static(self, request: IRequest) -> KleinRenderable: + @static + def staticEndpoint(self, request: IRequest) -> KleinRenderable: return File(resourcesDirectory.path) + # + # URLs + # + + @router.route(URLs.urlsJS, methods=("HEAD", "GET")) + @static + def urlsEndpoint(self, request: IRequest) -> KleinRenderable: + """ + JavaScript variables for service URLs. + """ + urls = { + k: getattr(URLs, k).asText() for k in URLs.__dict__ + if not k.startswith("_") + } + + request.setHeader( + HeaderName.contentType.value, ContentType.javascript.value + ) + + return "\n".join(( + "var url_{} = {};".format(k, jsonTextFromObject(v)) + for k, v in urls.items() + )) + + # # Child application endpoints # @router.route(URLs.api, branch=True) + @static def apiApplicationEndpoint(self, request: IRequest) -> KleinRenderable: """ API application resource. @@ -145,6 +174,7 @@ def apiApplicationEndpoint(self, request: IRequest) -> KleinRenderable: @router.route(URLs.auth, branch=True) + @static def authApplicationEndpoint(self, request: IRequest) -> KleinRenderable: """ Auth application resource. @@ -153,6 +183,7 @@ def authApplicationEndpoint(self, request: IRequest) -> KleinRenderable: @router.route(URLs.external, branch=True) + @static def externalApplicationEndpoint( self, request: IRequest ) -> KleinRenderable: @@ -163,6 +194,7 @@ def externalApplicationEndpoint( @router.route(URLs.app, branch=True) + @static def webApplicationEndpoint(self, request: IRequest) -> KleinRenderable: """ Web application resource. diff --git a/src/ims/config/_urls.py b/src/ims/config/_urls.py index 30470a20d..7b78c3d8e 100644 --- a/src/ims/config/_urls.py +++ b/src/ims/config/_urls.py @@ -38,6 +38,7 @@ class URLs(object): root = URL.fromText("/") prefix = root.child("ims").child("") + urlsJS = prefix.child("urls.js") # Static resources static = prefix.child("static") diff --git a/src/ims/element/_page.py b/src/ims/element/_page.py index 7a9cf61a1..ea746093c 100644 --- a/src/ims/element/_page.py +++ b/src/ims/element/_page.py @@ -18,6 +18,11 @@ Element base classes. """ +from collections import OrderedDict +from typing import Iterable, MutableMapping + +from hyperlink import URL + from twisted.web.iweb import IRequest from twisted.web.template import Tag, renderer, tags @@ -44,6 +49,46 @@ def __init__(self, config: Configuration, title: str) -> None: self.titleText = title + def urlsFromImportSpec(self, spec: str) -> Iterable[URL]: + """ + Given a string specifying desired imports, return the corresponding + URLs. + """ + urls = self.config.urls + + result: MutableMapping[str, URL] = OrderedDict() + + def add(name: str) -> None: + if not name or name in result: + return + + if name == "bootstrap": + add("jquery") + + if name == "ims": + add("jquery") + add("moment") + + try: + result[name] = getattr(urls, "{}JS".format(name)) + except AttributeError: + raise ValueError( + "Invalid import {!r} in spec {!r}".format(name, spec) + ) + + if name == "dataTables": + add("dataTablesBootstrap") + + # All pages use Bootstrap + add("bootstrap") + add("urls") + + for name in spec.split(","): + add(name.strip()) + + return result.values() + + @renderer def title( self, request: IRequest, tag: Tag = tags.title @@ -62,14 +107,24 @@ def title( @renderer def head(self, request: IRequest, tag: Tag = tags.head) -> KleinRenderable: """ - element. + `` element. """ urls = self.config.urls children = tag.children tag.children = [] + imports = ( + tags.script(src=url.asText()) + for url in + self.urlsFromImportSpec(tag.attributes.get("imports", "")) + ) + + if "imports" in tag.attributes: + del tag.attributes["imports"] + return tag( + # Resource metadata tags.meta(charset="utf-8"), tags.meta( name="viewport", content="width=device-width, initial-scale=1" @@ -86,9 +141,10 @@ def head(self, request: IRequest, tag: Tag = tags.head) -> KleinRenderable: type="text/css", rel="stylesheet", media="screen", href=urls.styleSheet.asText(), ), - tags.script(src=urls.jqueryJS.asText()), - tags.script(src=urls.bootstrapJS.asText()), self.title(request), + # JavaScript resource imports + imports, + # Child elements children, ) @@ -127,7 +183,7 @@ def bottom( @renderer def nav(self, request: IRequest, tag: Tag = tags.nav) -> KleinRenderable: """ -