Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Creating Web Apps #4

Open
lsjroberts opened this issue Nov 29, 2018 · 4 comments
Open

Creating Web Apps #4

lsjroberts opened this issue Nov 29, 2018 · 4 comments
Labels
discussion Ideas open for discussion

Comments

@lsjroberts
Copy link
Contributor

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?

@lsjroberts lsjroberts added the discussion Ideas open for discussion label Nov 29, 2018
@lsjroberts
Copy link
Contributor Author

lsjroberts commented Nov 29, 2018

The route matching could be simplified from:

Type.matchOn(model[Browser.routeKey], {
  '/': () => el([], text`Welcome`),
  '/with-params': ({ greeting }) => el([], text`${greeting}`),
}),

to:

Browser.router(model, {
  '/': () => ...,
  '/with-params': ({ greeting }) => ...,
})

@lsjroberts
Copy link
Contributor Author

How do route parameters work?

Should it be defined in Browser.router? Or do routes need to be configured beforehand?

Browser.router(model, {
  '/with-params/:greeting': ({ greeting }) => ...,
})

@lsjroberts
Copy link
Contributor Author

lsjroberts commented Nov 30, 2018

Given the changes in #3, the messages would look like:

const Msg = Type.custom({
  SetGreeting: [Type.string],
});

Input.button([], {
  onPress: Maybe.just(Msg.SetGreeting('Bonjour')),
  label: el([], text`Change greeting`),
}),

const update = (msg, model) => Msg.match(msg, {
  SetGreeting: (greeting) => ({ greeting }),
});

@lsjroberts
Copy link
Contributor Author

And routing could be done like:

const Routes = Type.custom({
  Home: ['/'],
  WithParam: ['/with-param', Type.string],
});

Browser.router(Routes, model, {
  Home: () => {},
  WithParam: (greeting) => {},
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
discussion Ideas open for discussion
Projects
None yet
Development

No branches or pull requests

1 participant