Propel rocketboost.rs

Context and composers

Every view has one context: a Rust struct that holds the data the view needs. The handler builds it, and the view reads its fields. A view cannot use a field that the struct does not have.

Define the context

Derive Serialize so Propel can read the fields.

#[derive(Serialize)]
pub struct PostsIndex {
    pub title: String,
    pub posts: Vec<Post>,
}

Build it in a handler

The route is the view composer for this page: it collects the data and passes it to the view.

#[get("/posts")]
fn posts() -> PropelView {
    PropelView::render("posts.index", &PostsIndex {
        title: "Posts".into(),
        posts: all_posts(),
    })
}

Read it in the view

Fields are read by name, as in $title or $post.title. The $ is optional, and a loop variable such as post is read the same way.

@extends('layouts.app')

@section('content')
  <h1>{{ $title }}</h1>
  @forelse (post in posts)
    <h2>{{ $post.title }}</h2>
  @empty
    <p>No posts yet.</p>
  @endforelse
@endsection

Shared data with Page

Some data is needed on every page, such as the sidebar and the previous and next links. The test app wraps each context in a Page, which adds that data next to the view's own fields.

PropelView::render("posts.index", &Page::new("/posts", PostsIndex {
    title: "Posts".into(),
    posts: all_posts(),
}))

The layout then reads the shared data like any other field:

@foreach (group in nav)
  <p>{{ $group.heading }}</p>
  @foreach (link in group.links)
    <a href="{{ $link.href }}">{{ $link.label }}</a>
  @endforeach
@endforeach

Status

The Page wrapper is part of the test app for now, not of the library. In Laravel terms, it is a view composer written by hand. Propel will offer a request guard for shared data later. A view composer that runs on every request is planned, but not built.