|
| 1 | +# Examples Template |
| 2 | + |
| 3 | +Having a template is pretty slick. |
| 4 | +We'll now do the same for each example, but things are gonna get kinda weird: we need just part of the example's HTML. |
| 5 | + |
| 6 | +## Example Template and Route |
| 7 | + |
| 8 | +We'll start with a test. |
| 9 | +We already have a `TestClient` test at `test_hello_world.test_hello_world`. |
| 10 | +We start by adapting it to the same BeautifulSoup approach we just saw. |
| 11 | + |
| 12 | +Next, an implementation. |
| 13 | +We add a template at `templates/example.jinja2` then make a new `example` view and route in `app.py`. |
| 14 | +By copying the existing view, we get something that works and, with a small test change, passes the tests. |
| 15 | +But it's returning the contents of the home page. |
| 16 | + |
| 17 | +Instead, we: |
| 18 | +- Get the route parameter |
| 19 | +- Read that file |
| 20 | +- Use `beautifulsoup4` to extra the contents of `<main>` |
| 21 | +- Shove that into the template as the context value of `main` |
| 22 | + |
| 23 | +Along the way, we also extract this example's title from the `<title>` in the HTML file. |
| 24 | +We then shove it in as the template context value of `title`. |
| 25 | + |
| 26 | +This leaves out: |
| 27 | +- Everything in the `<head>`, such as...loading PyScript |
| 28 | +- All the `<py-*>` nodes elsewhere in the example's `<body>` |
| 29 | + |
| 30 | +Uhhh...that's kind of dumb. |
| 31 | +Why are we doing that? |
| 32 | + |
| 33 | +## Standalone vs. Integrated vs. Unmanaged |
| 34 | + |
| 35 | +The HTML for an example might appear in a bunch of places: |
| 36 | + |
| 37 | +1. *Standalone*. |
| 38 | +People want to cut-and-paste an example and run it from a `file:///` URL. |
| 39 | +The Contributor might want to start this way. It needs the PyScript JS/CSS and possibly a `<py-config>`. |
| 40 | +2. *Integrated Website*. |
| 41 | +In the website, for the "best" examples, we want everything to fit together well: consistent styling, fast page transitions, using the same PyScript/Pyodide. |
| 42 | +The Gallery should have control of these things, not the examples. |
| 43 | +Let's call those the "integrated" examples, vs. others that need their own control. |
| 44 | +3. *Unmanaged Website*. |
| 45 | +These are examples on the website which need to set their own Pyodide, or not use the Gallery CSS. |
| 46 | +4. *Integrated App*. |
| 47 | +These are when the examples are running in the Gallery Python web app, under Starlette. |
| 48 | +Perhaps the Contributor is browsing the example, perhaps a Coder is running the example via `pipx`. |
| 49 | +Mostly the same as "Integrated Website". |
| 50 | +5. *CI Builds Website*. |
| 51 | +In this case, the example is compiled into a `public` directory and included into the website. |
| 52 | +The example isn't really being executed. |
| 53 | +Rather, it's being assembled into output. |
| 54 | + |
| 55 | +At this point, we're still in "Integrated App". |
| 56 | +The Starlette process wants an "integrated" example, where the CSS/JS/Pyodide is under the `layout.jinja2` control. |
| 57 | +All the "integrated" examples will look and feel consistent. |
| 58 | + |
| 59 | +## Extra PyScript Stuff in Head |
| 60 | + |
| 61 | +With that said, an "integrated" examples might have other static assets to go in the `<head>`: extra CSS, for example. |
| 62 | +We'll add that to our example. |
| 63 | + |
| 64 | +Remember, these examples are "standalone". |
| 65 | +They include the `<link>` and `<script>` pointing to PyScript. |
| 66 | +We don't want *those* -- they come from `layout.jinja2`. |
| 67 | +We *do* want anything else them put in there, with relative links as the targets. |
| 68 | + |
| 69 | +Let's write a failing first for including `hello_world.css`. |
| 70 | +For implementation: |
| 71 | +- Add a slot in `layout.jinja2` |
| 72 | +- Change `example.jinja2` to fill that slot, based on passed in string |
| 73 | +- Pass in a string of all the HTML to include |
| 74 | +- Build that string from a `beautifulsoup` `select` |
| 75 | + |
| 76 | +## Example Template Needs `<py-config>` |
| 77 | + |
| 78 | +We want the HTML for the examples to get a Gallery-managed `<py-config>`. |
| 79 | +But we don't want this in other, non-example pages. |
| 80 | +We'll add an `extra_body` slot in `layout.jinja2`, then fill it from `example.jinja2`. |
| 81 | + |
| 82 | +Starting, of course, with a test. |
| 83 | + |
| 84 | +## Plucking Example Parts |
| 85 | + |
| 86 | +That's good for stuff in the `<head>`. |
| 87 | +But we have a problem in the `<body>`. |
| 88 | +PyScript only allows `<py-script>` as a direct child of `<body>`, so we can't put it in `<main>`. |
| 89 | +We need a policy like this: |
| 90 | + |
| 91 | +- Anything in the example's "UI" (the DOM) goes in `<main>` and gets copied over |
| 92 | +- Any `<py-*>` nodes directly under `<body>` get copied over |
| 93 | +- *Except* `<py-config>` |
| 94 | +- Everything else in `<body>` is left out |
| 95 | + |
| 96 | +We'll write some tests: |
| 97 | +- Ensure only one `<py-config>` with a runtime pointed to our local Pyodide |
| 98 | +- The `<py-script>` is copied over, in the right spot |
| 99 | +- Some tracer `<h6>` that is *outside* of `<main>` is *not* copied over |
| 100 | + |
| 101 | +## QA |
| 102 | + |
| 103 | +`mypy` gave us some trouble at the end, because `beautifulsoup` has some unusual typing. |
| 104 | +We thus moved the `example` view's soup filtering into a standalone function which had a `cast`. |
| 105 | + |
| 106 | +## Future |
| 107 | + |
| 108 | +This is actually pretty neat. |
| 109 | +But the view is doing too much. |
| 110 | +Later, we'll introduce a "resource" concept, kind of like a model, and move the work there. |
0 commit comments