ZuloOne
English
Sign in
Menu

Asking the user from a command

Updated

A command script can stop, ask the user a question and continue with the answer: Yes/No, a choice of buttons, or a small form. A second run replays from the start unless you persist answers yourself. Short recipe: Ask from a command.

← Developer wiki

Two kinds of confirmation#

Static confirmation A question from code
Where it is set requiresConfirmation + confirmationMessage in the command's json context.Confirm / Choose / Ask in the script
When the user sees it before the command starts while the command runs, when the code decides to ask
Text fixed built from data ($"{shortage} pcs short. Post anyway?")
Answers OK / Cancel Yes / No, your own buttons, or input fields

Use the static confirmation when every run needs the same "Are you sure?". Use a question from code when the question depends on what the command found.

The three calls#

// Yes/No. Returns true or false.
if (shortage > 0 && !context.Confirm("negative", $"{shortage} pcs short. Post with a negative stock?"))
    return;

// Your own buttons. Returns the caption of the button the user pressed.
var how = context.Choose("split", "Split the order?", "By warehouse", "By date", "Keep");

// A small form. Returns the entered values.
var form = context.Ask("move", "Move the remainder",
    CommandQuestionField.Decimal("Qty", "Quantity", required: true, defaultValue: shortage),
    CommandQuestionField.Date("OnDate", "Date"),
    CommandQuestionField.Reference("Store", "Warehouse", "Store"));
var qty = form.Get<decimal>("Qty");
var onDate = form.Get<DateTime?>("OnDate");
var store = form.Get<Guid?>("Store");

The first argument is the question key. It must be unique inside the command: the client sends the answer back under this key.

CommandQuestionField has Text, LargeText, Integer, Decimal, Boolean, Date, DateTime and Reference. The form uses the same inputs as the command parameter prompt. Reference takes the dictionary by name (the platform resolves it to the metaId) or by metaId.

The calls are on CommandContext, so every command kind has them. The untyped bases (DictionaryCommandBase, DocumentCommandBase, UserCommandBase and the list variants) also expose Confirm, Choose and Ask as protected methods, so you can call them without context..

How it works: two runs, not a paused thread#

The server does not keep a request open while the user thinks. The command runs twice:

  1. The command calls Confirm. There is no answer yet, so the call throws CommandQuestionException.
  2. CommandExecutor catches it and returns a result with awaitingAnswer: true, success: false and one Ask client action. The action carries the key, the kind (confirm, choose or form), the text, the buttons and the fields.
  3. The client (executeWithAnswers in frontend/src/lib/commandQuestions.ts) shows the dialog through CommandQuestionHost.
  4. The client runs the same command again, with the same selection and parameters, plus answers: { "negative": true } in the request body.
  5. This time Confirm finds the answer and returns it. The command continues.

Answers accumulate. A command that asks three questions runs four times, and each run sends all the answers given so far. The client stops after 20 questions in a row, which protects it from a command that asks the same question forever.

Closing the dialog cancels the command. The client gets cancelled: true and shows no error, because the user chose not to continue.

Warning

Everything the command did before the question happens again on the next run. Ask before you write anything: before SaveDocumentAsync, before creating records, before calling a service that posts. Messages added with CreateUserMessage before the question are not delivered.

Warning

Do not wrap a question in catch (Exception). The catch swallows CommandQuestionException, and the command continues as if the user had answered.

Where it works#

Caller Questions
POST /api/commands2/{kind}/{id}/execute and POST /api/commands/{id}/execute the request body takes answers; the result has awaitingAnswer
The command bar, ribbon user commands, layout buttons, command buttons widget, hotkeys, "Run" in the editor the dialog appears; they all go through api.executeCommand2 / api.executeCommand
Integration tests pass answers:; see below
Mobile sync (ExecuteCommand push item) not supported: the push fails with the question text

Testing a command that asks#

The test harness takes the answers as a named argument. Without them the result reports the open question.

var id = await Db.FindCommandIdAsync("document", "PostOrder");

var first = await Db.ExecuteDocumentCommandAsync(id, orderId);
Assert.IsTrue(first.AwaitingAnswer, "asks before posting");
Assert.AreEqual("negative", first.QuestionKey, "asks about the negative stock");

var posted = await Db.ExecuteDocumentCommandAsync(id, orderId,
    answers: new Dictionary<string, object?> { ["negative"] = true });
Assert.IsTrue(posted.Success, "posts after Yes: {0}", posted.Message);

The value for Confirm is true or false. The value for Choose is the button caption. The value for Ask is a dictionary from field name to value.

What breaks if you ask outside a command#

  • Object events (OnBeforeSave, field hooks) and services called outside a command cannot pause for UI — the save transaction cannot be replayed safely.
  • The mobile client does not implement command questions yet.
  • Standalone form modules have their own command surface; do not assume the document-command question API applies there without checking.

Where next#

Was this page helpful?