Osy#betaRun it on Osyrin: osyrin.com

Reference / UI

The site's frame — a menu, a strip and a footer staff change

live var nav = SiteMenu("menu", [ new NavItem { Label = "Lamps", To = "/lamps" } ]); · slot promo { Region("announcement", allow: [...]) { … } } · StoreFooter(chrome) { slot about { Region("footer", allow: [...]) { … } } }

The frame around every page — the menu, the strip across the top, the footer — can be the staff's to change, like a page's regions: `SiteMenu(…)` makes the menu a list of links they add to, rename, reorder and hide, and a `Region` in the layout's `promo` slot or in `StoreFooter`'s `about` slot makes the strip and the footer blocks they arrange. All of it follows the site's versions, dates and preview; the header's structure and the account and bag stay code.

stable1 example compiled by CIuiblockscontentnavigation

Summary#

A shop changes its frame as often as its front page: a strip saying "order by the 20th for Christmas", a "Christmas gifts" entry in the menu for three weeks, the holiday opening hours in the footer. Write the frame once, with what it says TODAY as its default, and those three become the staff's to change at the page desk (The PageBuilder kit — the desk staff arrange the site at) — in versions of the site, dated, previewed first.

live var menu = SiteMenu("menu", [ new NavItem { Label = "Lamps", To = "/lamps" } ]);   // the menu, as data
slot promo { Region("announcement", allow: [Strip]) { Strip(words: "…"); } }            // the strip across the top
StoreFooter(chrome) { slot about { Region("footer", allow: [About]) { About(words: "…"); } } }   // the footer

Signature#

live var <name> = SiteMenu("<menu name>", [ new NavItem { Label = "…", To = "/…", Exact = false, Children = [ … ] }, … ]);
slot promo { Region("<region name>", allow: [<blocks>]) { <default blocks> } }                     // in the layout
StoreFooter(chrome) { slot about { Region("<region name>", allow: [<blocks>]) { <default blocks> } } }

Description#

SiteMenu("name", [ … ]) is the value of a live var in the layout: the menu's name, and its default entries written out as NavItems — Label, To, Exact, and one level of Children. Hand it to the shell (Nav = menu) and the header, the phone's bar, the menu drawer and the footer's columns all read it.

Until staff publish a menu of their own it IS that list, entry for entry — the site looks exactly as the code has it. At the desk a menu is edited as a LIST, not as blocks: each entry's words and where it goes (a page of the site, a campaign page, or an outside https:// address), up and down, Hide (the entry and the entries inside it), Move in (under the entry above), dates it shows between, and its words in each language.

app Lantern {
  use Osysharp.Ui;
  use Osysharp.Storage;
  use Osysharp.Blocks;
  use Osysharp.PageBuilder@1;
  model "model/**/*.osy";
}

using Osysharp.Blocks;
using Osysharp.PageBuilder;

[Role] public enum AppRole { Authenticator, Staff }

[Principal]
public entity Member {
  [Required, Unique, MaxLength(200)] string Email;
  security { allow read when IsAuthenticated where Id == user.Id; }
}

[RoleGrant] public entity RoleGrant {
  [Required] Member Member;
  [Required] AppRole Role;
  security { allow read when IsAuthenticated; }
}

policy IsStaff => RoleGrant.Any(g => g.Member == user && g.Role == AppRole.Staff);

policy ArrangesPages => IsStaff;   // Osysharp.Blocks' one question: who arranges the site

/// One line across the top of every page.
[Block("Strip")]
component Strip([Label("What it says"), MaxLength(140)] string words, [Label("Goes to")] PageLink link = "/") {
  render { StorePromo(words); }
}

/// A few words under the shop's name in the footer.
[Block("About")]
component About([Label("Words"), MaxLength(300)] string words) {
  render { Text(words, color: Colors.TextMuted); }
}

[Layout]
[AllowAnonymous]
component Shop() {
  live var menu = SiteMenu("menu", [
    new NavItem { Label = "Lamps", To = "/lamps" },
    new NavItem { Label = "Bulbs", To = "/bulbs" },
  ]);
  live var chrome = new AppChrome { Product = "Lantern", Home = "/", Nav = menu };
  render {
    StorefrontShell(chrome) {
      SiteLook();
      Outlet;
      slot promo {
        Region("announcement", allow: [Strip]) { Strip(words: "Free delivery on anything that glows"); }
      }
      slot footer {
        StoreFooter(chrome) {
          slot about {
            Region("footer", allow: [About]) { About(words: "Old lamps, rewired by hand."); }
          }
        }
      }
    }
  }
}

[Route("/")]
[AllowAnonymous]
component Front() { render { Text("Lamps for dark evenings", p: 8); } }

[Route("/lamps")]
[AllowAnonymous]
component Lamps() { render { Text("The lamps", p: 8); } }

[Route("/bulbs")]
[AllowAnonymous]
component Bulbs() { render { Text("The bulbs", p: 8); } }

[Route("/staff/pages")]
[Authorize(IsStaff)]
component StaffPages() { render { Box(p: 6) { PagesDesk(); } } }

The strip and the footer — regions in the layout#

A Region in the layout is part of the FRAME: it is on every page, the desk lists it under Around every page, and previews it on the front page. Put the strip's region in the shell's promo slot, and the footer's in StoreFooter's about slot — under the site's name, beside the menu's columns. They are ordinary regions ([[ui-blocks#region]]): the code's blocks, the default written inside, dates per block — so a strip dated 1–21 December comes and goes by itself, and a region whose only block has ended shows nothing at all.

The general site contract ships the block TYPES for both (Osysharp.Site.Looks, Looks — a site's whole design, picked per version): Announcement for the strip, and FooterWords, FooterLink, OpeningHours, Address and FooterSignUp for the footer — drawn by the shop kit's plain design, or by the look the site wears. In an app with looks, each look's frame declares the menu and these two regions under the same names, so what staff arranged comes along to every look.

Versions, dates and the preview#

The menu and the frame's regions follow the site's VERSIONS like every region: a Christmas version carries its own strip, a "Christmas gifts" entry and its holiday footer words as well as its palette, live on its schedule, and a member of staff previewing "Christmas as of 14 December" sees all of it on every page — nobody else does. A version made before the menu was arranged shows the main version's menu.

What reaches a customer#

Only the PUBLISHED menu of the version that is live — never a draft, never a hidden entry, never an entry outside its dates. An entry's address is a page of the site (/…) or an http(s):// web address: anything else — javascript:, //elsewhere — is refused by name when it is saved, and left out if a row says it anyway. An outside address is shown marked as leaving the site: the outside glyph beside it, and "(another site)" in its accessible name. Only staff the app grants the tables to may change any of it.

What the compiler refuses#

  • SiteMenu(…) anywhere but as a live var's value — a plain field would not follow what staff publish;
  • a name that is not a constant lowercase string, or one another region or menu already has;
  • a default entry that is not a constant new NavItem { … } — only Label, To, Exact and one level of Children (an icon or a badge is not something staff edit);
  • SiteMenu without use Osysharp.Blocks; in the manifest.

See also#

Related

Blocks and regions — pages the staff arrange

A `[Block]` is a component staff can place on a page: its parameters are the fields they fill in, and each parameter's…

The PageBuilder kit — the desk staff arrange the site at

`Osysharp.PageBuilder` is the desk where the people who run a site arrange its [[ui-blocks|regions]]: add, move, fill…

App shells

An app shell is the frame around every page: a brand, a navigation tree, the signed-in person and a menu behind them, a…

theme tokens

A `theme` block names your app's design tokens — colors, spacing, radii, and more — as reusable values. A token can…