Osy#betaRun it on Osyrin: osyrin.com

Reference / UI

A callback that answers

component Adder(Func<string, Note> add) { action Save() { var made = add(title); } }

A component's callback parameter may return a value. Typed `Func<T, R>` and invoked from an action, it runs the parent's handler to completion and answers what the handler returned — and an exception the handler throws reaches the component at that call, where its own `try` catches it.

preview2 examples compiled by CIuiauthoringcompositioncallbacks

Summary#

A reusable component often needs its parent to do something and tell it how that went: make the record, save the draft, check the name. Declare the callback as a Func that returns what the parent makes, and call it from an action:

app Notes {
  model "model/**/*.osy";
  use Osysharp.Ui;
}

entity Note {
  [Required, MaxLength(80)] string Title;
  security { allow read, create when IsAnonymous || IsAuthenticated; }
}

[Composable] component Adder(Func<string, Note> add) {
  string title = "";
  string error = "";
  string made = "";
  action Save() {
    try {
      var note = add(title);          // runs the parent's handler, and waits for it
      made = "Made " + note.Title;
    } catch (ValidationException ex) {
      error = ex.Message;             // the parent's own words
    }
  }
  render {
    Stack(gap: 2) {
      Input(value: title);
      Button("Save", onPress: Save);
      Text(made);
      Text(error);
    }
  }
}

[Route("/")] [AllowAnonymous]
component Home() {
  Note Make(string t) {
    if (t == "") { throw new ValidationException("Name it."); }
    var note = new Note { Title = t };
    UnitOfWork.Commit();               // the act IS the save — without it the note is never stored
    return note;
  }
  render { Adder(add: Make); }
}

Signature#

component C(Func<T, R> handle)        // a callback that answers an R
handle(value)                         // in an action: waits for the parent, answers its R

C(handle: ParentMethod)               // the parent passes a method that returns an R
R ParentMethod(T value) { … }

Description#

It waits — with or without await#

A call that has to wait simply suspends where it is and resumes with the answer, so Func<T, R> called like any function is enough: the action pauses at the call until the parent's handler has finished, then carries on with its value. C#'s own spelling — a Func<T, Task<R>> the component awaits, and an async Task<R> method in the parent — means the same thing and works as written (async / await and Task):

app Notes {
  model "model/**/*.osy";
  use Osysharp.Ui;
}

entity Note {
  [Required, MaxLength(80)] string Title;
  security { allow read, create when IsAnonymous || IsAuthenticated; }
}

[Composable] component Adder(Func<string, Task<Note>> add) {
  string title = "";
  string made = "";
  action Save() {
    var note = await add(title);
    made = "Made " + note.Title;
  }
  render {
    Stack(gap: 2) {
      Field("Title", value: title);
      Button("Save", onPress: Save);
      Text(made);
    }
  }
}

[Route("/")] [AllowAnonymous]
component Home() {
  async Task<Note> Make(string t) {
    var note = new Note { Title = t };
    UnitOfWork.Commit();
    return note;
  }
  render { Adder(add: Make); }
}

The parent's exception is the component's to catch#

If the parent's handler throws — a rule the component knows nothing about, a name that is taken — the exception is raised at the call in the component's action. A try around the call catches it, which is how a dialog stays open and shows the parent's reason instead of closing on something that was never made. Uncaught, it fails the component's action exactly as an exception of its own would.

What the parent passes#

The parent names a method of its own that returns the answer — add: Make, as an event handler is passed by name. The method may do anything an action may: create rows, read data, call a server function. A parent may also pass a lambda (add: t => new Note { Title = t }); a lambda is an expression, so it answers at once.

Action is the callback that answers nothing#

A void Action<T> callback is fired and the component carries on without waiting: nothing comes back, so there is nothing to wait for. When the component needs to know how it went — a value, or that it was refused — declare a Func.

One action at a time, still#

A page runs its actions one at a time. The parent's handler runs inside the waiting action rather than after it, so it sees the same unsaved changes the action made, and its own changes are kept with them until the page saves.

Examples#

[Composable] component FieldList(CustomField[] fields, Func<FieldDraft, CustomField> add) {
  string addError = "";
  action Add(FieldDraft draft) {
    try { var made = add(draft); }
    catch (Exception ex) { addError = ex.Message; }
  }
}

// the parent:
CustomField AddGuestField(FieldDraft d) => FillField(new GuestField(), d, fields.Count);
// …
FieldList(fields, AddGuestField);

See also#

Related

component

The one archetype for all UI: a bounded reactive unit — typed props, reactive members (fields, `live`…

Func<T, R>

A parameter or class field typed `Func<T, R>` takes a lambda and can be invoked for a result, so reusable code can be…

async / await and Task

C#'s async and await, with C#'s meaning. A call to a Task-returning method that you do not await where you make it…

try / catch / finally

Handles a fault. C#'s syntax, including typed catches, catch filters and finally. The rows written inside a try block…