"""Rendering helpers."""
# Websauna
from websauna.utils.orderedset import OrderedSet
[docs]class OnDemandResourceRenderer:
"""A JS and CSS resource registry which helps to allow widgets and such add JS and CSS files dynamically on the page.
Usually instance of this class is available as ``request.on_demand_resource_renderer``.
The best practice is to place all JavaScript files before the closing ``</body>`` tag, so that they do not block the the page rendering and decrease the page response time. However, out there exist a lot of badly written JavaScript code which does not honour this - they have ``<script>`` tags in the middle of ``<body>`` and even worse assume you have loaded dependencies, like jQuery, in the ``<head>``. Namely, Deform 2 form library does this.
``BodyRelocatableResourceRegistry`` contains the logic to determine if ``<script>`` tags should go to the end of the page (preferred) or to ``<head>`` (in case some javascript makes assumptions of this). If any widget requires resources assume JS must go to head.
See also :py:attr`deform.widget.default_resources` which includes the list of default resources activated for any form on the page.
More information
* https://developers.google.com/speed/docs/insights/BlockingJS
* https://developers.google.com/web/fundamentals/performance/critical-rendering-path/adding-interactivity-with-javascript#parser-blocking-vs-asynchronous-javascript
"""
def __init__(self):
self.resources = {
"js": OrderedSet(),
"css": OrderedSet(),
}
self.js_requires_head = False
[docs] def request_resource(self, kind: str, resource_url: str, js_requires_head: bool = False):
"""A widget or something wants to place a CSS or JS file on the page rendering.
:param kind: "js" or "css"
:param resource_path: Resolved full URL to this resource
:param js_requires_head: Move all JavaScript to <head> instead of </body> end. I.e. you have <script> tags in the middle of HTML.
"""
self.resources[kind].add(resource_url)
# If one JS wants to go head then everybody goes
self.js_requires_head |= js_requires_head
[docs] def is_js_in_head(self, request) -> bool:
return self.js_requires_head
[docs] def get_resources(self, kind) -> OrderedSet:
"""Get list of resource URLs to render.
:param kind: "js" or "css"
"""
return self.resources[kind]
[docs]def get_on_demand_resource_renderer(request):
"""Reify method for configuration."""
return OnDemandResourceRenderer()