Skip to content

Commit

Permalink
Build MVP docs & examples (closes #9)
Browse files Browse the repository at this point in the history
  • Loading branch information
wwwillchen committed Dec 20, 2023
1 parent d8e8c57 commit d7ef69c
Showing 19 changed files with 315 additions and 138 deletions.
122 changes: 0 additions & 122 deletions docs/concepts.md

This file was deleted.

1 change: 0 additions & 1 deletion docs/concepts/components.md

This file was deleted.

1 change: 0 additions & 1 deletion docs/concepts/interactivity.md

This file was deleted.

86 changes: 85 additions & 1 deletion docs/getting_started.md
Original file line number Diff line number Diff line change
@@ -5,4 +5,88 @@ hide:

# Getting Started

TODO
This guide will walk you step by step through building a simple interactive Mesop app.

## Installing Mesop

Currently, Mesop isn't installable by `pip` so you'll need to clone the [GitHub repo](https://github.com/google/mesop) and follow our [internal development guide](internal/development.md). In the future, we plan to provide a pip package and a CLI to make it easier to get started with Mesop.

## Hello World app

Let's start by creating a simple Hello World app in Mesop:

```python
--8<-- "mesop/examples/docs/hello_world.py"
```

This simple example demonstrates a few things:

- Every Mesop app starts with `import mesop as me`. This is the only recommended way to import mesop, otherwise your app may break in the future because you may be relying on internal implementation details.
- `@me.page` is a function decorator which makes a function a _root component_ for a particular path. If you omit the `path` parameter, this is the equivalent of doing `@me.page(path="/")`.
- `app` is a Python function that we will call a __component__ because it's creating Mesop components in the body.

## Components

Components are the building blocks of a Mesop application. A Mesop application is essentially a tree of components.

Let's explain the different kinds of components in Mesop:

- Mesop comes built-in with __native__ components. These are components implemented using Angular/Javascript. Many of these components wrap [Angular Material components](https://material.angular.io/components/).
- You can also create your own components which are called __user-defined__ components. These are essentially Python functions like `app` in the previous example.

## Counter app

Let's build a more complex app to demonstrate Mesop's interactivity features.

```python
--8<-- "mesop/examples/docs/counter.py"
```

This app allows the user to click on a button and increment a counter, which is shown to the user as "Clicks: #".

Let's walk through this step-by-step.

### State

The `State` class represents the application state for a particular browser session. This means every user session has its own instance of `State`.

`@me.stateclass` is a class decorator which is similar to Python's [dataclass](https://docs.python.org/3/library/dataclasses.html) but also sets default value based on type hints and allows Mesop to inject the class as shown next.

> Note: Everything in a state class must be serializable because it's sent between the server and browser.
### Event handler

The `button_click` function is an event handler. An event handler has a single parameter, `event`, which can contain a value (this will be shown in the next example). An event handler is responsible for updating state based on the incoming event.

`me.state(State)` retrieves the instance of the state class for the current session.

### Component

Like the previous example, `main` is a Mesop component function which is decorated with `page` to mark it as a root component for a path.

Similar to the event handler, we can retrieve the state in a component function by calling `me.state(State)`.

> Note: it's _not_ safe to mutate state inside a component function. All mutations must be done in an event handler.
Rendering dynamic values in Mesop is simple because you can do standard Python string interpolation use f-strings:

```python
me.text(f"Clicks: {state.clicks}")
```

The button component demonstrates two aspects of calling a Mesop component:

```python
with me.button(on_click=button_click):
me.text("Increment")
```

The `with` statement allows you to nest components inside another component. In this case, we want to show the text "Increment", so we call `me.text` as a child component inside of `me.button`.

The `on_click` argument is how you wire the event handler defined above to a specific component. Whenever a click event is triggered by the component, the registered event handler function is called.

In summary, you've learned how to define a state class, an event handler and wire them together using interactive components.

## What's next

To learn more about Mesop, I recommend reading the [Guides](./guides/components.md) and then spend time looking at the [examples on GitHub](https://github.com/google/mesop/tree/main/mesop/examples). As you build your own applications, you'll want to reference the [Components API reference](./components/button.md) docs.
55 changes: 55 additions & 0 deletions docs/guides/components.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Components

Please read [Getting Started](../getting_started.md) before this as it explains the basics of components. This page provides an in-depth explanation of the different types of components in Mesop.

## Kinds of components

### Native components

Native components are components implemented using Angular/Javascript. Many of these components wrap [Angular Material components](https://material.angular.io/components/). Other components are simple wrappers around DOM elements.

If you have a use case that's not supported by the existing native components, please [file an issue on GitHub](https://github.com/google/mesop/issues/new) to explain your use case. Given our limited bandwidth, we may not be able to build it soon, but in the future, we will enable Mesop developers to build their own custom native components.

### User-defined components

User-defined components are essentially Python functions which call other components, which can be native components or other user-defined components. It's very easy to write your own components, and it's encouraged to split your app into modular components for better maintainability and reusability.

## Composite components

Composite components allow you to compose components more flexibly than regular components. A commonly used composite component is the [button](../components/button.md) component, which accepts a child component which oftentimes the [text](../components/text.md) component.

Example:

```python
with me.button():
me.text("Child")
```

You can also have multiple composite components nested:

```python
with me.box():
with me.box():
me.text("Grand-child")
```

Sometimes, you may want to define your own composite component for better reusability. For example, let's say I want to define a scaffold component which includes a menu positioned on the left and a main content area, I could do the following:

```python
@me.composite
def scaffold(url: str):
with me.box(style="background: white"):
menu(url=url)
with me.box(style=f"padding-left: {MENU_WIDTH}px"):
me.slot()
```

Now other components can re-use this scaffold component:

```python
def page1():
with scaffold(url="/page1"):
some_content(...)
```

This is similar to Angular's [Content Projection](https://angular.io/guide/content-projection).
63 changes: 63 additions & 0 deletions docs/guides/interactivity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Interactivity

This guide continues from the Counter app example in [Getting Started](../getting_started.md#counter-app) and explains advanced interactivity patterns.

## State

Example state class:

```python
@me.stateclass
class State:
clicks: int
```

Each user session, which is equivalent to a browser session, will have its own state instance. Everything in a state class, recursively, must be serializable. This is because, under the hood, Mesop is sending the state back and forth between the server and browser client.

### Nested State

You can also have classes inside of a state class as long as everything is serializable:

```python
class NestedState:
val: int

@me.stateclass
class State:
nested: NestedState

def app():
state = me.state(State)
```

> Note: you only need to decorate the top-level state class with `@me.stateclass`. All the nested state classes will automatically be wrapped.
## Slow / async patterns

These are patterns for dealing with common use cases such as calling a slow blocking API call or a streaming API call.

### Loading

If you are calling a slow blocking API (e.g. several seconds) to provide a better user experience, you may want to introduce a custom loading indicator for a specific event.

> Note: Mesop has a built-in loading indicator at the top of the page for all events.
```python
--8<-- "mesop/examples/docs/loading.py"
```

In this example, our event handler is a Python generator function. Each `yield` statement yields control back to the Mesop framework and executes a render loop which results in a UI update.

Before the first yield statement, we set `is_loading` to True on state so we can show a spinner while the user is waiting for the slow API call to complete.

Before the second (and final) yield statement, we set `is_loading` to False, so we can hide the spinner and then we add the result of the API call to state so we can display that to the user.

> Tip: you must have a yield statement as the last line of a generator event handler function. Otherwise, any code after the final yield will not be executed.
### Streaming

This example builds off the previous Loading example and makes our event handler a generator function so we can incrementally update the UI.

```python
--8<-- "mesop/examples/docs/streaming.py"
```
2 changes: 2 additions & 0 deletions docs/concepts/pages.md → docs/guides/pages.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Pages

You can define multi-page Mesop applications by using the page feature you learned from [Getting Started](../getting_started.md)

## Simple, 1-page setup

If you want to create a simple Mesop app, you can do the following:
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
@@ -15,7 +15,7 @@ Mesop is a Python-based UI framework that allows you to rapidly build web demos.
- Mesop is built on battle-tested [Angular](https://angular.dev/) web framework and [Angular Material](https://material.angular.io/) components.
- Mesop provides a flexible and composable component system and allows you to build custom UIs _without_ writing Javascript/CSS/HTML.

Learn more in [Getting Started](getting_started)
Learn more in [Getting Started](./getting_started.md)

## Example app

1 change: 0 additions & 1 deletion docs/internal/modes.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# Modes

There are several modes that you can run Mesop in.
TODO bad

## Uncompiled mode

2 changes: 1 addition & 1 deletion docs/stylesheets/extra.css
Original file line number Diff line number Diff line change
@@ -140,7 +140,7 @@
}

.highlight code {
font-size: 0.75rem;
font-size: 0.7rem;
}

.md-tabs__link {
1 change: 1 addition & 0 deletions mesop/examples/BUILD
Original file line number Diff line number Diff line change
@@ -14,6 +14,7 @@ py_library(
srcs = glob(["*.py"]),
deps = [
"//mesop",
"//mesop/examples/docs",
"//mesop/examples/shared",
],
)
13 changes: 13 additions & 0 deletions mesop/examples/docs/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
load("//build_defs:defaults.bzl", "py_library")

package(
default_visibility = ["//build_defs:mesop_internal"],
)

py_library(
name = "docs",
srcs = glob(["*.py"]),
deps = [
"//mesop",
],
)
Empty file added mesop/examples/docs/__init__.py
Empty file.
Loading

0 comments on commit d7ef69c

Please sign in to comment.