✈️ FlightPlan Developer Guide

← Back to Guide

Creating Pages

Every page in FlightPlan is a C# class that inherits from a base page and implements BuildContent. The framework handles routing, the HTML document structure, styles, and the render lifecycle automatically.

Page Lifecycle

BasePage orchestrates the render lifecycle in this order:

  1. Authorize() - Authentication hook. Return null to continue, or return an HttpResponse to abort (e.g., redirect to login).
  2. HandleAction() - Non-HTML response hook (file downloads, etc.). Return null to continue, or return an HttpResponse to short-circuit. See HandleAction guide.
  3. ConfigureStyles() - Build the style library. Override and call base to layer in app-level styles.
  4. CreateDocument() - Build the DOM structure. In OpsO apps this is sealed and calls BuildContent.
  5. Render output - The Document is combined with the StyleLibrary and serialized to HTML.

You typically only work in BuildContent. The framework handles everything else.

Minimal Page Example

A standalone page (not OpsO) inherits from your app's base page:

public class HelloPage : MyAppBasePage
{
    protected override void BuildContent(ContainerElement content)
    {
        PageTitle = "Hello";
        content.Add(new H1("Hello, World!"));
        content.Add(new P("This is a FlightPlan page."));
    }
}

Place this class in your Pages namespace and it is automatically routable.

Routing

Pages are discovered by convention. The framework scans the PagesNamespace defined in your WebAppBase and maps URLs to class names automatically:

URL Class Name Namespace
/MyApp/Hello HelloPage MyApp.Pages
/MyApp/Reports/Monthly MonthlyPage MyApp.Pages.Reports
/MyApp LandingPage MyApp.Pages

The URL path segments after the app route map to subfolders and class names. The suffix "Page" is added automatically when searching for the class.

Handling GET and POST

BuildContent runs on every request. Use IsPostBack() to detect form submissions and GetPostBackSource() to identify which control triggered it:

protected override void BuildContent(ContainerElement content)
{
    if (IsPostBack())
    {
        string source = GetPostBackSource();
        if (source == "btnSave") HandleSave();
    }

    // Build UI - runs on both GET and POST
    var form = content.Add(new Form("/MyApp/EditItem"));
    form.Add(new Input(InputType.Text, "itemName"));
    form.Add(new Button(ButtonType.Submit, "Save")
        .WithName("btnSave"));
}

Page Helpers

BasePage provides helper methods available in any page:

Method Purpose
IsPostBack() Returns true if the request is a POST
GetPostBackSource() Returns the name of the control that triggered the postback
GetFormValue(name) Get a value from POST form data
GetQueryValue(name) Get a value from the URL query string
SetCookie(name, value, days) Set a response cookie (auto-sets Secure for HTTPS)
DeleteCookie(name) Delete a cookie by expiring it
ShowAlert(message, type) Queue an alert for display (see Alerts below)
RedirectTo(url) Abort page rendering and redirect (see Redirects below)

Alerts (ShowAlert)

ShowAlert queues a modal dialog that the user must acknowledge before interacting with the page. Alerts stack and are shown one at a time in FIFO order.

ShowAlert("Record saved successfully.", AlertType.Success);
ShowAlert("Check the warnings below.", AlertType.Warning);

Available alert types:

Type Icon Use Case
AlertType.Info ℹ️ General information
AlertType.Success Operation completed successfully
AlertType.Warning ⚠️ Something needs attention
AlertType.Error Something failed

Alerts are rendered by the AlertDialog element which is automatically injected into the document. You never need to create AlertDialog yourself.

Redirects (RedirectTo)

RedirectTo aborts page rendering at any point and sends a 302 redirect to the browser. Code after RedirectTo will not execute.

protected override void BuildContent(ContainerElement content)
{
    var item = LoadItem(GetQueryValue("id"));
    if (item == null)
        RedirectTo("/MyApp");

    // This only runs if item was found
    content.Add(new H1(item.Name));
}

Redirects with Alerts

ShowAlert and RedirectTo work together. Any alerts queued before the redirect are carried to the target page via a flash cookie and displayed there automatically:

if (!user.HasRole("Admin"))
{
    ShowAlert("You do not have access to this page.", AlertType.Error);
    RedirectTo("/MyApp");
}

// Multiple alerts are supported
ShowAlert("Session expired.", AlertType.Warning);
ShowAlert("Please log in again.", AlertType.Info);
RedirectTo("/OpsO/Login");

The alerts appear on the target page as normal modal dialogs. The user clicks OK to dismiss each one. The flash cookie is consumed on first read, so refreshing the target page will not re-show the alerts.

How It Works

Under the hood, RedirectTo throws a RedirectException which unwinds the call stack back to BasePage.Render(). The catch block writes any pending alerts into a flash cookie, then sends the 302 response. When the target page renders, BasePage.Render() checks for the flash cookie, deserializes the alerts, adds them to the page's Alerts list, and deletes the cookie.

Please Wait Overlay (WithPleaseWait)

For buttons that trigger slow operations, add WithPleaseWait to show a full-screen overlay that dims the page and disables all controls while the server processes the request. The overlay disappears automatically when the new page loads.

form.Add(new Button(ButtonType.Submit, "Generate Report")
    .WithName("btnReport")
    .WithPleaseWait("Generating report..."));

With a custom message:

form.Add(new Button(ButtonType.Submit, "Export Data")
    .WithName("btnExport")
    .WithPleaseWait("Exporting data, this may take a minute..."));

Without a message (uses the default "Please wait..."):

form.Add(new Button(ButtonType.Submit, "Refresh")
    .WithName("btnRefresh")
    .WithPleaseWait());

WithPleaseWait is also available on LinkButton for slow page navigations:

content.Add(new LinkButton("View Full Report", "/MyApp/FullReport")
    .WithPleaseWait("Loading report..."));

How It Works

The PleaseWait overlay is injected into every page automatically by BasePage. It is hidden by default and has zero visual or performance cost. WithPleaseWait adds a data-please-wait attribute to the element. When clicked, framework JavaScript detects the attribute, shows the overlay with the specified message, and disables all interactive elements. When the server responds and the browser renders the new page, the overlay is gone because the entire DOM is replaced.

Only use WithPleaseWait on controls that trigger operations the user will notice as slow. Quick filter changes and simple postbacks should not show the overlay.

Putting It All Together

Here is a complete page demonstrating forms, postback handling, alerts, redirects, and PleaseWait:

public class ReportPage : OpsOBasePage
{
    protected override void BuildContent(ContainerElement content)
    {
        PageTitle = "Monthly Report";

        if (IsPostBack() && GetPostBackSource() == "btnGenerate")
        {
            string month = GetFormValue("month");
            var result = ReportAdapter.Generate(month);

            if (!result.Success)
            {
                ShowAlert(result.Message, AlertType.Error);
                RedirectTo("/MyApp");
            }

            ShowAlert("Report generated.", AlertType.Success);
        }

        // Build UI
        content.Add(new H1("Generate Monthly Report"));

        var form = content.Add(new Form("/MyApp/Report"));
        form.Add(new Input(InputType.Text, "month")
            .WithPlaceholder("2026-01"));
        form.Add(new Button(ButtonType.Submit, "Generate")
            .WithName("btnGenerate")
            .WithPleaseWait("Generating report..."));
    }
}

← Back to Guide

Please wait...