Example Applications

Number Game

A guess-the-number game that shows how to keep a value hidden from the browser between runs.

The computer thinks of a number between 1 and 100; you guess until you find it. It is a toy, but it raises a real question: where do you keep a value that must survive from one click to the next, but must not be sent to the browser?

Every named control's value travels to the browser and back. Put the secret number in a hidden input and anyone with developer tools can read it. So the secret goes into per-user session state instead, with sp_api_modal_set and sp_api_modal_get, which stays on the server.

The script

-- Variables
DECLARE @Secret       NVARCHAR(MAX);
DECLARE @Tries        NVARCHAR(MAX);
DECLARE @Guess        NVARCHAR(MAX);
DECLARE @GuessButton  NVARCHAR(MAX);
DECLARE @NewButton    NVARCHAR(MAX);
DECLARE @GuessNo      INT;
DECLARE @SecretNo     INT;
DECLARE @TriesNo      INT;
DECLARE @Hint         NVARCHAR(MAX);
DECLARE @Message      NVARCHAR(MAX);

-- Server-side state: the secret and the number of tries
EXEC sp_api_modal_get @key = N'number_game_secret', @value = @Secret OUT;
EXEC sp_api_modal_get @key = N'number_game_tries',  @value = @Tries OUT;

-- Client-side state: what the user typed and clicked
EXEC sp_api_modal_get_value @name = N'@Guess',       @value = @Guess OUT;
EXEC sp_api_modal_get_value @name = N'@GuessButton', @value = @GuessButton OUT;
EXEC sp_api_modal_get_value @name = N'@NewButton',   @value = @NewButton OUT;

-- Start a game when there is none, or when the player asks for a new one
IF @Secret IS NULL OR @NewButton IS NOT NULL
BEGIN
    SET @SecretNo = 1 + ABS(CHECKSUM(NEWID())) % 100;
    SET @Secret = CONVERT(NVARCHAR(10), @SecretNo);
    SET @Tries = N'0';
    EXEC sp_api_modal_set @key = N'number_game_secret', @value = @Secret;
    EXEC sp_api_modal_set @key = N'number_game_tries',  @value = @Tries;
    EXEC sp_api_modal_value @name = N'@NewButton', @value = NULL;
    EXEC sp_api_modal_value @name = N'@GuessButton', @value = NULL;
    SET @GuessButton = NULL;
END

SET @SecretNo = TRY_CONVERT(INT, @Secret);
SET @TriesNo  = ISNULL(TRY_CONVERT(INT, @Tries), 0);
SET @GuessNo  = TRY_CONVERT(INT, @Guess);

-- Work out the hint before drawing, so it shows on this run
IF @GuessButton IS NOT NULL AND @GuessNo BETWEEN 1 AND 100
BEGIN
    SET @TriesNo += 1;
    SET @Tries = CONVERT(NVARCHAR(10), @TriesNo);
    EXEC sp_api_modal_set @key = N'number_game_tries', @value = @Tries;

    IF @GuessNo = @SecretNo
    BEGIN
        SET @Message = CONCAT(N'You found ', @SecretNo, N' in ', @TriesNo, IIF(@TriesNo = 1, N' try.', N' tries.'));
        EXEC sp_api_modal_set @key = N'number_game_secret', @value = NULL;   -- game over
        EXEC sp_api_modal_set @key = N'number_game_tries',  @value = NULL;
        EXEC sp_api_modal_clear @alert_text = N'Got it!', @sub_text = @Message;
        RETURN;
    END
    SET @Hint = CONCAT(@GuessNo, IIF(@GuessNo < @SecretNo, N' is too low.', N' is too high.'),
                       N' Tries so far: ', @TriesNo);
END
ELSE IF @GuessButton IS NOT NULL
    SET @Hint = N'Enter a whole number from 1 to 100.';
ELSE
    SET @Hint = N'I am thinking of a number between 1 and 100.';

-- Draw
EXEC sp_api_modal_text   @text = N'Number Game', @class = N'h3';
EXEC sp_api_modal_text   @text = @Hint;
EXEC sp_api_modal_input  @name = N'@Guess', @value = @Guess OUT, @placeholder = N'Your guess', @focus = 1, @select = 1;
EXEC sp_api_modal_button @name = N'@GuessButton', @value = N'Guess',
     @valueout = @GuessButton OUT, @class = N'btn-primary', @key = N'Enter';
EXEC sp_api_modal_button @name = N'@NewButton', @value = N'New game',
     @valueout = @NewButton OUT, @class = N'btn-outline-secondary';

-- Ready for the next guess
EXEC sp_api_modal_value @name = N'@GuessButton', @value = NULL;

What to notice

Two kinds of state. sp_api_modal_get_value reads what came back from the browser. sp_api_modal_get reads a value the server kept for this user. The secret never leaves the server; only the hint does.

Session values outlive the dialog. Values stored with sp_api_modal_set belong to the user and persist until overwritten — close the dialog halfway and the same game is waiting next time. That is why winning sets both keys back to NULL: it is the clean-up, not an afterthought.

The logic runs before the drawing. Unlike a form that saves and closes, a game stays open after each click. Working out the hint first means the reply appears on the same run as the guess.

The button is reset at the end of every run. Without the final sp_api_modal_value, the Guess button would still count as clicked on the next run, even if the user only pressed New game.

@key = N'Enter' binds the Guess button to the Enter key, and @focus/@select put the cursor back in the input with the previous guess selected — small touches that make it playable from the keyboard.