=============== LIBRARY RULES =============== From library maintainers: - Check the installed package first: marionette is v5; backbone.marionette is legacy. Match docs to that version and its release tag; master may differ. V5 core requires neither Backbone nor jQuery; Backbone Models and Collections use optional adapters. - Read the relevant task or API section; use docs/agents.md to find it. Inspect renderer/data providers only for integration changes, and preserve compatible existing choices. - Keep local interaction and model edits in their View. Choose ownership by the work's lifetime; async alone does not require Application. Register child Applications for ownership; start them explicitly. Regions own child UI. - Use Application for readiness and shared coordination: prepareStart loads required data using its signal; synchronous onStart applies it. Await start/restart; stop/destroy are synchronous. Active restart retains UI; construct retained shells once. - Prefer optional @mnjs/data for observable Models and Collections when suitable. It is incomplete as an application data layer: fetching and persistence require an API layer or another provider; it has no Model.save(). - Shared API names do not guarantee shared behavior: verify v5 contracts. Keep v4 contracts for installed backbone.marionette 4.x; for upgrades read docs/guides/migration.md. - For routing, read docs/guides/routing.md. The router owns URLs, application code owns navigation and cancellation, and Marionette owns feature and View lifecycle. - Use public package exports. Verify the task's affected behavior against the installed artifact, including replacement, cancellation or teardown when relevant. ### Install dependencies and run the records example Source: https://github.com/marionettejs/marionette/blob/master/docs/records.md Run these commands from the framework repository root to install dependencies and start the dev server for the records example. Uses the pinned Node 24 toolchain; packaged apps follow the consumer Node LTS policy. ```sh npm ci --ignore-scripts npm --prefix examples/records ci --ignore-scripts npm --prefix examples/records run dev ``` -------------------------------- ### Build records example Source: https://github.com/marionettejs/marionette/blob/master/docs/records.md Builds the records example from the framework repository root. ```sh npm --prefix examples/records run build ``` -------------------------------- ### start({ region }) Source: https://github.com/marionettejs/marionette/blob/master/docs/api/application.md Binds an existing Region from the same runtime before startup preparation. An active or pending start ignores newly supplied options. Use stop(); await start({ region }) to move hosts. ```APIDOC ## start({ region }) ### Description Binds an existing Region from the same runtime before startup preparation. An active or pending start ignores newly supplied options. Use `stop(); await start({ region })` to move hosts. ### Method start ### Parameters #### Request Body - **region** (Region) - Required - An existing Region instance from the same runtime. ### Behavior - If the application is already active or pending start, the supplied options are ignored. - To move hosts, call `stop()` first, then `await start({ region })`. ``` -------------------------------- ### Prepare before showing UI with Application and View (TypeScript) Source: https://github.com/marionettejs/marionette/blob/master/docs/api/application.md This example loads a summary from '/summary.json' before showing it. The Application owns the fetch request in 'prepareStart', and the View renders the result. 'prepareStart' may return a value or Promise; its result reaches 'onStart'. Failure rejects the calling 'start()' or 'restart()'. 'stop()' cancels pending preparation and tears down owned UI synchronously. ```TypeScript import { Application, View, type ApplicationInstance, type LifecycleContext } from 'marionette'; import LitDomApi from '@mnjs/adapters/dom/lit-html'; import { html } from 'lit-html'; type Summary = { title: string }; const SummaryView = View.extend({ template: ({ title }: Summary) => html`

${title}

`, }).setDomApi(LitDomApi); const SummaryApplication = Application.extend({ async prepareStart(_options: unknown, { signal }: LifecycleContext): Promise { const response = await fetch('/summary.json', { signal }); if (!response.ok) throw new Error('Could not load the summary.'); return response.json(); }, onStart(_app: ApplicationInstance, _options: unknown, summary: Summary) { this.showView(new SummaryView({ model: summary })); }, }); const mount = document.createElement('section'); document.body.append(mount); const app = new SummaryApplication({ region: { el: mount } }); await app.start(); ``` -------------------------------- ### TypeScript Application with prepareStart and onStart Source: https://github.com/marionettejs/marionette/blob/master/docs/guides/typescript.md Shows a Marionette Application that fetches and validates a summary in prepareStart, then displays a View in onStart. The example uses lit-html for templating and LitDomApi for the DOM adapter. It demonstrates passing the lifecycle signal to fetch and handling the Promise returned by start(). ```TypeScript import { Application, View, type LifecycleContext } from 'marionette'; import LitDomApi from '@mnjs/adapters/dom/lit-html'; import { html } from 'lit-html'; type Summary = { title: string }; const SummaryView = View.extend({ initialize(_options: Summary) {}, templateContext() { return { title: this.options.title }; }, template: ({ title }: Summary) => html`

${title}

` }); SummaryView.setDomApi(LitDomApi); const SummaryApplication = Application.extend({ async prepareStart(_options: unknown, { signal }: LifecycleContext): Promise { const response = await fetch('/summary.json', { signal }); if (!response.ok) { throw new Error('Could not load summary'); } const value: unknown = await response.json(); if (typeof value !== 'object' || value === null || !('title' in value) || typeof value.title !== 'string') { throw new Error('Invalid summary response'); } return { title: value.title }; }, onStart(_application: unknown, _options: unknown, summary: Summary) { this.setView(new SummaryView(summary)); this.showView(); } }); const mount = document.createElement('main'); document.body.append(mount); const app = new SummaryApplication({ region: { el: mount } }); const activation: Promise = app.start(); const activated: boolean = await activation; ``` -------------------------------- ### start(options?) Source: https://github.com/marionettejs/marionette/blob/master/docs/api/application.md Prepares and activates the Application. Returns a Promise that resolves to true if activation succeeds, or false if superseded or cancelled. ```APIDOC ## start(options?) ### Description Prepares and activates the Application. An active Application resolves `true` without repeating preparation or applying new options. Pending initial starts share the first preparation's Promise and options. ### Method start ### Parameters - `options` (object) - Optional - ApplicationStartOptions, accepts an existing Region and feature options. ### Returns - `Promise` - Resolves `true` when activation succeeds, `false` when superseded or cancelled, or when a stop/terminal owner prevents activation. ### Behavior - Initial preparation is inactive. - Start/restart resolve `false` when superseded or cancelled, or when a stop/terminal owner prevents activation. - Current preparation and startup callback failures reject. - A hook returning `false` is not a veto: it may be a successful preparation result. - Start/restart during stop or destruction resolve `false`. ### State Transition `stopped → start() → prepareStart → active → onStart` ``` -------------------------------- ### Install development tools Source: https://github.com/marionettejs/marionette/blob/master/docs/tooling.md Install ESLint and TypeScript as development dependencies with exact versions. ```sh npm install --ignore-scripts --save-dev --save-exact eslint@10.11.0 typescript@6.0.3 ``` -------------------------------- ### Install Playwright browsers Source: https://github.com/marionettejs/marionette/blob/master/docs/records.md Installs the Chromium, Firefox, and WebKit browser binaries required by the Playwright test runner before running the records example checks. ```sh npx playwright install chromium firefox webkit ``` -------------------------------- ### Create project directory and install dependencies Source: https://github.com/marionettejs/marionette/blob/master/docs/quick-start.md Creates a project directory, initializes npm, sets module type and scripts, and installs Marionette 5.0.0 with matching companion packages and dev dependencies. Use --ignore-scripts and --save-exact to pin versions. ```sh mkdir marionette-example cd marionette-example npm init -y npm pkg set type=module scripts.dev="vite" scripts.build="vite build" npm install --ignore-scripts --save-exact marionette@5.0.0 @mnjs/utils@5.0.0 @mnjs/radio@5.0.0 @mnjs/adapters@5.0.0 lit-html@3.3.3 npm install --ignore-scripts --save-dev --save-exact vite@8.3.0 ``` -------------------------------- ### Asynchronous navigation with child applications Source: https://github.com/marionettejs/marionette/blob/master/docs/guides/routing.md This example shows a complete navigation application that loads pages asynchronously. It uses a child application to fetch page data, shows loading and error views, and handles route changes. The application stops the previous child before starting a new one, and handles errors by showing a retry view. ```javascript import { Application, View } from 'marionette'; import LitDomApi from '@mnjs/adapters/dom/lit-html'; import { html } from 'lit-html'; const ShellView = View.extend({ template: () => html`
`, regions: { content: '.content' }, }).setDomApi(LitDomApi); const MessageView = View.extend({ template: ({ title, body }) => html`

${title}

${body}

`, }).setDomApi(LitDomApi); const ContentView = View.extend({ template: ({ title, body }) => html`

${title}

${body}

`, }).setDomApi(LitDomApi); const ErrorView = View.extend({ template: ({ message }) => html`

Could not load page

${message}

`, triggers: { 'click .retry': 'retry' }, }).setDomApi(LitDomApi); const PageApplication = Application.extend({ async prepareStart({ route }, { signal }) { const response = await fetch(`/pages/${route.slice(1)}.json`, { signal }); if (!response.ok) throw new Error('The page request failed.'); return response.json(); }, onStart(_app, _options, page) { this.showView(new ContentView({ model: page })); document.title = page.title; }, }); const NavigationApplication = Application.extend({ childApps: { page: PageApplication }, onStart() { if (this.getView()) return; this.showView(new ShellView()); this.onHashChange = () => { this.navigationTask = this.showRoute(); this.navigationTask.catch(error => console.error(error)); }; window.addEventListener('hashchange', this.onHashChange); this.onHashChange(); }, async showRoute() { if (!this.isRunning()) return false; const route = window.location.hash || '#home'; if (route === this.currentRoute) return false; this.currentRoute = route; const child = this.getChildApp('page'); child.stop(); const region = this.getView().getRegion('content'); if (route !== '#home' && route !== '#about') { document.title = 'Page not found'; region.show(new MessageView({ model: { title: 'Page not found', body: 'Choose Home or About.', } })); return true; } document.title = 'Loading page…'; region.show(new MessageView({ model: { title: 'Loading page…', body: 'Please wait.', } })); try { return await child.start({ region, route }); } catch (error) { child.stop(); this.currentRoute = undefined; document.title = 'Could not load page'; const errorView = new ErrorView({ model: { message: error.message } }); this.listenTo(errorView, { retry: this.onHashChange }); region.show(errorView); return false; } }, onBeforeStop() { this.currentRoute = undefined; window.removeEventListener('hashchange', this.onHashChange); }, }); const mount = document.createElement('div'); document.body.append(mount); const app = new NavigationApplication({ region: { el: mount } }); await app.start(); ``` -------------------------------- ### Configure default runtime with setDomApi Source: https://github.com/marionettejs/marionette/blob/master/docs/api/runtime.md Install the Lit DOM adapter once in the application's setup module before constructing Views. The default renderer already calls the template function with its data; configure a different renderer only when the template engine needs one. ```js import { View, setDomApi } from 'marionette'; import LitDomApi from '@mnjs/adapters/dom/lit-html'; import { html } from 'lit-html'; setDomApi(LitDomApi); const Heading = View.extend({ template: () => html`

Overview

`, }); const heading = new Heading().render(); ``` -------------------------------- ### Run Playwright tests for records example Source: https://github.com/marionettejs/marionette/blob/master/docs/records.md Runs the Playwright test suite for the records example with three workers, from the framework repository root. ```sh npx playwright test --config examples/records/playwright.config.js --workers=3 ``` -------------------------------- ### Run the development server Source: https://github.com/marionettejs/marionette/blob/master/docs/quick-start.md Starts the Vite development server. Open the printed address to see 'Hello, Marionette'. Use npm run build for a production bundle in dist. ```sh npm run dev ``` -------------------------------- ### Install @mnjs/data package Source: https://github.com/marionettejs/marionette/blob/master/docs/integrations/setup.md Install the data package at the same version as core using npm. The --ignore-scripts and --save-exact flags are used. ```sh npm install --ignore-scripts --save-exact @mnjs/data@5.0.0 ``` -------------------------------- ### Create radio and channel with typed request Source: https://github.com/marionettejs/marionette/blob/master/docs/packages/radio.md Shows how to create a radio instance, obtain a channel, reply to a request, and request a value. The result of `request` is `unknown`, so the example narrows it with a type guard before using it. ```ts import { createRadio, type RadioApi, type Channel } from '@mnjs/radio'; const radio: RadioApi = createRadio(); const channel: Channel = radio.channel('settings'); channel.reply('theme', 'dark'); const result = channel.request('theme'); const theme = typeof result === 'string' ? result : 'system'; ``` -------------------------------- ### Await child readiness in prepareStart with cancellation Source: https://github.com/marionettejs/marionette/blob/master/docs/api/application.md Use this recipe when child readiness is required for parent success. It checks for the child, preserves an already-running child, aborts the child on signal abort, and throws if the child did not start. The abort listener stays connected until the attempt settles; a committed preparation's signal is not aborted later. ```javascript import { Application } from 'marionette'; const Feature = Application.extend({ childApps: { results: Application }, async prepareStart(options, { signal }) { const child = this.getChildApp('results'); if (!child) { throw new Error('Required child is missing'); } if (child.isRunning()) { return; } signal.addEventListener('abort', () => child.stop(), { once: true }); const started = await child.start(); signal.throwIfAborted(); if (!started) { throw new Error('Required child did not start'); } }, }); const feature = new Feature(); await feature.start(); ``` -------------------------------- ### Test: readiness precedes activation and stop destroys the screen Source: https://github.com/marionettejs/marionette/blob/master/docs/guides/testing.md Verifies that an application does not start until its readiness promise resolves, and that stopping destroys the screen and region. Uses a mocked fetch that resolves with a summary. ```javascript test('readiness precedes activation and stop destroys the screen', async t => { const mount = document.body.appendChild(document.createElement('section')); const request = Promise.withResolvers(); t.mock.method(globalThis, 'fetch', () => request.promise); const app = new SummaryApplication({ region: { el: mount } }); t.after(() => { app.destroy(); mount.remove(); }); const starting = app.start(); assert.equal(app.isRunning(), false); assert.equal(mount.childElementCount, 0); request.resolve(new Response(JSON.stringify({ title: 'Ready summary' }))); assert.equal(await starting, true); assert.equal(app.isRunning(), true); assert.equal(mount.querySelector('h1').textContent, 'Ready summary'); const screen = app.getView(); assert.equal(app.stop(), true); assert.equal(app.isRunning(), false); assert.equal(screen.isDestroyed(), true); assert.equal(mount.childElementCount, 0); const region = app.getRegion(); assert.equal(app.destroy(), true); assert.equal(app.isDestroyed(), true); assert.equal(region.isDestroyed(), true); }); ``` -------------------------------- ### Vue-like sketch of RecordPage component Source: https://github.com/marionettejs/marionette/blob/master/docs/guides/framework-migration.md Illustrative pseudocode outlining the component's setup, watch, state, template, callbacks, and unmount behavior. Not executable. ```text RecordPage.vue setup(): obtain route id and global store watch(route.id): fetch record, overwrite store, reset form component state: title draft and request/loading flags template: title editor + summary + reload button child callbacks: write global store and query parent's DOM unmount(): cancel component request ``` -------------------------------- ### MnObject#initialize Source: https://github.com/marionettejs/marionette/blob/master/docs/api/mnobject.md Called when a new instance is created. In the example, it sets up a listener on the draft model to update the dirty state. ```APIDOC ## MnObject#initialize ### Description Called during instance construction. Use it to set up event listeners and initial state. ### Method `initialize` ### Parameters - **options** (Object) - Required - Options passed to the constructor. In the example, `draft` is a model to track. ### Example ```js initialize({ draft }) { this.listenTo(draft, { change: () => this.getState().set('dirty', true) }); } ``` ``` -------------------------------- ### Native replacement using Marionette, Lit, and @mnjs/data Source: https://github.com/marionettejs/marionette/blob/master/docs/guides/framework-migration.md Separates readiness from local interaction. Uses Lit for safe text interpolation and stable input updates, and observable state from @mnjs/data. Follow setup guide for matching companion packages. GET /records/42.json returns { "title": string }; API access stays in loadRecord. ```js import { Application, View } from 'marionette'; import { Model, DataApi, StateApi } from '@mnjs/data'; import LitDomApi from '@mnjs/adapters/dom/lit-html'; import { html } from 'lit-html'; import { live } from 'lit-html/directives/live.js'; async function loadRecord(id, signal) { const response = await fetch(`/records/${encodeURIComponent(id)}.json`, { signal }); if (!response.ok) throw new Error('Could not load record.'); return response.json(); } const PageView = View.extend({ template: () => html`
`, regions: { editor: '.editor', summary: '.summary' }, triggers: { 'click .reload': 'reload' }, }).setDomApi(LitDomApi); const TitleEditorView = View.extend({ template: ({ title }) => html` `, ui: { title: '.title' }, events: { 'input @ui.title': 'editTitle' }, modelEvents: { 'change:title': 'render' }, editTitle() { this.model.set('title', this.getUI('title')[0].value); }, }).setDataApi(DataApi).setDomApi(LitDomApi); const SummaryView = View.extend({ template: ({ loadedTitle, error }) => html`

Loaded title: ${loadedTitle}

${error}

`, modelEvents: { 'change:loadedTitle': 'render', 'change:error': 'render' }, }).setDataApi(DataApi).setDomApi(LitDomApi); const RecordApplication = Application.extend({ createState() { return new Model({ title: '', loadedTitle: '', error: '' }); }, viewEvents: { reload: 'reloadRecord' }, prepareStart({ id }, { signal }) { return loadRecord(id, signal); }, onStart(_app, { id }, record) { this.recordId = id; const state = this.getState(); state.set({ loadedTitle: record.title, error: '' }); if (this.getView()) return; state.set('title', record.title); const page = this.setView(new PageView()); page.showChildView('editor', new TitleEditorView({ model: state })); page.showChildView('summary', new SummaryView({ model: state })); this.showView(); }, reloadRecord() { return this.restart({ id: this.recordId }).catch(error => { this.getState().set('error', error.message); }); }, }).setStateApi(StateApi); const mount = document.createElement('main'); document.body.append(mount); const app = new RecordApplication({ region: { el: mount } }); await app.start({ id: '42' }); ``` -------------------------------- ### Use RegionInstance and ViewInstance types in screen.ts Source: https://github.com/marionettejs/marionette/blob/master/docs/tooling.md Use instance types such as RegionInstance and ViewInstance in annotations; the Region and View exports are constructors. This example checks a Region and View against the installed declarations. ```ts import type { RegionInstance, ViewInstance } from 'marionette'; export function showScreen(region: RegionInstance, view: ViewInstance): void { region.show(view); } ``` -------------------------------- ### Create main.js Source: https://github.com/marionettejs/marionette/blob/master/docs/quick-start.md Sets up the Lit DOM adapter, defines a View with a template, and shows it in a Region. The Region renders and mounts the View, and owns replacement and destruction. ```js import { Region, View, setDomApi } from 'marionette'; import LitDomApi from '@mnjs/adapters/dom/lit-html'; import { html } from 'lit-html'; setDomApi(LitDomApi); const WelcomeView = View.extend({ template: () => html`

Hello, Marionette

` }); const region = new Region({ el: '#app' }); region.show(new WelcomeView()); ``` -------------------------------- ### RecordsApplication prepareStart and onStart lifecycle methods Source: https://github.com/marionettejs/marionette/blob/master/docs/records.md Part of the RecordsApplication Application.extend definition. prepareStart awaits the API and returns a Collection built from the response; onStart stores the collection, creates the layout and status View only when needed, then calls showList and showDetail. The preparation and commit methods live in the same Application definition. ```js async prepareStart(options, { signal }) { const records = await recordsApi.list({ signal }); return new Collection(records); }, onStart(app, options, collection) { this.records = collection; const state = this.getState(); if (!(this.getView() instanceof RecordsLayout)) { const layout = new RecordsLayout(); this.showView(layout); } this.showList(); this.showDetail(); }, ``` -------------------------------- ### Dashboard application with restart and retained UI Source: https://github.com/marionettejs/marionette/blob/master/docs/guides/retained-restart.md Constructs a Marionette dashboard application that uses Application.restart() to reload summary data while preserving the page and unfinished notes. Requires @mnjs/data and @mnjs/adapters/dom/lit-html packages. The example shows setup of data and DOM APIs, a page view with summary and notes regions, and an application that manages state and handles reload errors. ```javascript import { Application, View, setDataApi, setDomApi } from 'marionette'; import { Model, DataApi, StateApi } from '@mnjs/data'; import LitDomApi from '@mnjs/adapters/dom/lit-html'; import { html } from 'lit-html'; setDataApi(DataApi); setDomApi(LitDomApi); async function loadSummary(signal) { const response = await fetch('/summary.json', { signal }); if (!response.ok) throw new Error('Could not load the summary.'); return response.json(); } const PageView = View.extend({ template: () => html`
`, regions: { summary: '.summary', notes: '.notes' }, triggers: { 'click .reload': 'reload' }, }); const SummaryView = View.extend({ template: ({ openTasks, loading, error }) => html`

Open tasks: ${openTasks}

${loading ? 'Loading…' : error}

`, modelEvents: { change: 'render' }, }); const NotesView = View.extend({ template: () => html``, }); const DashboardApplication = Application.extend({ createState() { return new Model({ openTasks: 0, loading: false, error: '' }); }, viewEvents: { reload: 'reloadSummary' }, onBeforeStart() { this.getState().set({ loading: true, error: '' }); }, prepareStart(_options, { signal }) { return loadSummary(signal); }, onStart(_app, _options, summary) { const state = this.getState(); state.set({ openTasks: summary.openTasks, loading: false, error: '' }); if (this.getView()) return; const page = this.setView(new PageView()); page.showChildView('summary', new SummaryView({ model: state })); page.showChildView('notes', new NotesView()); this.showView(); }, reloadSummary() { return this.restart().catch(error => { this.getState().set({ loading: false, error: error.message }); }); }, }).setStateApi(StateApi); const mount = document.createElement('main'); document.body.append(mount); const app = new DashboardApplication({ region: { el: mount } }); await app.start(); ``` -------------------------------- ### new Application(options?) Source: https://github.com/marionettejs/marionette/blob/master/docs/api/application.md Creates a stopped Application instance. Construction calls preinitialize, configures Region, Radio and state, constructs declared children, calls initialize, then binds state events. ```APIDOC ## new Application(options?) ### Description Creates a stopped Application instance. Construction calls `preinitialize(options)`, configures Region, Radio and state, constructs declared children, calls `initialize(options)`, then binds state events. Neither hook starts the Application. `getState()` is available inside `initialize`. ### Parameters #### Options - **region** (string | object | constructor | Region instance) - Optional - Selector string, Region options object, Region constructor, or existing Region instance. Optional without mounted UI. - **regionClass** (constructor) - Optional - Default constructor when building a Region from configuration; defaults to the runtime's Region. Individual definitions can override it. - **childApps** (object | function) - Optional - Map of names to Application constructors, or a function returning that map. Children are constructed once without arguments. Constructor/subclass maps replace rather than merge. - **state** (object) - Optional - Borrowed state. `createState(options)` supplies owned state. - **stateEvents** (object) - Optional - Declarative observation of state. - **viewEvents** (object | function) - Optional - Event-handler map, or function returning one, bound to each selected root View. Values are callbacks or Application method names. - **channelName** (string | function) - Optional - Channel name, or function returning one. A falsy name disables declarative Radio setup. - **radioEvents** (object | function) - Optional - Event handler maps, or functions returning maps. Values are callbacks or Application method names. - **radioRequests** (object | function) - Optional - Reply handler maps, or functions returning maps. Values are callbacks or Application method names. ### Notes Resolvers run with the Application as `this`. A Region constructor is a class to instantiate; a function supplied as `state` remains the source itself. `options` merges class and constructor options. Custom options are read with `getOption(name)` and are not automatically instance fields. ``` -------------------------------- ### restart(options?) Source: https://github.com/marionettejs/marionette/blob/master/docs/api/application.md Repeats preparation without stopping, retaining the root, active children, state, and subscriptions. Returns a Promise that resolves to true if activation succeeds. ```APIDOC ## restart(options?) ### Description Repeats preparation without stopping. Retains the root, active children, state, and subscriptions unless application code replaces them. Each call supersedes pending preparation. When stopped, prepares and activates using the retained destination. ### Method restart ### Parameters - `options` (object) - Optional - ApplicationRestartOptions, forwards feature options, including any `region`, unchanged to hooks, but restart never changes its destination. ### Returns - `Promise` - Resolves `true` when activation succeeds, `false` when superseded or cancelled, or when a stop/terminal owner prevents activation. ### Behavior - Restart of an active Application stays active, including `stateEvents` delivery. - An active restart retains its UI during preparation; `onStart` chooses what to update. - A newer restart, stop or destroy cancels pending preparation, preventing its obsolete result from reaching `onStart`. ### State Transitions - `stopped → restart() → prepareStart → active → onStart (retained destination)` - `active → restart() → prepareStart → onStart → active` - If preparation rejects, existing active UI is retained. ``` -------------------------------- ### Create index.html Source: https://github.com/marionettejs/marionette/blob/master/docs/quick-start.md HTML entry point that loads the main.js module and provides a #app div for the Region. ```html Marionette example
``` -------------------------------- ### Install JSDOM for testing Source: https://github.com/marionettejs/marionette/blob/master/docs/guides/testing.md Install JSDOM version 30.1.0 as a dev dependency to provide a DOM implementation for tests. ```sh npm install --save-dev --save-exact jsdom@30.1.0 ``` -------------------------------- ### Example interaction.js using public APIs Source: https://github.com/marionettejs/marionette/blob/master/docs/tooling.md Example of a Marionette View that uses public APIs and triggers a custom event. ```js import { View } from 'marionette'; export const ToggleView = View.extend({ events: { 'click button': 'toggle' }, toggle() { this.trigger('toggle', this); } }); ``` -------------------------------- ### SummaryApplication with fetch on prepareStart Source: https://github.com/marionettejs/marionette/blob/master/docs/guides/testing.md Defines a Marionette Application that fetches /summary.json during prepareStart. Uses a Lit-html view to display the title. Throws an error if the response is not OK. Tests should replace fetch and run sequentially due to shared DOM and global fetch. ```javascript import { Application, View } from 'marionette'; import LitDomApi from '@mnjs/adapters/dom/lit-html'; import { html } from 'lit-html'; const SummaryView = View.extend({ template: ({ title }) => html`

${title}

`, }).setDomApi(LitDomApi); export const SummaryApplication = Application.extend({ async prepareStart(_options, { signal }) { const response = await fetch('/summary.json', { signal }); if (!response.ok) throw new Error('Could not load the summary.'); return response.json(); }, onStart(_app, _options, summary) { this.showView(new SummaryView({ model: summary })); }, }); ``` -------------------------------- ### showView(view?, options?) Source: https://github.com/marionettejs/marionette/blob/master/docs/api/application.md Selects a supplied root and shows it through the destination with Region ShowOptions. Returns the root, or undefined if none exists. A destination is required when a root exists. If the root is already displayed, returns it without showing again; supplied options have no effect. ```APIDOC ## showView(view?, options?) ### Description Selects a supplied root and shows it through the destination with Region `ShowOptions`. Returns the root, or `undefined` if none exists. A destination is required when a root exists. If the root is already displayed, returns it without showing again; supplied options have no effect. ### Method showView ### Parameters #### Request Body - **view** (View) - Optional - The root view to show. - **options** (Object) - Optional - Region ShowOptions. ### Returns - **View** or **undefined** - The root view if shown or already displayed, otherwise undefined. ### Behavior - A destination is required when a root exists. - If the root is already displayed, it is returned without showing again and options are ignored. ``` -------------------------------- ### Create a Model with defaults and observe changes Source: https://github.com/marionettejs/marionette/blob/master/docs/packages/data.md Shows how to define a Model with a defaults function, instantiate it, listen for attribute changes, and use set and reset. Note that reset reapplies defaults. ```javascript import { Model } from '@mnjs/data'; const Preferences = Model.extend({ defaults() { return { compact: false }; }, }); const preferences = new Preferences({ theme: 'light' }); const changes = []; preferences.on('change:theme', (model, theme) => changes.push(theme)); preferences.set('theme', 'dark'); preferences.get('theme'); // 'dark' preferences.reset({ theme: 'system' }); // Reapplies compact: false. ``` -------------------------------- ### Resolve installed Marionette package path Source: https://github.com/marionettejs/marionette/blob/master/docs/readme.md Use this command from your application's directory to find the installed Marionette package.json path. Then open docs/readme.md beside it to follow relative Markdown links. ```sh node -p "require.resolve('marionette/package.json')" ``` -------------------------------- ### Look up a diagnostic with docs.mjs --diagnostic Source: https://github.com/marionettejs/marionette/blob/master/docs/api/errors.md Run the local documentation helper from the application's directory to look up a diagnostic. Replace with the installed or copied skills/marionette directory. The result includes the installed version, code status, affected objects, remediation, diagnostic URL, and docsSection. ```sh node /scripts/docs.mjs --diagnostic MN0019 ``` -------------------------------- ### Register and request channel replies with createRadio Source: https://github.com/marionettejs/marionette/blob/master/docs/packages/radio.md Shows how to create a radio channel, register multiple replies with a map, request multiple values at once, and use replyOnce for a one-time reply. The second request for 'welcome' returns undefined because the one-time reply was removed after the first invocation. ```javascript import { createRadio } from '@mnjs/radio'; const catalog = createRadio().channel('catalog'); const titles = new Map([['a', 'Overview'], ['b', 'Release notes']]); catalog.reply({ title: id => titles.get(id), count: () => titles.size, }); const summary = catalog.request({ title: 'b', count: undefined }); catalog.replyOnce('welcome', 'Welcome back'); const welcome = catalog.request('welcome'); const repeatedWelcome = catalog.request('welcome'); ``` -------------------------------- ### addChildApp(name, child) Source: https://github.com/marionettejs/marionette/blob/master/docs/api/application.md Registers a child application instance with the parent and returns the instance without starting it. ```APIDOC ## addChildApp(name, child) ### Description Registers a child application instance with the parent and returns the instance without starting it. ### Method addChildApp ### Parameters - **name** (string) - Required - The name to register the child under. - **child** (Application) - Required - The child application instance. ### Returns - The registered child instance. ``` -------------------------------- ### setView(view) Source: https://github.com/marionettejs/marionette/blob/master/docs/api/application.md Owns a prepared root without rendering or mounting it and returns it. Replacing a prepared root destroys that root. ```APIDOC ## setView(view) ### Description Owns a prepared root without rendering or mounting it and returns it. Replacing a prepared root destroys that root. ### Method setView ### Parameters #### Request Body - **view** (View) - Required - The root view to own. ### Returns - **View** - The supplied view. ### Behavior - The view is not rendered or mounted. - If a prepared root already exists, it is destroyed before the new one is set. ``` -------------------------------- ### show(view, options?) Source: https://github.com/marionettejs/marionette/blob/master/docs/api/region.md Documentation for the Region.show method, which displays a view in a region. ```APIDOC ## show(view, options?) ### Description Shows a View or CollectionView instance in the Region. Returns the Region on success, or `undefined` when an unmatched selector is allowed by `allowMissingEl`. ### Method show ### Parameters - **view** (View|CollectionView) - Required - The view instance to show. - **options** (Object) - Optional - Options to override `allowMissingEl` and `replaceElement` for this call. ### Behavior - Resolves the region's element and checks the incoming view. - Emits `before:show`, empties the outgoing view, and takes ownership of the incoming view. - Renders the incoming view if it is not already rendered. - Inserts its element, emitting attach notifications if the region is in the document and monitoring is enabled. - Emits `show`. ### Notes - Showing the current view again does not rerender it or repeat show events. - A destroyed view cannot be shown. - A view already owned by an Application, Region, or CollectionView must first be detached from that owner. - A view adopting existing attached markup does not receive another attach notification just because a region shows it. - An unmatched selector produces `MN0005` unless allowed; no configured element produces `MN0004`. - A destroyed incoming view produces `MN0007`, and an already owned view produces `MN0003`. - `allowMissingEl` skips that show attempt without taking ownership of the view. - A later explicit operation retries an unresolved selector; the region does not watch for DOM changes. - An unmatched lookup clears the cached `el` but retains the original selector. - A later `show()` retries that selector and applies its missing-element options. - With no current view, `empty()` also needs to resolve the element; `reset()` and `destroy()` use that emptying path. - When a current view exists, cleanup releases it using the resolved element without another selector lookup. - A skipped show leaves the region usable for cleanup or a later explicit show. - Operations are synchronous. Lifecycle callbacks that throw interrupt the operation; region does not roll back partially completed work. ``` -------------------------------- ### MnObject.extend Source: https://github.com/marionettejs/marionette/blob/master/docs/api/mnobject.md Creates a new MnObject subclass. The example shows how to define a class that tracks draft changes using a state model and event listeners. ```APIDOC ## MnObject.extend ### Description Creates a new MnObject subclass with custom methods and state API. ### Method `extend` ### Parameters - **properties** (Object) - Required - An object containing methods and properties to add to the subclass. ### Example ```js import { MnObject } from 'marionette'; import { Model, StateApi } from '@mnjs/data'; const DraftChanges = MnObject.extend({ createState() { return new Model({ dirty: false }); }, initialize({ draft }) { this.listenTo(draft, { change: () => this.getState().set('dirty', true) }); }, markSaved() { this.getState().set('dirty', false); }, }).setStateApi(StateApi); ``` ``` -------------------------------- ### Set up views and collection for a sortable and filterable list Source: https://github.com/marionettejs/marionette/blob/master/docs/guides/lists.md This snippet defines ItemView, EmptyView, ItemList, and CatalogView, creates a Collection with sample items, and mounts the CatalogView in a Region. It uses @mnjs/data and lit-html adapters. The list is sorted by label and can be filtered by availability. The outer Region owns CatalogView, which owns a Region for the list. Destroy the outer Region when the feature ends and dispose data appropriately. ```javascript import { CollectionView, Region, View } from 'marionette'; import { Collection, DataApi } from '@mnjs/data'; import LitDomApi from '@mnjs/adapters/dom/lit-html'; import { html } from 'lit-html'; const ItemView = View.extend({ tagName: 'li', template: ({ label }) => html` ${label} `, modelEvents: { change: 'render' }, }).setDataApi(DataApi).setDomApi(LitDomApi); const EmptyView = View.extend({ tagName: 'li', template: () => html`No matching items.`, }).setDomApi(LitDomApi); const ItemList = CollectionView.extend({ tagName: 'ul', childView: ItemView, emptyView: EmptyView, viewComparator: 'label', }).setDataApi(DataApi); const CatalogView = View.extend({ template: () => html`
`, regions: { items: '.items' }, ui: { available: '.available' }, events: { 'change @ui.available': 'filterItems', 'click .reverse': 'reverseItems' }, onRender() { this.descending = false; this.getUI('available')[0].checked = false; this.list = new ItemList({ collection: this.options.items }); this.showChildView('items', this.list); }, filterItems() { this.list.setFilter(this.getUI('available')[0].checked ? { available: true } : null); }, reverseItems() { this.descending = !this.descending; this.list.setComparator(this.descending ? (left, right) => right.model.get('label').localeCompare(left.model.get('label')) : 'label'); }, }).setDomApi(LitDomApi); const items = new Collection([ { id: 'a', label: 'Apricot', available: true }, { id: 'b', label: 'Blueberry', available: false }, ]); const catalog = new CatalogView({ items }); const mount = document.createElement('section'); document.body.append(mount); const region = new Region({ el: mount }); region.show(catalog); ``` -------------------------------- ### Application.extend(prototypeProperties?, staticProperties?) Source: https://github.com/marionettejs/marionette/blob/master/docs/api/application.md Creates a subclass of Application. `preinitialize` and `initialize` default to no-ops. `cid` uses the class's `cidPrefix` ('mna' by default). ```APIDOC ## Application.extend(prototypeProperties?, staticProperties?) ### Description Creates a subclass of Application. `preinitialize` and `initialize` default to no-ops. `cid` uses the class's `cidPrefix` (`'mna'` by default). See class utilities for shared options and custom constructors. ### Parameters - **prototypeProperties** (object) - Optional - Properties to add to the subclass prototype. - **staticProperties** (object) - Optional - Static properties to add to the subclass. ``` -------------------------------- ### Using @mnjs/utils standalone helpers Source: https://github.com/marionettejs/marionette/blob/master/docs/packages/utils.md Demonstrates the standalone helpers from @mnjs/utils: buildEventArgs, callHandler, onceWrap, uniqueId, setProperty, and isString. Shows how to build event descriptors, call a handler with a receiver and arguments, wrap a callback to run once, generate a unique ID, set a property safely, and check if a value is a string. ```javascript import { buildEventArgs, callHandler, onceWrap, uniqueId, setProperty, isString } from '@mnjs/utils'; const receiver = { prefix: 'Hello' }; const greet = function(name) { return `${this.prefix}, ${name}`; }; const descriptors = buildEventArgs({ greet }, receiver); const greeting = callHandler(greet, receiver, ['Ada']); const removals = []; const greetOnce = onceWrap(greet, wrapper => removals.push(wrapper)); const first = greetOnce.call(receiver, 'Ada'); const repeated = greetOnce.call(receiver, 'Grace'); const key = uniqueId('component-'); const values = {}; setProperty(values, key, first); const isLabel = isString(first); ``` -------------------------------- ### BackboneApi Source: https://github.com/marionettejs/marionette/blob/master/docs/packages/adapters.md The BackboneApi adapter implements DataApi and StateApi for Backbone models and collections. It provides methods for key, get, has, serialize, models, subscribe, observeCollection, and disposeOwned. ```APIDOC ## BackboneApi ### Description The BackboneApi adapter implements DataApi and StateApi for Backbone models and collections without modifying Backbone sources or prototypes. It supports Backbone 1.3.1 and above. ### Methods #### key(model) - **model** (Backbone.Model) - Required - The model to get the key for. - Returns the model's `cid`. #### get(model, name) - **model** (Backbone.Model) - Required - The model to read from. - **name** (string) - Required - The attribute name. - Returns the attribute value, or `undefined` if absent. #### has(model, name) - **model** (Backbone.Model) - Required - The model to check. - **name** (string) - Required - The attribute name. - Returns `true` if the attribute exists (including `undefined` values), otherwise `false`. #### serialize(model) - **model** (Backbone.Model) - Required - The model to serialize. - Returns the current `model.attributes` object without copying. #### models(collection) - **collection** (Backbone.Collection) - Required - The collection to get models from. - Returns a shallow copy of the ordered `collection.models` array. #### subscribe(source, nameOrMap, callback?, context?) - **source** (Backbone.Model or Backbone.Collection) - Required - The event source. - **nameOrMap** (string or object) - Required - Event name or map of events. - **callback** (function) - Optional - Callback function. - **context** (object) - Optional - Callback context. - Uses native Backbone events and returns an idempotent disposer that removes only these registrations. #### observeCollection(collection, notify, context?) - **collection** (Backbone.Collection) - Required - The collection to observe. - **notify** (function) - Required - Callback for structural changes. - **context** (object) - Optional - Callback context. - Translates native collection events into structural changes and returns an idempotent disposer. #### disposeOwned(source) - **source** (Backbone.Model or Backbone.Collection) - Required - The source to dispose. - Does nothing to the source itself; owner subscriptions are released by StateApi cleanup. ### Event Handling - `sort` reports `reorder`. - `update` reports added/removed models; removals destroy children. - `reset` reports `reset` and rebuilds children. - Sort events with truthy `add`, `remove`, or `merge` options are suppressed. ### Limitations - Reorder-only `collection.set([second, first])` does not emit `update`; use `collection.trigger('sort', collection, {})` to notify observing CollectionViews. - For explicit comparator sort, call `collection.sort()` without forwarding truthy `add`, `remove`, or `merge` flags. ### Usage Example ```js import Backbone from 'backbone'; import BackboneApi from '@mnjs/adapters/backbone'; import { View } from 'marionette'; const LabelView = View.extend({ template: ({ label }) => label, modelEvents: { 'change:label': 'render' } }); LabelView.setDataApi(BackboneApi); const model = new Backbone.Model({ label: 'Inbox' }); const view = new LabelView({ model }).render(); model.set('label', 'Archive'); ``` ``` -------------------------------- ### Run ESLint Source: https://github.com/marionettejs/marionette/blob/master/docs/tooling.md Run ESLint on the project with no warnings allowed. ```sh npx eslint . --max-warnings=0 ```