layout and raw()
JOPS layouts are plain HTML strings. The layout tag builds them safely from template literals, raw() lets trusted HTML through, and loadLayout() loads them from files.
import { layout, raw } from "jops-core";Learn more: Layouts.
layout
Section titled “layout”layout(strings: TemplateStringsArray, ...values: any[]): stringA tagged template function for HTML. Every interpolated value is HTML-escaped (&, <, > and "), so data from users or the network can’t inject markup. Wrap a value in raw() to insert it unescaped.
Returns the HTML string.
const html = layout`<p>${userInput}</p>`; // escapedconst html = layout`<p>${raw("<b>trusted</b>")}</p>`; // raw HTMLnullandundefinedvalues are left out (empty).- Put interpolated attribute values in double quotes (
title="${value}"): double quotes are escaped, single quotes are not. - Values are converted with
String(), so an array is joined with commas and escaped. To insert rows built withlayout(already escaped), join them and wrap the result inraw():
const rows = quotes.map((q) => layout`<tr><td>${q.number}</td></tr>`).join("");const table = layout`<table><tbody>${raw(rows)}</tbody></table>`;Typical uses: an inline layout in a view’s constructor, and values passed to Element.set().
import { View, layout } from "jops-core";
export default class Greeting extends View { constructor() { super(); this.layout = layout`<div><h1 id="title"></h1></div>`; this.inflate(); } onLayout() { this.get("title").set(layout`Hello, ${this.props.name}!`); }}raw(html: string): RawHTMLWraps a trusted HTML string so that layout inserts it as is. Use it only for HTML you control.
Returns a wrapper object that only has meaning inside a layout template.
layout`<p>${raw("<b>Hello</b>")}</p>`; // "<p><b>Hello</b></p>"Layout sources
Section titled “Layout sources”A view’s layout can come from four places. Paths follow the path rules of your setup.
| Source | Example | Loading |
|---|---|---|
| Inline string | this.layout = layout`…` , new View("<div>…</div>") |
synchronous |
.html file |
<View jops layout="/src/layout/home.html">, loadLayout("/src/layout/home.html") |
fetch(), cached per page |
.js layout module |
loadLayout("/src/layout/home.js") |
dynamic import() |
layout= attribute on a declared tag |
<Card jops src="/src/Card.js" layout="/src/layout/card.html"> |
as the path type |
Inline string
Section titled “Inline string”Assign this.layout in the constructor (then call inflate() if the view should be ready before it is added), or pass the string to the View constructor. A string passed to loadLayout() that is not a .js or .html path is also used as inline HTML.
HTML file
Section titled “HTML file”A plain HTML fragment: no import, no export, no tagged template.
<div class="jops-root"> <View jops layout="/src/layout/layout_header.html"></View> <main> <Router jops animation="slide"> <View path="/home" jops layout="/src/layout/layout_home.html"></View> <MyView path="/detail" jops src="/src/MyView.js"></MyView> </Router> </main></div>Declaratively, with the layout attribute:
<View jops layout="/src/layout/mainlayout.html"></View>Or in code:
await root.loadLayout("/src/layout/mainlayout.html");root.render();.html layouts are cached in memory for the page’s lifetime, and .js layouts by the browser’s module cache. Once a path has loaded, later views use the cached copy; views that load the same path at the same moment (for example rows added in a loop) may each fetch it once.
JS layout module
Section titled “JS layout module”An ES module whose default export is a layout string:
import { layout } from "jops-core";
export default layout` <section> <h1 id="title">Home</h1> </section>`;Overriding loadLayout()
Section titled “Overriding loadLayout()”A view class can choose its own layout by overriding loadLayout(). When it passes a path, it must return or await super.loadLayout(…), because the framework waits for that promise before inflate():
class QuoteRow extends View { loadLayout() { return super.loadLayout("/quotes/layout/quote_row.html"); // returns the promise }}
// Equivalent; handy when the override does more after loading:class QuoteRow extends View { async loadLayout() { await super.loadLayout("/quotes/layout/quote_row.html"); }}Passed to super.loadLayout() |
Loading | await needed |
|---|---|---|
An .html path |
fetch() |
yes |
A .js path |
dynamic import() |
yes |
| A string (inline HTML, or a layout module imported at the top of the file) | synchronous | no |
loadLayout() loads once per view: calls made while a load is in progress wait for it, and later calls return at once. A failed load leaves the view without a layout so it can be retried. This also makes new View("/path/to/layout.html") followed by addSubView() work: addSubView() waits for the load the constructor started.
Several top-level elements
Section titled “Several top-level elements”A layout may have one or more top-level elements; all of them belong to the view (domNodes), and domNode is the first. See View.
Paths in layout=, src= and loadLayout() are resolved by JOPS, and the rule depends on the setup:
| Setup | Write paths as |
|---|---|
| Library from the CDN | page-relative (./src/layout/home.html); root-based paths are turned page-relative too |
Vendored library on the same origin (npx jops-core init, Next.js, WordPress, embedded pages) |
root-based (/src/layout/home.html, /my-app/layout/home.html) |
Electron (file://) |
relative to the page, without ./ or / (layout/home.html) |