BlazorT4_1200x303

How does a child component inside a TelerikWindow close the window when the user is done with dialog?

An interesting conversation on one of my previous posts about Telerik UI for Blazor got me thinking about how a component nested inside a Telerik Window can control the window (or any parent component) that uses it. While the Telerik Blazor team has immediately taken into consideration the customer feedback and is already working toward the Dialog component, for this case study, I’ll stick with the original scenario: How does a child component inside a TelerikWindow close the window when the user is done with dialog?

I’ve got three solutions but they aren’t nearly as slick as the one suggested on my previous blog post. But this also lets me talk about a best practice when creating components: These are the tools you should be using to let your parent components know when something happens inside your component. And letting your potential parents know what you’re doing is a good thing.

Before I get started (and as long as we’re on the topic of creating dialogs), don’t ignore the Telerik Predefined Dialogs. If you’re creating a custom dialog with the TelerikWindow component to provide information (an alert dialog), get approval for some action (a confirmation dialog), or accept a string (a prompt dialog) from the user … well, you don’t need to create that custom dialog. The Predefined Dialogs make those common tasks easy to do.

But let’s say you’ve got a more interesting UI inside your dialog than the Predefined Dialogs support.

Integrating Controls

I’ll start with the simplest case: You have a dialog consisting of one or more components and a button that triggers processing. When the user clicks the button, you want the dialog to disappear. The easiest way to handle that is to bind the TelerikWindow’s Visible property to a class-level variable (a field) and, in the code attached to the button, set that field to false.

That solution looks like this:

<TelerikWindow  Visible="@windowVisible">    
    <WindowContent>
         …other components…
        <TelerikButton OnClick="Process">Finish</TelerikButton>
    </WindowContent>
</TelerikWindow>

@code {
    bool windowVisible = true;

    private void CloseWindow()
    {
        windowVisible = false;
    }

Integrating Components

But what if you want your dialog to close because of something that happens inside one of the dialog’s components? What if, for example, your dialog looks like this and you want the window to close because the user has done something inside of the DoSomething component:

<TelerikWindow>    
   <WindowContent>
     <DoSomething></DoSomething>
  </WindowContent>
<TelerikWIndow>

If you’re in luck, the DoSomething component exposes an event or property that you can use to control your window. And that’s great … but how do you make that happen if you’re the one creating the DoSomething component? The easiest solution is the typical one: If the child component doesn’t care what’s happening in the parent component, then all you need is one-way event binding.

For this solution, the DoSomething component just has to declare a property as an EventCallback, flag it as a parameter, and call that EventCallback’s InvokeAsync method when the component wants to notify its parent that something has happened (and, optionally, pass some data). If your event is returning data, you need to specify the kind of data you’re returning as part of declaring the EventCallback.

Here’s the code to create a DoSomething component that notifies its parent when the user clicks on the component’s button by raising a ProcessingCompleted event and passing a Boolean value:

…other components…
<TelerikButton OnClick="@DoProcessing">Finish</TelerikButton>

@code {
    [Parameter]
    public EventCallback<bool> ProcessingCompleted { get; set; }

    private async Task DoProcessing()
    {
        …processing code…
        await ProcessingCompleted.InvokeAsync(false);
    }
}

The parent component now has a choice. If all the parent component wants to do is react to the event and run a method, this example sets my windowVisible field when the component fires its ProcessingCompleted event:

<TelerikWindow  Visible="@windowVisible">    
   <WindowContent>
      …other components…
      <DoSomething ProcessingCompleted="@CloseWindow"></DoSomething>
   </WindowContent>
</TelerikWindow>

@code {
   bool windowVisible = true;

   private void CloseWindow()
   {
      windowVisible = false;
   }
}

If the parent actually wants to use the data passed by the event, the code is only slightly more complicated. You need to use a lambda expression when catching the event and the method used in the lambda expression needs to accept the data passed from the event.

Here’s some code that uses the value passed from the event raised in the child to set the windowVisible field:

<TelerikWindow  Visible="@windowVisible">    
   <WindowContent>
      …other components…
      <DoSomething ProcessingCompleted="@(p => CloseWindow(p))"></DoSomething>
   </WindowContent>
</TelerikWindow>

@code {
    bool windowVisible = true;

    private void CloseWindow(bool close)
    {
        windowVisible = close;
    }
}

So, by adding an EventCallback to your component, you can notify any parent that uses your component that something interesting has happened in your component. Of course, what the parent component does with that information is up to the parent (in this case: closing the window).

Sharing Information

There’s a slightly more complicated solution available to you if you want the parent to share information with your component.

For example, you may not want your DoSomething child component to fire its event if, for example, the window is already closed. I recognize I haven’t got a great example in this case study, for two reasons. First, there’s no harm in closing an already closed window, so why bother checking? Second, how exactly would someone click the button in a component inside a closed window? However, this is the scenario I’ve got, and you can probably imagine a better one than I’ve used here.

To have the parent share information with the child the DoSomething component, you need to add two-way event binding inside your component. To implement two-way databinding, you need two parameters in your component. One parameter holds the data you want to share (I’ll call it the data parameter). The other parameter is just an EventCallback as we’ve seen before (I’ll call this parameter the event parameter). The two parameters are joined by name: the EventCallback must be called <data parameter name>Changed.

Now, in your DoSomething component’s code you can check the value of the data parameter and raise your event. Here’s an example with a data parameter called windowState and a matching event parameter called windowStateChanged. In my button’s code, I only raise the event if the window’s state shows that the window is open:

<TelerikWindow  Visible="@windowVisible">    
   <WindowContent>
      …other components…
      <TelerikButton OnClick="@DoProcessing">Finish</TelerikButton>
   </WindowContent>
</TelerikWindow>

@code {
   [Parameter]
   public bool windowState { get; set; }
   [Parameter]
   public EventCallback<bool> windowStateChanged { get; set; } 

    private async Task DoProcessing()
    {
        …processing code…
        If (windowState)
       {
          await windowStateChanged.InvokeAsync(false);
       }
    }
}

The payoff is that you don’t need to write a method in the parent component to set the windowVisible field: you can tie the event directly to the field. The markup required, as a result, is different from what you use with one-way binding. With two-way binding, the DoSomething component uses the data parameter’s name (prefixed with @bind-) and bind directly to the field you’re sharing between the components. The event parameter ensures that the field in the parent is updated whenever the event is raised in the DoSomething component.

This example ties the DoSomething’s windowState parameter to the parent component’s windowVisible field:

<TelerikWindow  Visible="@windowVisible">    
   <WindowContent>
      …other components…
      <DoSomething @bind-windowState="windowVisible"></DoSomething>
   </WindowContent>
</TelerikWindow>

@code {
   bool windowVisible;

But the real moral of this post is that, when you’re writing a component, it’s always a good idea to fire events when something changes inside your component. You can do it with one-way or two-way databinding but, either way, raising events lets your parent integrate what it does with what your component does.


Peter Vogel
About the Author

Peter Vogel

Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter also writes courses and teaches for Learning Tree International.

Related Posts

Comments

Comments are disabled in preview mode.