DotNetT Light_870x220

A common UI pattern is the List+Details page: The top of the page has a grid (or, perhaps, just a drop-down list); the bottom of the page has the detail for the item selected at the top of the page. An example of this pattern is a page with a list of customers at the top and a list of the currently selected customer's salesorders at the bottom.

The TelerikWindow UI component in Telerik UI for Blazor makes this pattern easy to implement. Using the window effectively separates your page into two views (the top and the bottom), allowing you to develop the bottom portion displayed inside the window independently of the top portion. The window component allows you to hide or reveal the bottom of the page, depending on what's going on in the top part. Your biggest design decision is deciding how much UI you want to put inside the window.

Setting Up the Top of the Page

As an example, I'll start with a <TelerikGrid> element at the top of my page that displays a list of customers (that list is held in a field called customers in my functions block). Inside the grid, I use the <TelerikGridCommandButton> element to put a select button on each row and tie that button to method called DisplayDetail using the <TelerikGridEvents> and <EventsManager> elements. The markup to create my grid looks like this:

<TelerikGrid Data="@customers" Sortable="true">
  <TelerikGridEvents>
    <EventsManager OnEdit="@((args) => DisplayDetail(args))"></EventsManager>
  </TelerikGridEvents>
  <TelerikGridColumns>
    <TelerikGridCommandColumn>
      <TelerikGridCommandButton Command="Edit" Icon="edit">
        Select
      </TelerikGridCommandButton>
    </TelerikGridCommandColumn>
    <!-- ... -->
  </TelerikGridColumns>
</TelerikGrid>

Following the close tag for the <TelerikGridCommandColumn> element, I put the markup that defines the few columns of key information that allow the user to identify the customer they want to see details for:

<TelerikGridColumn Field="Id" Title="Customer Id" />
<TelerikGridColumn Field="LastName" Title="Last Name" />

The next step is to load the grid with customer data when the page first displays (I'm retrieving the data from a service endpoint in this code):

@functions {
  private IEnumerable<Customer> customers = null;

  protected override Task OnInitAsync()
  {
    HttpClient hc = new HttpClient();
    HttpResponseMessage rm = await hc.GetAsync("https://localhost:5001/customers");
    customers = await rm.Content.ReadAsAsync<IEnumerable<Customer>>();
    StateHasChanged();
    return base.OnInitAsync();
  }
}

With the top (List) part of the pattern in place, I can use the TelerikWindow to implement the bottom (detail) part.

Setting Up the Bottom of the Page

Initially (and until the user selects a customer in the grid), the window that displays the details is unavailable, so I've started by setting the TelerikWindow's Visible attribute to false:

<TelerikWindow Visible="false">
  <!-- ... -->
</TelerikWindow>

You can add additional attributes to the TelerikWindow element to control the window's location and size. The Size attribute, for example, will accept a value from the Size enumeration to use one of three standard sizes (Large, Medium, and Small). Alternatively, you can create a custom size by adding the Height and Width attributes to the element. In the absence of any of those attributes, the window is automatically sized to be just big enough to hold its content.

You can also set the TelerikWindow's Centered attribute to true to have the window centered in the browser window (not within the window's container). For custom positioning, the TelerikWindow's Top and Left attributes allow you to put the window wherever you want (within the browser's window, of course). Without the Centered, Top, and Left attributes, the window will display where it would appear in the “normal flow” of the page.

Wiring Up the Select Button

The next step is, in my DisplayDetail method, to activate the window and retrieve the data to be displayed in the window when the user selects a row. I first need to add two using statements at the top of the page to make the code work:

@using Telerik.Blazor
@using Telerik.Blazor.Components.Window

My DisplayDetail method, called from the grid, will automatically be passed a GridCommandEventArgs parameter that has two key properties: Item and IsCancelled. The Item property holds a reference to the Customer object displayed in the selected row; the IsCancelled property allows me to suppress the default behavior for the button (which is to switch the row out of display mode and into edit mode).

In the DisplayDetail method, I just want to move the selected Customer object into a field or property so that I can bind UI elements inside the window to properties on the Customer object. I also need to set the IsCancelled property to true to keep the row from being put into edit mode. That code looks like this:

@functions {
  private Customer currentCustomer;
    
  public void DisplayDetail(GridCommandEventArgs e)
  {
    currentCustomer = (Customer) e.Item;
    e.IsCancelled = true;

    // ...
  }
}

Of course, rather than display more information from the Customer object, I could retrieve other, related information – the sales orders for the customer or a list of shipments, for example.

Now that there's data to display, I need to make the window visible. The first step in that process is to tweak the TelerikWindow's Visible attribute so that it's bound to a field or property in my functions block. The enhanced markup looks like this:

<TelerikWindow Visible="@windowVisible">
  <!-- ... -->
</TelerikWindow>

Inside my page's functions block, I need to define the windowsVisible field:

@functions {
  private bool windowVisible;
  private Customer currentCustomer;

  // ...
}

Now, to make the window visible, I just need to set that windowVisable field to true and call Blazor's StateHasChanged method to let Blazor know that the page needs to be updated. I'll do that at the end of my DisplayDetail method:

public void DisplayDetail(GridCommandEventArgs e)
{
  currentCustomer = (Customer) e.Item;
  e.IsCancelled = true;
  windowVisible = true;
  StateHasChange();
}

Adding Content to the Window

With the top and bottom of my page defined (and their interaction), all that's left is to add content to the window. I add that content inside <TelerikWindowContent> elements inside the <TelerikWindow> element.

The following markup adds a textbox element that, using Blazor's bind attribute, implements two-way databinding between the textbox and the FirstName property on my currentCustomer object (i.e. changes to the property change the value in the textbox and changes to the value in the textbox automatically update the property). The content also includes a button that the user can click to save the changes made to that customer object through the textbox:

<TelerikWindow Visible="@windowVisible">
  <TelerikWindowContent>
    First Name: <input type="text" bind="@currentCustomer.FirstName" />
    <!-- more input elements to display/update Customer properties -->
    <input type="button" onclick="SaveCustomer" value="Save Changes" />
  </TelerikWindowContent>
</TelerikWindow>

I could also have added the <TelerikWindowTitle> element inside the <TelerikWindow> element to define a header for my window.

In the SaveChanges method called from my button, I need to do a few things. First, of course, I need to send the updated currentCustomer to the service endpoint I retrieved it from in order to update the data at the service. After that, I set the field that controls my window's visibility to false and set the currentCustomer field to null. The grid will take care of displaying the changed data so I don't need to call StateHasChanged to update the display with the changed data:

public async void SaveCustomer()
{
  HttpClient hc = new HttpClient();
  HttpResponseMessage rm = await hc.PutAsJsonAsync("https://localhost:5001/customers", 
                                                   currentCustomer);
  currentCustomer = null;
  windowVisible = false;
}

From this point on, it's just a matter of how complicated I want the bottom part of my page to be. If I want to have multiple, related views of detail data, I might add the TelerikTab control inside of my window to organize those views. If different kinds of data in my grid required different views, I might add multiple windows to my page and choose which one to make visible in my DisplayDetails method.

The key takeway is that, with this structure in place, extending this UI pattern just depends on what content you want to put in your windows and how many windows you want to include in your page.

The full download of this project is available here.


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.