Cheat Sheet

This page gives a quick summary of all the basics for developing apps with CodeOnlyJS.

#Generating New Projects

You can generate a new CodeOnly project with cogent, the CodeOnly code generator:

To generate a new standalone single page app use the spa template:

npx codeonlyjs/cogent new spa MySinglePageApp

The fullstack template generates a single page app with an Node/ExpressJS server configured to serve the app and to serve as a backend API server:

npx codeonlyjs/cogent new fullstack MySinglePageApp

The generated project will include a readme file with additional information on the structure of the project and how to run it.

See also cogent.

#Components

#Generating New Components

Cogent can also generate new components:

npx codeonlyjs/cogent new component MyNewComponent

The page template generates a component with router handler:

npx codeonlyjs/cogent new page MyNewPage

#Anatomy

Most components will conform to this basic structure:

import { Component, css } from "@codeonlyjs/core";

// Components extend the 'Component' class
export class MyComponent extends Component
{
    // Logic
    constructor()
    {
        super()
    }

    // DOM template
    static template = {

    }
}

// CSS styles
css`
`;

#Mounting

Components are usually automatically mounted as children of other components. The root component of your application needs to be manually mounted however by calling the mount method and passing either an element to mount into, or a selector for the element to mount into:

// Create component
let main = new MyMainComponent();

// Mount it into the `body` element
main.mount("body");

When a component is mounted/unmounted, its onMount/onUnmount methods will be called to notify the component. The mounted property indicates if the component is currently mounted.

class MyComponent extends Component
{
    // Called when component is mounted
    onMount()
    {
        // Acquire external resources
    }

    // Called when component is unmounted
    onUnmount()
    {
        // Release external resources
    }
}

Components should only hold references to external resources (eg: external event listeners) while the component is mounted otherwise dangling references to the component might be held causing the component to be unable to be garbage collected.

The listen method provides an easy way to safely connect external event listeners.

See also Mounting Components and Component Lifecycle.

#Async Data Loads

The load method provides an easy mechanism for performing async data loads:

class MyComponent extends Component
{
    constructor()
    {
        super();
        this.refresh();
    }

    refresh()
    {
        // Async data load...
        load(async () => {
            this.data = await fetch(...);
        });
    }

    static template = [
        {
            // Show spinner while loading
            if: c => c.loading,
            type: "div .spinner",
        },
        {
            // Show error message if failed
            elseif: c => c.loadError,
            type: "div .error",
            text: c => c.loadError.message,
        },
        {
            // Otherwise show the loaded data
            else: true,
            type: "pre .data",
            text: c => JSON.stringify(c.data, null, 4),
        }
    ]
}

While the load is in progress the components loading property will return true - this can be used to show a spinner for example.

If an error is thrown during the load, the loadError property will hold the error (after the load method returns).

See also Async Data Loads.

#Accessing HTML Elements

To access HTML elements created by a component's template, use the bind directive to store a reference to the element in a property on the component.

The create method ensures the DOM elements have been created (usually they're not created until the component is mounted).

class MyComponent extends Component
{
    constructor()
    {
        // Make sure DOM is constructed...
        this.create();

        // ...before accessing bound elements:
        this.myDiv.whatever...
    }


    static template = {
        type: "div",
        bind: "myDiv",
    }
}

See also Binding Elements.

#Templates

Templates define the HTML content of a component and are are declared as the static template property of the component class (see anatomy example above).

#Text and HTML

Declare text nodes as JavaScript strings:

static template = [
    "Text Node",
]

Use the html directive to include HTML in a template:

static template = [
    html("<em>This is HTML</em>"),
]

See also Text and HTML.

#Elements

HTML elements are declared as a JavaScript object with the type property set as the tag name.

Static attributes can be included after the tag name, and class and id with CSS style syntax.

static template = {
    type: "input .my-class #my-id type=password",
}

Attributes can also be declared as properties on the object:

static template = {
    type: "a",
    href: "/",
    text: "Home",
}

Child elements are declared using the $ property:

static template = {
    type: "div",
    $: [
        { type: "span", text: "child 1" },
        { type: "span", text: "child 2" },
        { type: "span", text: "child 3" }
    ]
}

See also HTML Elements.

#Dynamic Content

Most things in a template can be declared dynamically using fat arrow => callbacks. The callback's first parameter is a reference to the component instance and typically called c (for "component").

When a dynamic property has changed, call invalidate to mark the component as requiring an update.

class MyComponent extends Component
{
    #greeting = "Hello World",
    get greeting()
    {
        return this.#greeting;
    }
    set greeting(value)
    {
        // Store new greeting
        this.#greeting = value;

        // Schedule an update for this component's DOM
        this.invalidate();
    }

    static template = {
        type: "div",
        text: c => c.greeting,
    }
}

See also Dynamic Content.

#Boolean Classes

To dynamicly add or remove a class to an element, include a property in the template with the class name prefixed by class_:

static template = {
    type: "div",
    class_selected: c => c.isSelected,
}

Similar settings are available style properties and element visibility.

#Events

To connect event handlers to template elements, include a property with on_ prefixed to the event name:

class MyComponent extends Component
{
    onClick(ev)
    {
        alert("Clicked");
    }

    static template = {
        type: "button",
        on_click: (c, ev) => c.onClick(ev),
    }
}

Instead of a function you can also just include the name of the function on the component to call. This is equivalent to the above:

        on_click: "onClick"

See also Event Handlers.

#Nested Components

To use another component in a template, set the type property to the component class.

Component properties and event handlers work just like with HTML elements:

import { MyOtherComponent } from "./MyOtherComponent.js";

class MyComponent extends Component
{
    static template => {
        type: MyOtherComponent,
        prop1: "Hello",
        prop2: "World",
        on_click: c => alert("Oi!"),
    }
}

See also Component References.

#if Blocks

To conditionally include template content, use an if directive

static template = [
    {
        if: c => c.someCondition,
        type: "div",
        text: "Hello World",
    }
]

elseif and else directives are also supported:

static template = [
    {
        if: c => c.someCondition,
        type: "div",
        text: "Hello World",
    },
    {
        elseif: c => c.someOtherCondition,
        type: "div",
        text: "Goodbye",
    }
    {
        else: true
        type: "div",
        text: "Error",
    }
]

See also if directive.

#foreach Blocks

Repeat elements using the foreach directive, passing an array of items to repeat over.

The callbacks for other properties change from c for the component to i for the array item. To access the component use i.outer.model.

static template = [
    {
        foreach: [ "apples", "pears", "bananas" ],
        type: "div",
        text: i => i,
    }
]

For more efficient updates on larger arrays, provide an item key callback:

static template = [
    {
        foreach: {
            items: c => c.items,
            itemKey: i => i.id,
        },
        type: "div",
        text: i => `${i.id}: ${i.name}`,
    }
]

See also foreach directive.

#Other

Templates also support Input Bindings, CSS Transitions and Embed Slots.

Also, for a set of helpers for more concisely construction templates, see the Fluent API.

#Styles

Styles can be declared with the css template literal function and will be added to the <head> section of the document:

css`
div.my-class
{
}
`

See also Styles.

#Router

For single page apps, CodeOnly include a simple but effective router object.

Register route handlers:

import { router } from "@codeonlyjs/core";

router.register({
    pattern: "/about",
    match: (to) => { 
        to.page = new AboutPageComponent(); 
        return true;  
    }
});

Hook up event handler to show the loaded page:

router.addEventListener("didEnter", (from, to) => {

    // Load page into our embed slot
    this.routerContentSlot.content = to.page;

});

Don't forget to start the router after you've mounted the root component of your application:

// Mount main component
new Main().mount("main");

// Start the router
router.start();

To construct links just use normal <a href> links - the router will detect clicks on suitable links and automatically perform in-page navigation - no special handling is required here. See Out of App Links for more on controlling which links the router responds to.

See Router for more information including how to configure navigation guards, async page loading and URL mapping.

#Rendering

CodeOnlyJS supports server-side rendering (SSR) and static site generation (SSG).

See Rendering for how to set this up.