Skip to content

Project structure

A JOPS web project is a folder of static files. There is no build output to manage while you develop: the browser loads exactly the files you see.

Choice 1 in npx jops-core init creates the smallest possible app:

  • Directorymy-jops-app/
    • index.html page with the import map and the module script
    • Directorylib/
      • jops-core.min.js the library, copied from node_modules
    • Directorysrc/
      • App.js entry point
      • Directorylayout/
        • mainlayout.html the root layout
    • Directory.vscode/
      • extensions.json
      • settings.json HTML highlighting in layout templates
    • package.json with build and postinstall scripts
    • Directorynode_modules/
      • …

The generated index.html looks like this:

index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>JOPS App</title>
<script type="importmap">
{
"imports": {
"jops-core": "/lib/jops-core.min.js"
}
}
</script>
</head>
<body>
<script type="module" src="/lib/jops-core.min.js" data-app="./src/App.js"></script>
</body>
</html>

The library is served from your own project (/lib/jops-core.min.js), not from a CDN, so the app works offline and always uses the version you installed.

Choice 2 adds a complete example app on top of the same base:

  • Directorymy-jops-app/
    • index.html
    • Directorylib/
      • jops-core.min.js
    • Directoryassets/
      • jops-hierarchy.svg
      • jops-lifecycle.svg
      • jops-navigation.svg
    • Directorysrc/
      • App.js entry point: loads the styles and the main layout
      • Features.js route view, adds Card sub-views in code
      • Form.js route view with an HTML layout file
      • DataSection.js route view, builds table rows and list items
      • Media.js route view
      • Directorycss/
        • styles.css
      • Directorylayout/
        • mainlayout.html header, Router with five routes, footer
        • layout_header.html
        • layout_hero.html
        • layout_form.html
        • layout_data.html
        • layout_footer.html
    • Directory.vscode/
      • …
    • package.json

The page the browser opens. It holds the import map (so modules can import … from "jops-core") and the module script whose data-app names your entry point. See Installation.

JOPS imports the module named in data-app and creates an instance of its default export. Its job is to create the root view, give it a layout and call render():

src/App.js
import { View } from "jops-core";
export default class App {
constructor() {
console.log("JOPS App mounted");
const mainContainer = new View();
mainContainer.setStyle("/src/css/styles.css");
mainContainer
.loadLayout("/src/layout/mainlayout.html")
.then(() => mainContainer.render());
}
}

The root view renders into <body>, or into the element named by data-root.

A layout is plain HTML: an .html file loaded with loadLayout() or a layout attribute, or a layout template string in a view class. Layouts declare nested views with tags that carry the jops attribute:

src/layout/mainlayout.html
<div class="jops-root">
<View jops layout="/src/layout/layout_header.html"></View>
<main class="main">
<Router jops animation="slide">
<View path="/home" jops layout="/src/layout/layout_hero.html"></View>
<Features path="/features" jops src="/src/Features.js"></Features>
<DataSection path="/data" jops src="/src/DataSection.js"></DataSection>
</Router>
</main>
<View jops layout="/src/layout/layout_footer.html"></View>
</div>
  • layout="…" alone creates a plain View with that HTML: good for static parts like a header or footer.
  • src="…" loads a view class from a module (its default export).
  • path="…" on a child of <Router> makes the view a route (#home, #features).

A view is a class that extends View. It owns its layout, reacts to lifecycle calls (onLayout(), onResume(), onPause(), onBackPressed()) and handles its events in methods:

src/Form.js
import { View, Store } from "jops-core";
export default class Form extends View {
async loadLayout() {
await super.loadLayout("/src/layout/layout_form.html");
}
onSubmit(event) {
event.preventDefault();
const data = Object.fromEntries(new FormData(event.target));
Store.singleton().set("form.submission", data);
}
}

Its layout binds the form’s submit event to onSubmit():

src/layout/layout_form.html
<form class="form" jops-event-bind onsubmit="onSubmit">
…
</form>

The Next.js (choice 3) and Electron (choice 4) templates create full projects with their own layout. They are described on their platform pages: