Skip to content

Creating Web Apps #4

@lsjroberts

Description

@lsjroberts

Creating Web Apps

Following on from #2, the wool/browser package provide programs for creating websites and applications.

It uses the same underlying wool/ui package as wool/terminal and wool/native to describe the layout of its elements.

Programs

All these programs allow the website / app to be built into the html, css and javascript artifacts.

wool run hello/world build

wool/browser is a disappearing framework in a similar vein to Svelte. When compiled, the framework code that is required is embedded into your app and the rest is ditched.

It is a functional library, with a single store of state at the root and declarative view functions that describe how that state is presented.

Sandbox

A sandbox application can not communicate with the outside world but can react to user input.

interface Sandbox<Model, Msg> {
  init: () => Model;
  view: (model: Model) => UI.Layout;
  update: (msg: Msg, model: Model) => Model;
}
import * as Browser from 'wool/browser';
import { layout, el, text } from 'wool/ui';

export default Browser.sandbox({
  init: () => {},
  view: (model) => layout([], el([], text`Hello, World!`)),
  update: (msg, model) => model,
});

The syntax and functionality of wool/ui is a separate work-in-progress.

Messages

Events are declaratively added to interactions, such as pressing a button. The event is given a message that will be passed into the program's update function. This can then be used to update the model. The new model is then passed into the view function to create the new view.

Custom types are discussed in #3

import * as Browser from 'wool/browser';
import { Maybe, Type } from 'wool/core';
import { Events, Input, layout, column, el, button, text } from 'wool/ui';

interface Model {
  greeting: string;
}

const SetGreeting = Type.custom('SetGreeting', Type.string);

const init = (): Model => ({
  greeting: 'Hello',
});

const view = ({ greeting }: Model) => layout(
  [],
  column([], [
    text`${greeting}, World!`,
    Input.button([], {
      onPress: Maybe.just(SetGreeting('Bonjour')),
      label: el([], text`Change greeting`),
    }),
  ]),
);

const update = (msg, model) => Type.matchOn(msg, {
  [SetGreeting]: (greeting) => ({ greeting }),
});

export default Browser.sandbox({ init, view, update });

Application

An application provides access to the document head and url. Though a routed application is probably more useful.

interface Application<Model, Msg> {
  init: (url: Url, key?) => Tuple<Model, Cmd<Msg>>;
  view: (model: Model) => Document<Msg>;
  update: (msg: Msg, model: Model) => Tuple<Model, Cmd<Msg>>;
  onUrlRequest: (req: UrlRequest) => Msg;
  onUrlChange: (url: Url) => Msg;
}

interface Document<Msg> {
  head: { title: String };
  body: UI.Layout;
}

Routed Application

The routed application is a simplified application that handles routing for you. This is probably the ideal for most web apps.

interface RoutedApplication<Model, Msg> {
  init: (url: Url, key?) => Tuple<Model, Cmd<Msg>>;
  view: (model: Model) => Document<Msg>;
  update: (msg: Msg, model: Model) => Tuple<Model, Cmd<Msg>>;
}

It will populate your model with the updated route whenever it changes.

const init = () => {};
const update = (msg, model) => model;
const view = (model) => ({
  head: [],
  body: layout(
    [],
    Type.matchOn(model[Browser.routeKey], {
      '/': () => el([], text`Welcome`),
      '/with-params': ({ greeting }) => el([], text`${greeting}`),
    }),
  ),
});

export default Browser.routedApplication({ init, update, view });
Possible implementation of Browser.routedApplication
const RouteRequest = Type.custom('RouteRequest', Browser.Type.routeRequest);
const RouteChange = Type.custom('RouteChange', Browser.Type.routeChange);

const routeKey = Type.custom('routeKey', Type.string);

Browser.routedApplication = (config) => Browser.application({
  init: () => ({
    ...config.init(),
    [routeKey]: new Route('/'),
  }),
  view: config.view,
  update: (msg, model) => {
    case newModel = Type.matchOn(msg, {
      [RouteRequest]: (req) => ({...model, route: req }),
      [RouteChange]: (url) => model,
    });

    return {
      ...config.update(msg, newModel),
      [routeKey]: newModel[routeKey],
    };
  },
  onUrlRequest: (req) => RouteRequest(req),
  onUrlChange: (url) => RouteChange(url),
});

Questions

Extra programs?

This design borrows heavily from elm/browser, which provides two further programs: element and document.

The element program provides a way to embed an elm app within an existing webpage, which won't be needed with wool.

The document program is the same as the application but without the url routing. Is that useful? Or can users who want that just noop the url functions on application?

Metadata

Metadata

Assignees

No one assigned

    Labels

    discussionIdeas open for discussion

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions