Form encapsulation at the HTML level

One of the things I love about Anvil is the concept of Forms. Forms constitute clean building blocks for an SPA-oriented architecture; Forms can be placed in other Forms, and at the “Python level” each Form nicely exhibits encapsulation (as they of course are Python classes).

Each Form (except for “blank Forms”) is associated with HTML markup (“custom HTML”, by reference to an HTML file in assets or by passing in an HTML string). This HTML association does not offer the same encapsulation that is seen at the “Python level”. Once a given HTML markup hits the DOM (i.e., when a Form that refers to the HTML is shown at run time), the HTML (including everything inside style and script tags) become part of the same “single document namespace”. This does not have to be a challenge if apps are mostly built with native Anvil components and if the HTML markups are primarily used to outline containers (anvil slots), into which components can be placed.

However, I have recently adopted the practice (at least tentatively) of not using native Anvil components (except for the “high-level abstraction” Plot component) and instead write all visual elements directly in HTML and to use the JavaScript DOM API for manipulation of these elements (and to do “everything from code”, i.e., not using the Anvil Designer). My motivation for doing so is grounded in a desire to take advantage of HTML tags not covered by the native Anvil components, to make CSS styling easier (without the need to decipher the quite nested HTML structure of the native Anvil components, including all their predefined styling), and finally, to tap into the options that the JavaScript DOM API offers – after all this is what JavaScript was designed to do (and seems to be the right tool for that job). At the same time, I still wish to essentially “develop a Python app” with respect to controlling data and navigation. And I wish to take advantage of Anvil’s fantastic services, Anvil’s neat “integration of client-side and server-side code (including DB)” as well as Anvil’s easy “devops”. Anvil is therefore still my absolute preference, also for the front-end. But the lack of encapsulation of Form HTML has been a challenge for me in two major ways. First, regarding styling. Styling everything from one or more global CSS files can certainly have its advantages, but it also calls for careful and sometimes complex management of CSS classes. Sometimes I would prefer to be able to style components just within the Form’s “namespace”. The second related challenge concerns using the JavaScript DOM API. This typically involves heavy use of e.g., querySelector(someSelector) and querySelectorAll(someSelector) [no, I do not wish to use JQuery :slight_smile: ]. If you simply try to select “Form specific HTML elements” based on their classes, you risk selecting elements outside the Form (if other Form exposed in the DOM have elements with the same classes).

Anvil’s anvil.js offers multiple and easy solutions to these challenges. With anvil.js you can essentially write “hybrid JavaScript-Python code”. This is extremely powerful… But it can sometimes also feel a little messy mixing two languages like this. Moreover, another of my recent habits is to write more and more code in VS Code (directly in a cloned Git repo of the Anvil app). When I work in VS Code, I would like to be able to test run my HTML/CSS/JavaScript code without too many changes and without the need to first push the repo and then run it from the Anvil IDE. In other words, I try to keep my Python code as separate as possible from my HTML/CSS/JavaScript code. So how to overcome the aforementioned “encapsulation challenges” with Form-associated HTML (and HTML-embedded styles and scripts)? I have found a way to do this, but I’m far from sure that it’s the most elegant approach – which is part of my motivation for posting this. Anyway, I do the following:

  1. I pass the DOM node of the Form itself to a JavaScript init function in the associated HTML markup (using self.call_js to target a Form-specific function). This essentially overcomes the “JavaScript encapsulation challenge" since element selection can then be done from the the Form’s DOM element.
  2. To allow for “CSS encapsulation”, I attach a shadow DOM to the Form’s DOM element and move all the associated HTML markup into the Form element’s shadow DOM.

This sounds complicated, but it’s only a few lines of code. It’s probably also a little difficult to describe accurately, so I’m providing a link to a demo app below.

https://anvil.works/build#clone:A3NUH6TUGBA2MVM5=2KJFW7DJFOYGLXLU7O6VXU25

I’ve toyed with a few other solutions that also do the job. Perhaps the one that felt most “clean” involved writing a JavaScript class that essentially does the same as the Python Form class does – only at the HTML level. However, I abandoned this solution because it simply required too much code. I’ve also toyed with a variant of this that involved writing native web components (again to mimic the Python Form encapsulation at the HTML-level).

I would greatly appreciate any ideas concerning other ways to achieve the same thing.

The ultimate solution would of course be if Anvil allowed HTML markup to be uniquely associated (“HTML encapsulation”) with a given Form (hence, not requiring the “hacks” I apply). Moreover, it would be great if such a unique\encapsulated Form-HTML markup association could be done with an option to “make Form styling private” (i.e., to implement a shadow DOM for the Form). And finally, I wish that the “Design” and “Code” tabs would have a sibling: “HTML” (without the need to access the HTML via a Designer property or by opening an asset file).

3 Likes