Skip to content

CORE CONCEPTS

Loom has five core concepts.

They are the roots of what makes loom a composable and reusable framework.


Node

Nodes are the most fundamental primitives. They represent each and every parts of your UI.

A Node is responsible for displaying, updating, and destroying one or more piece of interface. They are the synchronisation between your code, and what’s displayed on the screen.

But you will most likely never write a Node yourself. They are low-level and mostly spesific to renderers. Instead you will interact with higher-level abtractions built on top of them (components).


Component

Components are an abstraction on top of nodes.

It is nothing but a regular Go function, that returns a Node:

import (
    "github.com/loom-go/loom"
)

func MyComponent() loom.Node {
    return nil
}

Loom (and the renderer of your choice) provide various built-in components. Theses components can be used and composed in your own components to build a complete UI.

import (
    "github.com/loom-go/loom"
    . "github.com/loom-go/loom/components"
    . "github.com/loom-go/term/components"
)

func MyComponent() loom.Node {
    return Fragment( // using the Fragment() component from loom
        P(Text("hello")), // P() and Text() from the loom-term renderer

        P(
            Text("hello in pink"),
            Apply(Style{ // also from loom-term
                BackgroundColor: "#ffc0cb"
            }),
        ),
    ),
}

Components are only called at mount. Meaning you can spin up goroutines for async work and update states from there!

import (
    "github.com/loom-go/loom"
    . "github.com/loom-go/loom/components"
    . "github.com/loom-go/term/components"
)

func Counter() Node.loom. {
    // if your not sure what a signal is,
    // read the following section about reactivity
	count, setCount := Signal(0)

    go func() {
        for {
            time.Sleep(time.Second)
            setCount(count() + 1)
        }
    }()

	return P(Text("Count: "), BindText(count))
}

Applier

Appliers are similar to attributes in HTML. They apply something on a Node.

They come as Go struct that you instantiate yourself, and apply on a Node with loom’s Apply() component.

For instance the Style{} applier from LOOM-TERM -> :

// instantiate a Style{} applier
var styleBox = Style{
    Width: 10,
    Height: 10,
    BackgroundColor: "red",
}

return Box(
    Text("a box"),

    Apply(styleBox), // apply styleBox on the Box()
)

Renderers can also provide extended Apply() components like LOOM-TERM’s ApplyOn():

import (
    "github.com/loom-go/loom"
    . "github.com/loom-go/loom/components"
    "github.com/loom-go/term"
    . "github.com/loom-go/term/components"
)

var (
	styleBox        = Style{BackgroundColor: "red"}
	styleBoxHover   = Style{BackgroundOpacity: 0.5}
)

func MyComponent() loom.Node {
	return Box(
        Apply(styleBox),
        ApplyOn("hover", styleBoxHover),
	)
}
Example with Ref{}, On{}, Style{} and ApplyOn()
import (
    "github.com/loom-go/loom"
    . "github.com/loom-go/loom/components"
    "github.com/loom-go/term"
    . "github.com/loom-go/term/components"
)

var (
	styleInput    = Style{Width: 30, BackgroundColor: "lightgray"}
	styleBtn      = Style{BackgroundColor: "gray"}
    styleBtnHover = Style{BackgroundOpacity: 0.5}
)

func MyComponent() loom.Node {
	var input term.InputElement

	focus := func(*term.EventMouse) { input.Focus() }
	blur := func(*term.EventMouse) { input.Blur() }

	return Box(
		// apply can take multiple appliers
		Input(Apply(
            Ref{Ptr: &input},
            styleInput,
        )),

		Box(
            Text("focus"),
            Apply(On{Click: focus}, styleBtn),
            ApplyOn(styleBoxHover),
        ),
		Box(
            Text("blur"),
            Apply(On{Click: blur}, styleBtn),
            ApplyOn(styleBoxHover),
        ),
	)
}

Reactivity

Reactivity makes the UI respond to changes.

It can be updating a color when a user clicks a button, or refreshing a list when a user fills an input, or anything else related to a reaction from change.

import (
    "github.com/loom-go/loom"
    . "github.com/loom-go/loom/components"
    . "github.com/loom-go/term/components"
)

func MyComponent() loom.Node {
    text, setText := Signal("")

    update := func(e *EventInput) {
        setColor(e.InputValue()) // update the text with what the user typed
    }

    return Fragment(
        // note the use of BindText().
        // reactivity is explicit in loom.
        // read the BINDING guide to learn more
        P(Text("You typed: "), BindText(text)),

        InputText(On("input", udpate)),
    ),
}

As shown above, in loom reactivity is signal-based. If you’re coming from a modern JavaScript framework, you’ll feel right at home.

But that doesn’t mean reactivity works exactly the same as in JS frameworks. Make sure to read -> SIGNALS SCHEDULING and -> BINDING to understand the differences with loom.

Or if you want to understand more about using reactivity in general, you can read the full guide -> REACTIVITY


Renderer

By itself, loom cannot display anything on your screen. It needs a Renderer for that.

A Renderer is responsible for displaying content on screen by providing you plateform-specific components to build a UI with.
For instance a web renderer would provide DOM components like <div> or <ul>. And a theoretical mobile renderer would provide native components like View, Text, ScrollView, etc.

There’s currently two official renderers:
[*] LOOM-TERM -> | For building Terminal UIs.
[*] LOOM-WEB -> | For building Web SPAs.


Let’s keep going and build -> YOUR FIRST APP.