Getting Started

In this section, we will walk you through the steps to get started with the Instant UI library.



Installation

Instant UI either can be imported as a script or can be installed as npm package.


Using from CDN or static hosting:

Instant UI library can be used without any build system, either by uploading files to your own web server or by using an existing CDN. It supports both global and module imports.

Global:

<script src="https://cdn.jsdelivr.net/npm/instantui/dist/instantui.min.js"></script>

<script>

    const { App, Text } = instantui;

    const myApp = new App()
    myApp.addChild(new Text('Hello World!'))
    myApp.render()

</script>

Module:

<script type="module">

    import { App, Text } from 'https://cdn.jsdelivr.net/npm/instantui/dist/instantui.module.js';

    const myApp = new App();
    myApp.addChild(new Text('Hello World!'));
    myApp.render()

</script>


Installing from npm

Instant UI provides a building system for node.js applications. This way you can deploy compact and minified version of your project without using any other build system.

Let's start by creation node.js project. First we create an empty MyApp folder and run:

$ npm init

Then install Instant UI:

$ npm install instantui

Create myApp.js inside MyApp folder and paste the following code:

import { App, Text } =  require('instantui');

const myApp = new App();
myApp.addChild(new Text('Hello World!'));
myApp.render()

Now you can build the project by running following command:

$ npx instantui myApp.js -r

It will create a folder named public with following files inside:

  • default.min.css - Stylesheet for the app
  • index.html - Entry point for the app
  • instantui.min.js - Instant UI library
  • myApp.js - Your app

Now you can start your server and open the index.html file in your browser. You should be able to see the "Hello World!" rendered in the page.

If you don't have any server installed you can try building project with -r flag. This flag will build your project, start a server and open the index.html file in your browser.

Lets explore other flags that you can use:

  • -a or --asset: Asset folder path to be copied. This will copy all files from the specified folder to the public folder.
  • -h or --help: Shows help.
  • -c or --clean: Cleans output path before building.
  • -i or --input: Input file.
  • -m or --minimize: Minimizes output.
  • -t or --theme: Sets the theme. Values: default, darkly, materia, minty, morph, quartz, sketchy, vapor.
  • -o or --output: Output path.
  • -r or --run: Runs server and opens in the browser.
  • -v or --version: Shows the version number of Instant UI.


Basics

We learned how to install and build Instant UI. Now we will go through the basics of Instant UI.

Method chaining

In Instant UI, every method returns the instance of the object itself. This way we can create our widgets in an hierarchical way. It provides simple and clean way to create complex widgets. Let's create a simple app with login form:

const { App, Button, Form, Input } = require('instantui');

new App().addChild(
    new Form().addChildren(
        new Input("Email").required(true).setPlaceholder("Enter your email"),
        new Input("Password").required(true).setPlaceholder("Enter your password"),
        new Button("Submit").onClick(() => console.log("Form has been submitted."))
    )
).render();

Apply method

In Instant UI, every widget has an apply method. This method allows you to execute a block of code within the context of an object. In this scope, you can access the object itself and outer scope variables. This way you won't need to assign object to a variable and you'll stay in the method chain. See following example:

const { App, Button, Column, Text } = require('instantui');

let clickCount = 4;

new App().addChildren(
    new Column().addChildren(
        new Text(`Clicked ${clickCount} times`),
        new Button("Click me")
    ).apply(self => {
        let text = self.getChild(0);
        let button = self.getChild(1);
        button.onClick(() => {
            clickCount++;
            text.setText(`Clicked ${clickCount} times`);
        });
    })
).render();

Extending widgets

In Instant UI, every widget is a class. You can extend the base class and create your own widgets. Let's create a custom button:

const { App, Button, Icon } = require('instantui');

class MyButton extends Button {
    constructor() {
        super();
        this.setContent(new Icon("fullscreen"))
        this.onClick(() => console.log("Screen has been maximized."));
    }
}

new App().addChild(new MyButton()).render();

Icons

Instant UI uses Bootstrap 5 icons. You can find all available icons here. You can create icon by just creating an instance of Icon class with a name of the icon as seen in the example below:

const myIcon = new Icon("fullscreen")



State Management

Instant UI provides powerful and lightweight state machine. Any change on the states propagates to the app so app can react to the change. Nested state allows to define complex state trees with ease. Consider following example:

const { App, Input, State, Text } = require('instantui');

var userState = new State("user", {
    username: 'johndoe',
    email: 'johndoe@example.com'
});

new App().addChildren(
    new Input("email").setPlaceholder("Enter new email").onChange((val, self)=>{
        userState.set({
            ...userState.get(),
            email: val
        })
        self.value("")
    }),
    new Text(`Username: ${userState.get().username}, Email: ${userState.get().email}`).apply(self =>{
        userState.addChangeListener(user => {
            self.setText(`Username: ${user.username}, Email: ${user.email}`);
        });
    }),
).render();

In this example we have an input and output fields. We first created a state object. And then we added change calback to the input field. When the input field changes, we update the state object. Output text has an change listener that updates the text every time the state changes. You may ask, couldn't we just connect input field to the output field? Yes, you can. But this is not the best way to do it when you have a complex app. When your app grows, you will need a central state manager that you can manage the information traffic.

You can see extended state tree on the following example:

const appState = new StateGroup().addState(
    new State("user", {
        username: 'johndoe',
        email: 'johndoe@example.com',
        membership: 'basic'
    }),
    new StateGroup("project").addState(
        new State("details", {
            name: 'My Project',
            description: 'This is my project'
        }),
        new State("coverUrl", "./cover.png"),
        new State("assets", ["./img.png", "./img2.png"]),
        new StateGroup("settings").addState(
            new State("theme", "light"),
            new State("fontSize", 16),
            new State("fontFamily", "monospace"),
        )
    )
);

We can create a tree of states by adding StateGroups recursively. It's also possible to add listeners to the sub-states. This way we'll be able to listen only necessary states. As you can see in the example below, we can reach sub-states by the state name using dot notation.

appState.project.settings.theme.addChangeListener(theme => {
    console.log("Dark mode has been updated: ", theme);
})

appState.project.settings.theme.set("dark");
// output: 
// Dark mode has been updated: dark



Routing

Instant UI provides a simple routing system that allows you to create multi-page apps and navigate between them without reloading the whole window.

import { App, Heading, Nav, NavLink, Route, Router } from "instantui";

const myApp = new App().addChildren(
    new Nav().addChildren(
        new NavLink("Home").onClick(e => { e.preventDefault(); Router.navigate("/"); }),
        new NavLink("About").onClick(e => { e.preventDefault(); Router.navigate("/about"); }),
        new NavLink("Contact").onClick(e => { e.preventDefault(); Router.navigate("/contact"); }),
    ),
    new Route()
        .addRoute("/", () => new Heading(2, "Landing Page"))
        .addRoute("/about", () => new Heading(2, "About Page"))
        .addRoute("/contact", () => new Heading(2, "Contact Page"))
).render();

In the example above, we created a simple app with a navigation bar and a route. It's that simple to create a multi-page app. You may notice that we used e.preventDefault(), so we prevented link from reloading the page.

Since Route is an another layout widget, it's also possible to create nested routes just by adding sub Route widgets as children. In the example below, we have a nested document page route structure. /docs route has some sub-routes: /docs/installation, /docs/usage, /docs/api and the child page that loads the content of the docs page itself.

new Route()
    .addRoute("/docs", () =>
        new Route()
            .addRoute("/docs/installation", () => new Heading(2, "Installation Page"))
            .addRoute("/docs/usage", () => new Heading(2, "Usage Page"))
            .addRoute("/docs/api", () => new Heading(2, "Api Page"))
            .addChild(new Heading(2, "Documents Page"))
    );