Introduction

🎉🎊 Thank you for checking out Allotize. If you have any question, don't hesitate to ask a question via GitHub Issues.

What is Allotize?

Allotize provides a platform for building modern web applications without backend code, servers or even databases. Yet, you are able to build apps such as collaborative planning tools or social media platforms.

Think of Allotize as something that gives your data superpowers, and allows it to teleport and sync between devices!

How it works

You connect variables, objects, or any other JavaScript code to Allotize. Your users will then be able to detect updates and changes on each others' machines.

This means that you can build message boards, collaborative services, games or anything you could think of with just client-side code. Here is an example of a cube that can be rotated by users along with an upvoting system.

Let's take a look at the upvoting code. We simply create an object votes that holds our information and register it to Allotize via Allotize.Crate(votes).

const votes = Allotize.Crate({
    route: "cube/votes",

    data: {
        upvotes: 0,
    },

    onChange: (oldData, newData) => {
        document.getElementById("upvotes").innerHTML = newData.upvotes;
    },
});

document.getElementById("upvote").addEventListener('click', (e) => {
    votes.data.upvotes += 1;
});

document.getElementById("downvote").addEventListener('click', (e) => {
    votes.data.upvotes -= 1;
});

That's it! Everyone who loads our website will have a synced number of upvotes that changes in real-time. Proceed to the next chapter, to find out how you can create your own app!

Tutorial: Creating a To-Do application

This tutorial will help you get up and running with Allotize.

You are not able to follow this tutorial yet

This tutorial is under construction

Assumptions:

  • You have experience of JavaScript
  • You have some experience with React

In this tutorial, you learn:

  • How to create a simple collaborative To-Do app with Allotize
  • A workflow for developing applications using Allotize
  • Common pitfalls when using Allotize and how to avoid them
  • How to publish your app to GitHub Pages

The App

We are building a simple To-Do App, but it's not any To-Do App, this one will have collaboration support and sync its state between users in real-time!

App Preview

Here is an example of the app we are building. To the left is a view of the app running in Firefox, to the right you can see the same app in Chrome.

App Requirements

  • Users should be able create a new To-Do
  • Users should be able to mark a To-Do as done
  • Users should be able to remove a To-Do
  • Users should be able to change the description of a To-Do
  • All updates should be synced in real-time without conflicts
  • Opening and closing the app should not clear the To-Dos

Setup

This section describes how to install Allotize and crate a new app.

Install using create-allotize-app

Allotize is published to npm, along with a utility tool to quickly start a new Allotize app called create-allotize-app. If you don't already have npm installed, you can find instructions on how to install it here.

To create your project, run this command:

npm init allotize-app my-allotize-app

This will download a project template running Allotize and React. If everything went as expected, you should receive a message:

Thank you for downloading the Allotize template

Proceed by navigating to your newly created project and install the dependencies:

cd my-allotize-app
npm install

Once installed, you can start the development server via:

npm run start

Go ahead and visit localhost:8080 to make sure everything is running smoothly.

✨ Great job at creating your first Allotize app! In the next section, we will be looking at how to create a new component using Allotize.

Create a To-Do component

For our To-Do application to work, we need to create a To-Do component that contains the information associated with a To-Do.

Create a new file src/js/components/Todo.jsx.

The first thing we need to do is to create a new state of our To-Do. Allotize uses a URI scheme to be able to identify different resources. We will discover our To-Do's via todo/:todoId and use a unique id for every entry. To do so, we use the hook called useAllotizeState available in the allotize-react package. In this case, we want to create a new route to point to the state of our To-Do. Telling Allotize that this is our intention, we simply set route to match our access pattern and state to some initial state.

import { useAllotizeState } from "allotize-react";

export function Todo(props) {
    const [state, setState] = useAllotizeState({
        route: `todo/${props.todoId}`,
        state: {
            description: props.desc,
            done: false,
        },
    });

    return (
      <div>
      </div>
    );
}

Now we can use state and setState just like we use the regular useState-hook. In our case, we want a button that can mark the state.done as true/false.

import React, { useEffect, useRef, useState } from "react";
import { useAllotizeState } from "allotize-react";

export function Todo(props) {
    const [state, setState] = useAllotizeState({
        route: `todo/${props.todoId}`,
        state: {
            description: props.desc,
            done: false,
        },
    });

    return (
      <div>
        [TODO-{props.todoId}]
        <button onClick={() => setState({...state, done: !state.done})}>
            { state.done ? "Restore" : "Done"}
        </button>
      </div>
    );
}

Finally, we add some extra styling and buttons to our To-Do. This part can be skipped if you don't want to style your components using bootstrap.

import React, { useEffect, useRef, useState } from "react";
import { useAllotizeState } from "allotize-react";

const todoStyle = {
    padding: "12px",
}

const doneStyle = {
    opacity: 0.4,
}

export function Todo(props) {
    const [state, setState] = useAllotizeState({
        route: `todo/${props.todoId}`,
        state: {
            description: props.desc,
            done: false,
        },
    });

    const [edit, setEdit] = useState(false);
    const editInput = useRef();
    useEffect(() => {editInput.current && editInput.current.focus();});

    return (
      <div style={state.done ? {...todoStyle, ...doneStyle} : todoStyle}>
        <div className="card">
        <div className="card-header">
            <strong className="mr-auto">
                {state.done ? "Done - " : ""}[TODO-{props.todoId}]
            </strong>
            <div className="btn-group btn-group-sm float-right" role="group" aria-label="Basic example">
                <button className="btn btn-primary" onClick={() => setState({...state, done: !state.done})}>
                    { state.done ? "Restore" : "Done"}
                </button>
                <button className="btn btn-secondary" onClick={() => setEdit(true)}>Edit</button>
                <button className="btn btn-danger" onClick={() => props.deleteTodo(props.todoId)}>
                    Delete
                </button>
            </div>
        </div>
        <div className="card-body">
            {
                edit ?
                <form className="input-group mb-3" onSubmit={() => setEdit(false)}>
                    <input
                    ref={editInput}
                    type="text"
                    className="form-control"
                    name="description"
                    onChange={(e) => setState({...state, description: e.target.value})}
                    placeholder="To-Do description"
                    value={state.description}
                    aria-describedby="button-addon2"/>
                    <div className="input-group-append">
                    <button className="btn btn-primary" type="submit" id="button-addon2">
                        Save
                    </button>
                    </div>
                </form>
                :
                state.description
            }
        </div>
        </div>

      </div>
    );
}

Creating a container for our To-Dos

Create a new file src/containers/TodoTracker.jsx.

import React, { useState } from "react";
import { Todo } from './Todo.jsx'
import { useAllotizeState } from "allotize-react";

export function TodoTracker(props) {
    const [state, setState] = useAllotizeState({
        route: "todo-collection",
        state: {
            todos: [],
            id: 0,
        }
    });
    const [newTodoDesc, setNewTodoDesc] = useState("");

    const handleTodoDescChange = (e) => setNewTodoDesc(e.target.value);

    const createTodo = (e) => {
        e.preventDefault();
        setState({
            todos: state.todos.concat({desc: newTodoDesc, id: state.id}),
            id: state.id + 1,
        });
        setNewTodoDesc("");
    };

    const deleteTodo = (id) => {
        setState({
            ...state,
            todos: state.todos.filter(todo => todo.id !== id),
        });
    };

    return (
        <div>
            <form className="input-group mb-3" onSubmit={createTodo}>
                <input
                type="text"
                className="form-control"
                name="description"
                onChange={handleTodoDescChange}
                placeholder="To-Do description"
                value={newTodoDesc}
                aria-describedby="button-addon2"/>
                <div className="input-group-append">
                <button className="btn btn-primary" type="submit" id="button-addon2">
                    Create
                </button>
                </div>
            </form>
            {state.todos.map(todo => {
                return (
                    <Todo
                        key={todo.id}
                        desc={todo.desc}
                        todoId={todo.id}
                        deleteTodo={deleteTodo}
                    />
                );
            })}
        </div>
    );
}

Allotize Concepts

Installation

Use Allotize

npm install allotize@0.0.1-alpha.1

Allotize is internally using WebAssembly (WASM), which must be dynamically imported. Information on dynamic imports can be found at https://javascript.info/modules-dynamic-imports. You will also need to handle .wasm files. If you struggle setting up WASM support, you can use our template create-allotize-app (see bottom of the page).

Use Allotize with React

npm install react
npm install allotize@0.0.1-alpha.1
npm install allotize-react@0.0.1-alpha

Allotize is internally using WebAssembly, which must be dynamically imported. Information on dynamic imports can be found at https://javascript.info/modules-dynamic-imports. You will also need to handle .wasm files. If you struggle setting up WASM support, you can use our template create-allotize-app (see bottom of the page).

Create an Allotize app with React using create-allotize-app

For the easiest setup, we recommend using create-allotize-app, which will automatically handle the dynamic import of allotize. In addition, this template includes helpers for

npm init allotize-app my-allotize-app
cd my-allotize-app
npm install

Crates

Crates is a resource that is available in the Allotize ecosystem. A crate aims to act like a box of information and you as a developer is able to define what data it includes, what is shared and what happens when it changes.

Let's take a quick look at a very simple example of a Crate. If we want to create a Counter that is available and editable by everyone on our website, we can simply define the user at the route counters/<counter> and define the desired data scheme.

const Counter = (counter) => Allotize.Crate({
    route: "counters/" + counter,

    state: {
      count: 0,
    },

    onChange: (oldState, newState) => {
      console.log(newState.count);
    }
});


const counter = Counter("counter-1");
counter.state.count += 1;

Common Crate Patterns

Thera are some common crate patterns you can use:

State

const State = Allotize.Crate({
    route: "myState",
    state: {
      count: 0,
    },

    onChange: (oldState, newState) => {
      console.log(newState.count);
    }
});

Stream

A stream crate will not persist any data, this means that you can use this crate as a simple message channel that holds the last transmitted message.

const Stream = Allotize.Crate({
    route: "myStream",
    persist: false,
    state: {
      count: 0,
    },

    onChange: (oldState, newState) => {
      console.log(newState.count);
    }
});

Generator

Oftentimes you want to be able to dynamically create new routes:

let counter = (name) => Allotize.Crate({
    route: `counters/${name}`,

    state: {
        count: 0,
    },
})

React.js

By using the allotize-react package, you can use the useAllotizeState-hook to easily use Allotize in your app.

Installation

npm install react
npm install allotize@0.0.1-alpha.1
npm install allotize-react@0.0.1-alpha

Allotize is internally using WebAssembly, which must be dynamically imported. Information on dynamic imports can be found at https://javascript.info/modules-dynamic-imports. You will also need to handle .wasm files. If you struggle setting up WASM support, you can use our template create-allotize-app (see bottom of the page).

Usage

import React from "react";
import { useAllotizeState } from "allotize-react";

export function Counter() {
    const [allotize, setAllotize] = useAllotizeState({
        route: "counter",
        state: {
            count: 0,
        }
    });

    return (
      <div>
        {allotize.count}
        <button onClick={() => setAllotize({...allotize, count: allotize.count += 1})}>+</button>
        <button onClick={() => setAllotize({...allotize, count: allotize.count -= 1})}>-</button>
      </div>
    );
}

Allotize

  • ?: optional
// Creates a shared state
Allotize.Crate({
    // URI for the resource
    route: 'String',
    // Specifies if the resource should be persisted
    persist: '?bool',
    // Callback when a change is made to the resouce
    onChange: '?function',
    // Callback for when a local change is made to the resouce
    onLocalChange: '?function',
    // Callback for when a remote change is made to the resouce
    onRemoteChange: '?function',
    // List of callbacks to call when a change is made to the resouce
    onChangeCallbacks: '?array[]function',
    // Throttle interval for sending and syncing changes
    throttleInterval: '?int (ms) [DEFAULT=350ms]',
    // Data for your resouce
    data: {
        field1: 'value1',
        field2: 'value2',
        ...,
        fieldN: 'valueN',
    }
})
// Returns connection information
Allotize.metadata()
// Returns all objects stored by Allotize
Allotize.get_all()
// Removes an item from Allotize by its key
Allotize.remove(key: 'String')

allotize-react

  • ?: optional
import {useAllotizeState} from 'allotize-react';

const [allotize, setAllotize] = useAllotizeState({
    route: "counter",
    ?config: {
        // Specifies if the resource should be persisted
        persist: '?bool',
        // Callback when a change is made to the resouce
        onChange: '?function',
        // Callback for when a local change is made to the resouce
        onLocalChange: '?function',
        // Callback for when a remote change is made to the resouce
        onRemoteChange: '?function',
        // List of callbacks to call when a change is made to the resouce
        onChangeCallbacks: '?array[]function',
        // Throttle interval for sending and syncing changes
        throttleInterval: '?int (ms) [DEFAULT=350ms]',
    }
    state: {
        count: 0,
    }
});