Dashboard Modularity using Tuxboard
In this post, we look at breaking up our dashboard into more modular components for easier access
What's Next?
- Introducing Tuxboard
- Layout of a Tuxboard dashboard
- Dashboard Modularity using Tuxboard
- Moving Widgets in Tuxboard
- Creating a Tuxbar for Tuxboard
- Managing Layouts in Tuxboard: Simple Layout Dialog
- Managing Layouts in Tuxboard: Advanced Layout Dialog
- Adding Widgets with a Tuxboard Dialog
- Using Widget Toolbars (or Deleting Widgets)
- Creating User-Specific Dashboards
- Creating Default Dashboards using Roles
- Creating Default Widgets using Roles
- Creating Custom Tuxboard Widgets
Full examples located at Tuxboard.Examples.
Since we now understand how Tuxboard is organized into sections, let's shift our focus to the layout of the dashboard itself and how we can make it a little more modular for our needs.
In one example, everything loads through the Index.cshtml
page. While this approach is very simplistic, there are a couple reasons why we should avoid this type of dashboard:
- The rendering of the page occurs all at once and then displays the dashboard. If a user has a lot of widgets, it'll take a while to render the page for processing causing the user to wait.
- It just looks messy.
What we need is to make the dashboard a little more modular. This means breaking the dashboard into multiple ViewComponents. We want to present the user with the skeleton of a dashboard and then load the widget's content dynamically. There is a method to the madness, but using ViewComponents has a number of benefits.
Important Note: If you notice the code below, there are places where ID's are being displayed (id="@Model.DashboardId"
). If you are creating a public/static dashboard, REMOVE the id's from the HTML. The ID's are specific to when users log in to their own customized dashboard and used to identify when widgets are moved using TypeScript/JavaScript. DO NOT use them when displaying a public dashboard. This could be a potential security issue.
Creating the Tuxboard Template
If you look at a simple dashboard project, you'll notice the Index.cshtml
page contains everything which essentially slows down the rendering of the page. We want to break this down into it's own ViewComponents.
We'll breakdown it down by building a TuxboardTemplate ViewComponent as our top-level component.
- Create a
TuxboardTemplate
folder underShared\Components
- Add a
Default.cshtml
and aTuxboardTemplateViewComponent
class file.
Shared\Components\TuxboardTemplate\Default.cshtml
@model Tuxboard.Core.Domain.Entities.Dashboard
<section class="dashboard" id="@Model.DashboardId">
@foreach (var tab in Model.Tabs)
{
foreach (var layout in tab.Layouts)
{
@await Component.InvokeAsync("layouttemplate", layout)
}
}
</section>
Shared\Components\TuxboardTemplate\TuxboardTemplateViewComponent.cs
[ViewComponent(Name="tuxboardtemplate")]
public class TuxboardTemplateViewComponent : ViewComponent
{
public IViewComponentResult Invoke(Dashboard model)
{
return View(model);
}
}
- Confirm you have the
"@addTagHelper *, <yourProjectName>"
in your_ViewImports.cshtml
file. - Remove all of your code from the
Index.cshtml
and replace it with the following snippet below:
<vc:tuxboardtemplate model="Model.Dashboard"></vc:tuxboardtemplate>
Since you are already passing in a full Dashboard model, this will simply pass it into the ViewComponent.
A couple notes regarding the code:
- The ViewComponent name is all lowercase. Since all HTML tag elements are lowercase, it makes sense to follow the standard.
- The name of the ViewComponent MUST match the case of the HTML ViewComponent for it to work as expected.
- I purposefully called the ViewComponent TuxboardTemplate because if we went with Tuxboard, it would've caused confusion with Visual Studio thinking the folder name was a classname.
- We loop through all Layouts (which, for now, should be just one) and pass them asynchronously into the LayoutTemplateViewComponent which we'll create next.
If you decide to run this, you'll get an error saying it can't find the LayoutTemplateViewComponent. We need to create that ViewComponent next.
Creating the Layout Template
The layout template is the next ViewComponent to create. The Layout Template contains the rows and columns of our dashboard along with our widgets and their positions sorted by index. It's almost exactly the same as the Tuxboard Template.
- Create a
LayoutTemplate
folder underShared\Components
- Add a
Default.cshtml
and aLayoutTemplateViewComponent
class file.
Shared\Components\LayoutTemplate\Default.cshtml
@model Tuxboard.Core.Domain.Entities.Layout
<form method="post">
<div class="dashboard-tab" data-id="@Model.TabId">
@foreach (var row in Model.LayoutRows.OrderBy(e => e.RowIndex))
{
<div class="layout-row" data-id="@row.LayoutRowId">
@foreach (var col in row.GetColumnLayout())
{
<div class="column @col.ColumnClass">
@foreach (var wp in row.WidgetPlacements.Where(y => y.ColumnIndex == col.Index).OrderBy(e => e.WidgetIndex))
{
@await Component.InvokeAsync("widgettemplate", wp)
}
</div>
}
<div class="clearfix"></div>
</div>
}
<div class="clearfix"></div>
</div>
</form>
Shared\Components\LayoutTemplate\LayoutTemplateViewComponent.cs
[ViewComponent(Name="layouttemplate")]
public class LayoutTemplateViewComponent : ViewComponent
{
public IViewComponentResult Invoke(Layout layout)
{
return View(layout);
}
}
The LayoutTemplate is using the Layout class to populate the dashboard. First, the row is displayed ordered by the .RowIndex
property. We, then proceed to create each column through the .GetColumnLayout()
method by Layout property containing CSS classes delimited by a slash (/). The property .ColumnClass
is used for each column.
We finally loop through all of the WidgetPlacements specific for the column and use the widgettemplate ViewComponent to render each widget.
The last step is creating the widget template ViewComponent.
Finishing with the Widget Template
The widget template is the last piece for making Tuxboard more modular. Again, it's the same process as the other two ViewComponents.
- Create a
WidgetTemplate
folder underShared\Components
- Add a
Default.cshtml
and aWidgetTemplateViewComponent
class file.
Shared\Components\WidgetTemplate\Default.cshtml
@model Tuxboard.Core.Domain.Entities.WidgetPlacement
@if (Model.UseTemplate)
{
<!-- Widgets -->
<div class="card @(Model.Collapsed ? "collapsed" : string.Empty)"
data-id="Model.WidgetPlacementId">
<div class="card-header">
@Model.GetSettingValueByName("widgettitle")
</div>
<div class="card-body">
@await Component.InvokeAsync(Model.Widget.Name, Model)
</div>
</div>
}
else
{
@await Component.InvokeAsync(Model.Widget.Name, Model)
}
Shared\Components\WidgetTemplate\WidgetTemplateViewComponent.cs
[ViewComponent(Name="widgettemplate")]
public class WidgetTemplateViewComponent : ViewComponent
{
public IViewComponentResult Invoke(WidgetPlacement placement)
{
return View(placement);
}
}
I feel we need to explain the Widget template a little bit.
The condition at the top (if (Model.UseTemplate)
) is what wraps the widget with a header and border around it. UseTemplate is a property on the WidgetPlacement letting the ViewComponent know whether it's a static widget or not.
Look at it this way: If it doesn't use a template (the else
), it will render the widget ViewComponent without borders making it a static widget on the dashboard. The whole idea is for each widget to have a handle or grip (a.k.a the header) so a user can move widgets around the dashboard. If there isn't a header, there won't be options for the widget (which we'll add later and cover in a future post, but this is the ViewComponent where we'll do it).
When you run the application, it should look exactly as it was before. The only difference is it's a little more modular so we can work with each individual part of the dashboard.
Why Did We Do This?
There are a couple of reasons we separated everything:
- All of our widgets will have a consistent UI in our dashboard.
- In the future, we'll incorporate
Refresh
features into the Tuxbar for refreshing the dashboard and provide a way to refresh individual widgets through the widget toolbar. For the long term, it's easier to request a single widget ViewComponent than a full dashboard and parsing HTML to find a widget. - We're more confident in adding features to an isolated ViewComponent without breaking something else.
When we made this dashboard more modular, it throws us into the "pit of success" for additional features.
Conclusion
In this post, we broke apart a dashboard into something more modular. Now that we've finished breaking out the ViewComponents, we can look at moving widgets on the dashboard.
What's Next?
- Introducing Tuxboard
- Layout of a Tuxboard dashboard
- Dashboard Modularity using Tuxboard
- Moving Widgets in Tuxboard
- Creating a Tuxbar for Tuxboard
- Managing Layouts in Tuxboard: Simple Layout Dialog
- Managing Layouts in Tuxboard: Advanced Layout Dialog
- Adding Widgets with a Tuxboard Dialog
- Using Widget Toolbars (or Deleting Widgets)
- Creating User-Specific Dashboards
- Creating Default Dashboards using Roles
- Creating Default Widgets using Roles
- Creating Custom Tuxboard Widgets
Full examples located at Tuxboard.Examples.