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#
- component — parameters, state and actions
- Func<T, R> — a
Funcused for a value in a render expression: a selector, a formatter - async / await and Task —
async,awaitandTask - try / catch / finally — catching a fault by its type