JPReumerman
JPReumerman

Reputation: 105

Is there a way to trigger two consecutive events in Blazor with a single click?

Background
I have created the card game called SET in Blazor WASM. In this game you click 3 cards and this can result in either a succesful SET, or a false submission. It works well and now I want to add an additional functionality to signal the outcome of the submission.

Desired result
First method: Upon clicking the 3rd card, the 3 cards should get a green (correct) or red (false) background and after x amount of time (say 1 second) a second method should fire.
Second method: If set was correct, replace the 3 cards with 3 new ones. If set was false, reset background color to white and reset border color to black.

Actual current result
What currently happens is that the result of the first method (green/red background) doesn't show, because there is only one callbackevent. So it does get executed, but it won't become visible until the second method is also executed, by which time either the cards have been replaced, or the backgroundcolor/bordercolor have been reset.

Tried so far
I tried to separate the two methods within the @onclick event, but there is still only one eventcallback and I could not find another way to do this so far, nor on stack overflow.

@onclick="() => { ProcessSelection(uniqueCardCombinations[index]); ProcessSetReplacement(); }

The most notable difference is that EventCallback is a single-cast event handler, whereas .NET events are multi-cast. Blazor EventCallback is meant to be assigned a single value and can only call back a single method. https://blazor-university.com/components/component-events/

I also tried the "State Container" as described here by Chris Sainty, but that only re-renders it and I couldn't adjust it to my situation (not sure that's even possible tbh): https://chrissainty.com/3-ways-to-communicate-between-components-in-blazor/

Code
I do intend to clean up the code once I get it working, making it more descriptive and splitting up the ProcessSetReplacement a bit more, but wanted to make it work first.

If you need more background/code info either let me know or you can find the entire repository here: https://github.com/John-Experimental/GamesInBlazor/tree/21_AddSetValidationVisualisation

SetPage.razor:

<div class="cardsContainer">
    @for (int i = 0; i < numberOfCardsVisible; i++)
    {
        var index = i;
        <div class="card @lineClass" style="background-color:@uniqueCardCombinations[index].BackGroundColor; 
                border-color:@uniqueCardCombinations[index].BorderColor;" 
                @onclick="() => { ProcessSelection(uniqueCardCombinations[index]); ProcessSetReplacement(); }">
            @for (int j = 0; j < uniqueCardCombinations[i].Count; j++)
            {
                <div class="@uniqueCardCombinations[i].Shape @uniqueCardCombinations[i].Color @uniqueCardCombinations[i].Border"></div>
            }
        </div>
    }
</div>

Relevant parts of SetPage.razor.cs (code behind)

private void ProcessSelection(SetCardUiModel setCard)
    {
        numberOfSelected += _uiHelperService.ProcessCardSelection(setCard);

        if (numberOfSelected == 3)
        {
            var setSubmission = uniqueCardCombinations.Where(card => card.BackGroundColor == "yellow").ToList();
            var potentialSet = _mapper.Map<List<SetCardUiModel>, List<SetCard>>(setSubmission);
            var isSet = _cardHelperService.VerifySet(potentialSet);

            _uiHelperService.SignalSetSubmissionOutcome(setSubmission, isSet);
        };
    }

private void ProcessSetReplacement()
    {
        // If it wasn't a set submission, you do nothing
        if (numberOfSelected == 3)
        {
            var redBorderedCards = uniqueCardCombinations.Where(card => card.BorderColor == "red").ToList();
            var countGreenBorders = uniqueCardCombinations.Count(card => card.BorderColor == "green");

            // The while ensures that the 'ProcessSelection' function, which is also called, has run first
            while (redBorderedCards.Count == 0 && countGreenBorders == 0)
            {
                Thread.Sleep(125);
                redBorderedCards = uniqueCardCombinations.Where(card => card.BorderColor == "red").ToList();
                countGreenBorders = uniqueCardCombinations.Count(card => card.BorderColor == "green");
            }

            // Wait 1.5 seconds so that the user can see the set outcome from 'ProcessSelection' before removing it
            Thread.Sleep(1500);

            if (countGreenBorders == 3)
            {
                // Replace the set by removing the set submission entirely from the list
                uniqueCardCombinations.RemoveAll(card => card.BackGroundColor == "yellow");
                numberOfSelected = 0;

                // Check if the field currently shows more cards than normal (can happen if there was no set previously)
                // If there are more cards, then remove 3 cards again to bring it back down to 'normal'
                numberOfCardsVisible -= numberOfCardsVisible > settings.numberOfCardsVisible ? 3 : 0;

                EnsureSetExistsOnField();
            }
            else
            {
                foreach (var card in redBorderedCards)
                {
                    card.BackGroundColor = "white";
                    card.BorderColor = "black";
                }
            }
        };
    }

Upvotes: 4

Views: 1464

Answers (2)

Henk Holterman
Henk Holterman

Reputation: 273766

When you need to execute several timed actions in an event, async is your main tool.

Blazor will recognize async event handlers:

//private void ProcessSelection(SetCardUiModel setCard) 
private async Task ProcessSelection(SetCardUiModel setCard) 
{
    ... // old code    
    await Task.Delay(1000);    
    ProcessSetReplacement();  
}

when you have more than one Task.Delay() then add StateHasChanged() calls before them.

And in the markup section:

@onclick="() => ProcessSelection(uniqueCardCombinations[index])"

Upvotes: 3

Alamakanambra
Alamakanambra

Reputation: 7891

Do not call both methods in onclick.

  • Call ProcessSelection, process result and set red/green there.
  • Call StateHasChanged()
  • Set timer to one second in ProcessSelection. Use Timer from System.Timers.
  • On Elapsed of the timer call ProcessSetReplacement.

Upvotes: 2

Related Questions